UVC等时传输中的dwMaxPayloadTransferSize
UVC的视频流接口控制请求的数据大小可为26字节,34字节和48字节,其分别对应的是UVC的1.0,1,1和UVC1.5版本。
在其整个视频流控制接口参数偏移地址22处的字段为一4字节的dwMaxPayloadTransferSize,根据其字段解释为“指定设备在单个有效负载传输中可以传输或接收的最大字节数。必须支持此字段。此字段由设备设置,仅从主机读取。某些主机实现限制此字段允许的最大值。主机应通过重新配置设备来避免单个有效负载传输大小的超调。(例如,通过更新比特率、分辨率等)”关于这个字段,经常有人理解不清楚,这里我们专门来解释一下。
LINUX中UVC驱动关于它的计算方法如下:
interval = (ctrl->dwFrameInterval > 100000) ? ctrl->dwFrameInterval:frame->dwFrameInterval[0];
/* Compute a bandwidth estimation by multiplying the frame
* size by the number of video frames per second, divide the
* result by the number of USB frames (or micro-frames for
* high-speed devices) per second and add the UVC header size
* (assumed to be 12 bytes long).
*/
//一帧大小
bandwidth = frame->wWidth * frame->wHeight / 8 * format->bpp;
//1秒大小
bandwidth *= 10000000 / interval + 1;
//1ms大小
bandwidth /= 1000;
//一个微帧大小
if (stream->dev->udev->speed == USB_SPEED_HIGH)
bandwidth /= 8;
//加上负载头
bandwidth += 12;
/* The bandwidth estimate is too low for many cameras. Don't use
* maximum packet sizes lower than 1024 bytes to try and work
* around the problem. According to measurements done on two
* different camera models, the value is high enough to get most
* resolutions working while not preventing two simultaneous
* VGA streams at 15 fps.
*/
//该值最小是1024字节
//所以这是计算一帧或者一个微帧最大传输的大小?
bandwidth = max_t(u32, bandwidth, 1024);
ctrl->dwMaxPayloadTransferSize = bandwidth;
USB端点的大小
通过UVC规范的学习可以知道,UVC在视频数据的传输只支持批量传输和等时/同步传输。对于等时传输,假如我们使用的是USB2.0高速,那么根据USB规范可知,其等时传输的端点数据包大小最大为1024字节(具体可参见USB规范端点描述符一节),但是为了提高数据的传输效率,在USB等时传输支持额外的传输次数,这里额外的传输次数最大为2。故在USB2.0高速的传输过程中,一个同步/等时传输端点的一次可最大传输字节数为1024*(2+1)=3072=0x0c00的字节数。所以这里的这个字节数就是dwMaxPayloadTransferSize即单个有效负载传输中可以传输或接收的最大字节数。
USB的等时传输
由于是同步/等时传输,为了提高数据的传输效率,一个URB可以含有多个单次有效负载传输传输,这里我们记为N。这样在一个URB的数据传输过程中,最大可以传输N*有效负载传输中可以传输,这个计算出来的数据就是一个抓包的数据大小。
抓包是按一个URB进行抓包的。
Linux源代码分析
在Linux的源代码uvc_video_start_transfer函数(linux-5.6.11\linux-5.6.11\drivers\media\usb\uvc\uvc_video.c:1810)中,在初始化时的代码如下:
if (intf->num_altsetting > 1) {
struct usb_host_endpoint *best_ep = NULL;
unsigned int best_psize = UINT_MAX;
unsigned int bandwidth;
unsigned int uninitialized_var(altsetting);
int intfnum = stream->intfnum;
/* Isochronous endpoint, select the alternate setting. */
bandwidth = stream->ctrl.dwMaxPayloadTransferSize;
if (bandwidth == 0) {
uvc_trace(UVC_TRACE_VIDEO, "Device requested null "
"bandwidth, defaulting to lowest.\n");
bandwidth = 1;
} else {
uvc_trace(UVC_TRACE_VIDEO, "Device requested %u "
"B/frame bandwidth.\n", bandwidth);
}
for (i = 0; i < intf->num_altsetting; ++i) {
struct usb_host_interface *alts;
unsigned int psize;
alts = &intf->altsetting[i];
ep = uvc_find_endpoint(alts,
stream->header.bEndpointAddress);
if (ep == NULL)
continue;
/* Check if the bandwidth is high enough. */
psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
if (psize >= bandwidth && psize <= best_psize) {
altsetting = alts->desc.bAlternateSetting;
best_psize = psize;
best_ep = ep;
}
}
if (best_ep == NULL) {
uvc_trace(UVC_TRACE_VIDEO, "No fast enough alt setting "
"for requested bandwidth.\n");
return -EIO;
}
uvc_trace(UVC_TRACE_VIDEO, "Selecting alternate setting %u "
"(%u B/frame bandwidth).\n", altsetting, best_psize);
ret = usb_set_interface(stream->dev->udev, intfnum, altsetting);
if (ret < 0)
return ret;
ret = uvc_init_video_isoc(stream, best_ep, gfp_flags);
}
在其源代码中首先将dwMaxPayloadTransferSize赋值给bandwidth,在各个视频流转换接口中的同步/等时端点中查找一个带宽大于等于其bandwidth的端点,然后选择其对应的接口。
计算其端点带宽的函数uvc_endpoint_max_bpi代码如下:
static unsigned int uvc_endpoint_max_bpi(struct usb_device *dev,
struct usb_host_endpoint *ep)
{
u16 psize;
u16 mult;
switch (dev->speed) {
case USB_SPEED_SUPER:
case USB_SPEED_SUPER_PLUS:
return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
case USB_SPEED_HIGH:
psize = usb_endpoint_maxp(&ep->desc);
mult = usb_endpoint_maxp_mult(&ep->desc);
return psize * mult;
case USB_SPEED_WIRELESS:
psize = usb_endpoint_maxp(&ep->desc);
return psize;
default:
psize = usb_endpoint_maxp(&ep->desc);
return psize;
}
}
可以看到,对于高带设备USB_SPEED_HIGH,获取其端点的大小和mult(额外传输的次数+1),最后的乘积就是最大带宽即单次传输大小。
usb_endpoint_maxp_mult(const struct usb_endpoint_descriptor *epd)
{
int maxp = __le16_to_cpu(epd->wMaxPacketSize);
return USB_EP_MAXP_MULT(maxp) + 1;
}