根据MSDN,摄像头驱动的数据采集和DirectShow显示为异步过程,两者间通过消息机制进行传递。客户端创建消息并将句柄交给驱动。当接收到一帧时,驱动将其放到消息队列中。用户端有单独的线程监听该队列,当有数据时就进行相应处理。摄像头功能具体实现时,同时存在两条buffer队列,用户端管理idle buffer队列,驱动管理ready buffer队列。
1. 向驱动“注册”用户端buffer @ CS_ALLOCATE
驱动初始化时,所有buffer均为idle buffer;驱动知道它们的存在,但无法对其进行控制。“驱动知道它们的存在”步骤在IOCTL_CS_BUFFERS -> CS_ALLOCATE时进行。在DWORD CPinDevice::AllocateBuffer( PCS_STREAM_DESCRIPTOR pCsDescriptor, LPVOID pOutBuf, DWORD OutBufLen, DWORD *pdwBytesTransferred)中
将用户传入的流标识符pCsDescriptor复制到PinDevice的成员变量m_pStreamDescriptorList的影子标识符csStreamDescriptorShadow中,并调用子函数,生成相应的handle,告知用户端和驱动。
2. 用户端将buffer使用权交给驱动 @ CS_ENQUEUE
用户端控制buffer的操作权限,在驱动需要操作buffer的时候,用户端使用CS_ENQUEUE命令把buffer从idle queue中移到ready queue中。子函数DWORD CPinDevice::EnqueueDescriptor( PCS_STREAM_DESCRIPTOR pUnMarshalCsDescriptor)是它的具体实现,参数从用户端传入。
之后的一段,
接着把用户端传入的流标识符(含数据buffer地址)映射后存储到该索引值处
注意上段倒数第二行pCsStreamDescriptorExternal变量,作为PinDevice的私有成员变量,只有此处将其赋非NULL值,可视为此索引值元素和用户端流标识符“绑定”的标志。至此,用户端将buffer交给了驱动。
3. 驱动填充buffer并交换给用户端 @ 硬件中断
在发生硬件帧同步的中断中,逐层向上回调并调用子函数,至bool CPinDevice::RemoveBufferFromList(PCS_STREAM_DESCRIPTOR * ppCsStreamDesc, PVOID * ppMappedData, PVOID * ppUnmappedData)
若pCsStreamDescriptorExternal变量非空,即用户端已将buffer交由驱动,则得到该数据buffer指针。接着调用BOOL CPinDevice::InitMsgQueueDescriptor (PCS_MSGQUEUE_BUFFER pCsMsgQBuff, PCS_STREAM_DESCRIPTOR pCsStreamDesc, PVOID pMappedData, PVOID pUnmappedData, BOOL bBufferFill),由硬件将数据写入buffer
并将包含buffer的整个流标识符放入消息内容中
最后驱动调用WriteMsgQueue函数把完成填充的一个buffer放入消息队列,供用户端异步处理。
至此完成了用户端和驱动间流媒体buffer的处理。只有在EnqueueDescriptor中将pCsStreamDescriptorExternal赋值,当其值非空时,才能在中断中将相应元素的buffer取出进行填充,取出后即将pCsStreamDescriptorExternal再次置空。手头暂时没有板子可以验证,但是可以推测,需要用户端重新发送CS_ENQUEUE控制命令,才能重新启动硬件下一帧的填充。