LINUX&UVC控制接口头描述符解析
UVC控制接口头描述符(Video Control Interface Header Descriptor )这里又称作UVC类特定视频控制接口头描述符(class-specific video control header)。无论叫什么名字,不过简称好像都叫USB_VC_HEADER。
关于该描述符各字段的具体介绍可详见:https://www.usbzh.com/article/detail-4.html
对照着LINUX&UVC驱动源代码,我们这里可以进行更加深刻地理解。
switch (buffer[2]) {
case UVC_VC_HEADER:
n = buflen >= 12 ? buffer[11] : 0;
if (buflen < 12 + n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d HEADER error\n", udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
dev->uvc_version = get_unaligned_le16(&buffer[3]);
dev->clock_frequency = get_unaligned_le32(&buffer[7]);
/* Parse all USB Video Streaming interfaces. */
for (i = 0; i < n; ++i) {
intf = usb_ifnum_to_if(udev, buffer[12+i]);
if (intf == NULL) {
uvc_trace(UVC_TRACE_DESCR, "device %d "
"interface %d doesn't exists\n",
udev->devnum, i);
continue;
}
uvc_parse_streaming(dev, intf);
}
break;
从代码分析来看,这里比较关键的三个字段分别为:
uvc_version
以BCD代码的形式表示了该设备所支持的UVC规范版本。
版本 | BCD |
---|---|
V1.0 | 0x0100 |
V1.1 | 0x0110 |
V1.5 | 0x0150 |
因UVC版本的差异,导致对一些描述符的解析或者控制请求的数据长度有所不同。具体表现在以下几个方面:
1.对于UVC处理单元,其UVC1.0与UVC1.1、UVC1.5长度不一致。
2.UVC视频控制请求的字节数。
static size_t uvc_video_ctrl_size(struct uvc_streaming *stream)
{
/*
* Return the size of the video probe and commit controls, which depends
* on the protocol version.
*/
if (stream->dev->uvc_version < 0x0110)
return 26;
else if (stream->dev->uvc_version < 0x0150)
return 34;
else
return 48;
}
clock_frequency:时钟频率HZ。
当进行UVC控制请求时,UVC1.0直接使用的是clock_frequency时钟频率,而UVC1.1和UVC1.5可以支持其它时钟频率。ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]); ctrl->bFormatIndex = data[2]; ctrl->bFrameIndex = data[3]; ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]); ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]); ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]); ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]); ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]); ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]); ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]); ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]); if (size >= 34) { ctrl->dwClockFrequency = get_unaligned_le32(&data[26]); ctrl->bmFramingInfo = data[30]; ctrl->bPreferedVersion = data[31]; ctrl->bMinVersion = data[32]; ctrl->bMaxVersion = data[33]; } else { ctrl->dwClockFrequency = stream->dev->clock_frequency; ctrl->bmFramingInfo = 0; ctrl->bPreferedVersion = 0; ctrl->bMinVersion = 0; ctrl->bMaxVersion = 0; }
这里可以在https://www.usbzh.com/tool/uvc.html 使用默认的示例dwClockFrequency也可以看到:
============UVC1.0============
0x01, 0x00, UINT16 bmHint;dwFrameInterval保持不变;
0x05, UINT8 bFormatIndex=5,
0x04, UINT8 bFrameIndex=4,
0x15, 0x16, 0x05, 0x00, UINT32 dwFrameInterval=333333,;33.3333,ms/帧
0x00, 0x00, UINT16 wKeyFrameRate=0,;缩格式中只有第1帧为关键帧;
0x00, 0x00, UINT16 wPFrameRate=0,;压缩格式P帧速率;
0x00, 0x00, UINT16 wCompQuality=0,,压缩质量1-10000;
0x00, 0x00, UINT16 wCompWindowSize;0,,平均比特率控制的窗口大小;
0x00, 0x00, UINT16 wDelay;0,,内部视频流接口延迟(毫秒);
0x00, 0x90, 0x7E, 0x00, UINT32 dwMaxVideoFrameSize;8294400,Bytes
0x0C, 0xC8, 0x00, 0x00, UINT32 dwMaxPayloadTransferSize;51212,Bytes
============UVC1.1============
0x00, 0x00, 0x00, 0x00, UINT32 dwClockFrequency=0,;指定格式的设备时钟频率HZ
0x03, UINT8 bmFramingInfo=3,负载信息位图
0x01, UINT8 bPreferedVersion=1,bFormatIndex预设版本
0x00, UINT8 bMinVersion=0,bFormatIndex最小版本
0x01, UINT8 bMaxVersion=1,bFormatIndex最大版本
// 34 bytes
bInCollection
第三个比较重要的参数就是bInCollection。其位直为buffer[11],其代表着视频流接口个数。
一般一个UVC视频设备只有一个视频流接口,本人也未曾见到过多个视频流的设备,但并不代表这种设备不存在。从LINUX源代码和UVC规范上来讲,人家是支持的。
n = buflen >= 12 ? buffer[11] : 0;
...
/* Parse all USB Video Streaming interfaces. */
for (i = 0; i < n; ++i) {
intf = usb_ifnum_to_if(udev, buffer[12+i]);
if (intf == NULL) {
uvc_trace(UVC_TRACE_DESCR, "device %d "
"interface %d doesn't exists\n",
udev->devnum, i);
continue;
}
uvc_parse_streaming(dev, intf);
}
bInCollection的为baInterfaceNr数组,其个数由bInCollection决定。UVC源代码使用usb_ifnum_to_if函数分别获取对应的接口描述符的指针,并使用uvc_parse_streaming解析该视频流接口描述符。
struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev,
unsigned ifnum)
{
struct usb_host_config *config = dev->actconfig;
int i;
if (!config)
return NULL;
for (i = 0; i < config->desc.bNumInterfaces; i++)
if (config->interface[i]->altsetting[0]
.desc.bInterfaceNumber == ifnum)
return config->interface[i];
return NULL;
}