Linux下打开多个UVC 摄像头报错问题(No space left on device)
报错log:
uvcvideo: Failed to submit URB 0 (-28).
这句话通常是应用层开始取流时,发生的错误。即在执行ioctl(fd, VIDIOC_STREAMON, &type);
时报错的。
分析问题
下图的UVC设备传输方式都是同步传输,并且两个UVC设备的端点描述的带宽占用量都是wMaxPacketSize=0x1400, bInterval=1
批量传输估计是占用实际带宽,选择mjpeg格式就行,问题不大。参考是不是同步传输,需要将UVC设备接入电脑,看下它的设备描述符。
下面是我使用是的场景:两个uvc设备通过Hub连接到linux主机:
当linux主机接入两个uvc设备时,能查询到两个uvc设备,并且单独打开其中一个是可以的,但是同时打开它们就不行,并且报错,错误代码为28。这是因为linux主机的usb带宽大部分都分配给了第一个uvc设备,导致打开第二个uvc设备时,剩余的usb带宽不足以启用这个流,所以,打开第二个uvc设备时失败。
所以,这个主要是由带宽分配问题引起的报错。关于usb带宽分配原则,一般是usb设备在它的设备描述符的端点描述符中描述了该端点正常运行时需要的带宽量,主机根据这个值,在跟该端点通信前,分配出相应的带宽量,如果剩余带宽量不足,则就会报错-28。因此,要想解决这个问题,主要还是要修改uvc设备的端点描述符中,它声明需要占用的带宽配置。
将上图的linux主机(我用的是嵌入式arm芯片)改为windows主机(intel i5-9400)下,效果也是一样的。但是,不用Hub,直接一个uvc设备接一个windows usb口的话,能同时取流。合理的解释是:假设UVC设备1声明它需要24M/s(同步传输方式单个端点最大吞吐量)才能传流,一般usb 2.0实际最大读写速度为48m/s,则hub的剩余带宽就只有一半不到了,就传不了UVC设备2的流了,除非UVC设备2声明带宽占用量低于24M/s,例如16M/s,这样即使两个设备接入同一个hub,再到电脑上,也是可以运行的。如果直接将UVC设备接入主机口,主机口的控制器直接处理数据,也就没有平分带宽的说法。
解决问题
方法一:将UVC设备直接接到linux主机的usb口,中间不能使用hub
这种方法需要linux主机本身就有多个usb口。
方法二:修改UVC设备的带宽分配值
这种方法需要修改UVC设备的描述符及其功能,有一定的局限性。
分析:
这里假设两个uvc设备的传输方式都是同步传输。
找到uvc设备的流描述符所使用的端点描述符,主要看wMaxPacketSize
参数和bInterval
参数。它们的含义具体可以参考usb规范的端点描述符说明。
- wMaxPacketSize 表示每次传输时用的包大小。最大值为1024字节加2次突发次数,即最大每次传3072字节。
- bInterval 表示多久通信一次。最快值为1,表示125us传输一次,最慢值为16,表示2^(15)*125 us传输一次
下图显示了不同的bInterval配置在高速同步传输中的数据传输频率,第四种情况代表端点运行需要占用usb同步方式满带宽。图片引用至USB中文网。
下图为上面第四种情况时的端点描述符示例。
所以,将UVC设备的wMaxPacketSize的值减少,或者将bInterval的值减少,都能解决这个问题,但是,将它们的值降低后,它们所能传的流的吞吐量也将降低。比如说wMaxPacketSize=0x1400(3072字节/次),bInterval=1,则每秒能传24MB/s,但是wMaxPacketSize=0x0C00(2048字节/次),bInterval=1就只能传16MB/s,以此类推。
嵌入式cpu和电脑cpu的性能和驱动不同,所能处理的吞吐量也不同。例如:电脑cpu支持的端点带宽组合最大值大约为 3072+2048。嵌入式cpu支持的端点带宽组合最大值大约为 2048+1024。