USB 等时/同步传输、块传输与转换设置在UVC摄像头驱动中的探讨
最近在Windows10 x64环境下,开发了一个虚拟UVC摄像头驱动。确切的来说这不是摄像头驱动,而是一个虚拟USB总线驱动。使用该虚拟总线驱动使用应用软件通过IOCTL控制总线子设备的创建与卸载。
框架设计
驱动安装完成后,是一个单纯的USB虚拟总线。
应用软件通过发送自定义IOCTL码IOCTL_BUSENUM_PLUGIN_CAMERA通知驱动程序。
驱动程序收到IOCTL_BUSENUM_PLUGIN_CAMERA请求后,使用IoCreateDevice创建一个PDO,并挂入子设备链表,然后使用IoInvalidateDeviceRelations通过PNP管理器该问线子设备有变化。
后续就是一堆的子设备的枚举,详细的过程可见 windows加载即插即用PNP设备的过程 一节。
言归正转,使用该虚拟总线驱动枚举出一个子设备后,子设备会加载USB通用驱动USBCCGP,再由USBCCGP枚举出UVC Camera设备。其总线关系如下图所示:
本人的测试机为英文操作系统
- Virtula USB Bus 我们开发的虚拟总线驱动vusbbus.sys
- USB Compoite Device - USB通用驱动usbccgp.sys
- USB Cpature - USB摄像头驱动 ksthunksys和usbvideo.sys
- USB Compoite Device - USB通用驱动usbccgp.sys
UVC描述符布局
批量传输
参考UVC1.5描述符相关章节.本人的配置描述符布局如下:
这里只写重要的描述符。
- 配置描述符
- 接口关联描述符
- 接口描述符 bInterfaceNunber = 0,bAlternateSetting=0
- 类特定视频控制接口头描述符
… - 接口描述符 bInterfaceNunber = 1,bAlternateSetting=0
- 类特定视频控制接口头描述符
- …
- 端点描术符 bulk,bEndpointAddress=0x82,wMaxPacketSize=400h
可以看到,在这里使视频流的传输使用了0x82地址,最大传输字节为16KB,使用的是块传输。
在使用过程中,发现会出现一个问题,就是上层打开摄像头时,而我们通过我们的开发的应用软件很难有一个好的方法获取当前摄像头的是否处于打开状态,这样很难报告上层应用软件是否需要写数据。
等时/同步传输
在原来来块传输的基础下,修改配置描述符如下:
- 配置描述符
- 接口关联描述符
- 接口描述符 bInterfaceNunber = 0,bAlternateSetting=0
- 类特定视频控制接口头描述符
… - 接口描述符 bInterfaceNunber = 1,bAlternateSetting=0
- 类特定视频控制接口头描述符
- …
接口描述符 bInterfaceNunber = 1,bAlternateSetting=1 - 端点描术符 异步传输,bEndpointAddress=0x82,wMaxPacketSize=400h
这里将原来的接口1中的端点个数设为0,并新增加一个其转换设置为1的接口描述符,在其内部再原来的块/批量传输改为等时传输,并需要修改wMaxPacketSize=400。这是因为在等时传输的包(高速、全速)的最大上限为1024个字节,否则虽然会枚举成功,但打开摄像头时会报说设备被占用的错误导致打开失败。
注意
wMaxPacketSize的值可设置大于1024,如在本人笔记本自带的摄像头在YUV2格式时设置为0XC00,详见 UV2摄像头相关数据大小计算
另外,修改模式时,在固件中也应相应配置相应端点的工作模式,不能只改端点描述符的属性而不改变在初始化端点的相关代码。另外不同类型的端点传输数据时使用的函数也有所不同,所以一定要注意。
关于等时传输的包大小:
等时/同步传输的包大小最大1024字节,是指每次的传输,但每个URB中可以包含多个包传输。
如以WINDOWS下URB的等进传输URB结构体定义如下:
struct _URB_ISOCH_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
struct _URB *UrbLink; // Reserved
struct _URB_HCD_AREA hca; // Reserved
ULONG StartFrame;
ULONG NumberOfPackets;
ULONG ErrorCount;
USBD_ISO_PACKET_DESCRIPTOR IsoPacket[1];
};
typedef struct _USBD_ISO_PACKET_DESCRIPTOR {
ULONG Offset;
ULONG Length;
USBD_STATUS Status;
} USBD_ISO_PACKET_DESCRIPTOR, *PUSBD_ISO_PACKET_DESCRIPTOR;
这里NumberOfPackets代表可以该URB可以有多少个数据包。如将最大设为1024,则Offset为该 USBD_ISO_PACKET_DESCRIPTOR的数据偏移,为1024的整数包,Length为该包的长度,前面几包为1024,最后一包小于等于1024字节。
这里有一个比较奇怪的问题就是,如果新的接口1中的端点类型设为块传输,但上层依旧会依等时/同步传输的方式来读取数据。可能这是因为是在转换设置接口中的吧。
PS:在Linux(内核版本为5.6.11)内核源代码中uvc_video.c:2056中有如下代码,可以看到,这里是通过判断转换接口来区分是等时传输还是块传输的:
/* Select the video decoding function */
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)
stream->decode = uvc_video_decode_isight;
else if (stream->intf->num_altsetting > 1)
stream->decode = uvc_video_decode_isoc;
else
stream->decode = uvc_video_decode_bulk;
}
关于摄像头是否打开的判断
通过上层选择接口中其转换设置的不同,可以判断当前摄像头的状态。
- 如转换设置为0,表示关闭或初始化摄像头。
- 如转换设置为1,表示打开摄像头。