项目需求:
应用端能直接获取h264格式的4k图像数据,但是谷歌没有提供获取h264格式图像的接口,于是只能使用uvcCamera私有库来解决该问题。
项目难点:
前提:根据uvc协议,当相机的interface→num_altsetting > 1时,会走同步传输来传输数据,速率为24MB/s;当interface→num_altsetting = 1时,会走bulk传输来传送视频数据,速率为48MB/s。因为是传输4k压缩数据,iso传输可能带宽不够,所以所使用的相机选择了bulk传输urb数据。
当使用uvcCamera私有库的时候,出现了如下报错:
进入代码找到uvc_stream_start_bandwidth(位于stream.c中)位置,如上所述,在该函数中,会先判断interface→num_altsetting值,然后根据该值使用bulk进行传输。
先封装需要提交的transfer:
for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; ++transfer_id) {
transfer = libusb_alloc_transfer(0);
strmh->transfers[transfer_id] = transfer;
strmh->transfer_bufs[transfer_id] = malloc(strmh->cur_ctrl.dwMaxPayloadTransferSize);
libusb_fill_bulk_transfer(transfer, strmh->devh->usb_devh,
format_desc->parent->bEndpointAddress,
strmh->transfer_bufs[transfer_id],
strmh->cur_ctrl.dwMaxPayloadTransferSize, _uvc_stream_callback,
(void *)strmh, 5000);
}
提交刚才封装好的transfer:
MARK("submit transfers");
for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; transfer_id++) {
ret = libusb_submit_transfer(strmh->transfers[transfer_id]);
if (UNLIKELY(ret != UVC_SUCCESS)) {
UVC_DEBUG("libusb_submit_transfer failed");
__android_log_print(ANDROID_LOG_ERROR,LOG_TAG, "yilan uvc_stream_start_bandwidth libusb_submit_transfer failed errno = %d, %s", errno, strerror(errno));
break;
}
}
这里的返回值是-1,也就是LIBUSB_ERROR_IO错误。
来看看libusb_submit_transfer的具体实现:
函数的核心内容在红色方框内,因此追进去看下,由于电视是android设备,因此我们一路跟到android设备下的submit_transfer方法中:
找到返回LIBUSB_ERROR_IO的地方:
显然,这里是通过ioctl的方式与linux内核进行通信,因此我们再进去内核中继续追踪。由于ioctl的命令是IOCTL_USBFS_SUBMITURB,我们直接在内核代码的根目录下搜寻相应的字符,发现没找到。因为ioctl命令可能在内核中进行了相应的重定义,但是关键字符应该是相同的,于是就在搜索SUBMITURB时搜到了关键线索:
然后就是追着submit关键字继续查找,发现带有submit关键字的函数最终都会执行proc_do_submiturb函数,于是我在该函数的头部加了log,而该log也确实打印了,因此可以确定是这个函数里爆出问题,报错函数为usbfs_increase_memory_usage:
可以看到内核这边为这些transfer提供的最大内存为16<<20,也就是16,777,216字节,camera那边申请的payload大小为固定值width*height*1.5,当申请4k数据时,由于会超过最大值4m,因此payload的大小被固定成4m(4194304字节),也就是一个tranfer对应的buffer大小,对应内核的大小为4202784(也就是amount的大小,从上述截图可以看到amount的计算方式)。接下来,我们再回过头看看进行libusb_submit_transfer地方:
其中LIBUVC_NUM_TRANSFER_BUFS定义为10。因此需要内核这边需要分配(10*4202784)字节的空间,对应usbfs_memory_mb的值为:(10*4202784)>> 20 = 40。我以为修改成40以后就万事大吉,可是usbfs_memory_mb设置到33的时候,电话会自动重启。于是我将LIBUVC_NUM_TRANSFER_BUFS的值改成了6(这里主要怕改太小影响libusb传输,中立值选择了6),usbfs_memory_mb的值改到24-32之间,就可以满足要求。
ps:这里需要吐槽一下,因为uvc需要先分配内存才能开流,因此我们不能根据每次提交payload的大小来分配相应的内存。只能先开辟一块内存来存储相应的payload。mjpg的压缩比差不多为0.3,h264的压缩比相较mjpg更高,因此分配h*w*0.3足以,如果需要带上深度信息,可以额外再加上点深度信息对应的内存,也不至于分配w*h*1.5这么大的内存。通过抓取一千张图片,我发现基本上0.3的压缩比已经足够满足。