usbcamera android 4k支持

usbcamera

android 外接 usbcamera,已经有比较成熟的技术方案UVCCamera,参考这个库,配置Andoird工程,依赖库接入,把外接usbcamera 流程跑通不难。

手头设备是海康威视的4K摄像头,run起立 camera画面也正常出来了。 如果都这么顺利的话,那就没这篇文章了 😂😂😂

4k

画面是出来了,发现分辨率最多只拉到2k(2560 x 1440),完全没有发挥出4k的能力。 比较容易想到的是 线 或者 hub 用的是usb2.0的(480Mbit/s),传输速度不够,所以出不了4K。
搞一根3.0的线 确定口子也是3.0的之后,再次跑起来,发现4k画面花了,只有顶上一点点内容,绝大部分是灰的,懵逼开始…

libXXX

懵逼没用 还是要解决问题的

简单梳理之后,问题应该不是android侧的,并没有什么java层的异常日志。 也是因为到目前为止,我们的工作也只是在android侧,各种lib 接入之后,目前不知道具体在做什么。

开始了解底层 native jni。

jni 进来对接的就是libuvc,libuvc 底层就是libusb

这两座大山全是C写的,懵逼升级…

闪烁

c/c++ 还给老师了, 但是 ctrl+c/v 那是不可能还的

很快发现 UVCCamera 都已经停留在5-6年前了,赶紧ctrl+c/v 把两座大山升级下,报错处理下、再次run起来。

出来了4k出来了,高兴不到3s,画面是出来了,新的问题出现了 画面闪烁,不是一直闪烁 偶尔闪那么几下。 几番来回实验下来,不单4k闪烁,连升级之前正常的2k也开始闪了。 懵逼继续…

闪烁的一帧画面如下:

fuck

log

硬着头皮看代码了,大概了解了调用流程之后(uvc usb的调用流程,官方的readme 跟 demo 也提到了),开始把一些日志开关打开,自己加一些log,mjpeg格式数据保存本地看看,把一些涉及到什么buffer_size 、timeout、payload 、lens等 可以调整参数的地方 改大 或者 改小。 一顿瞎操作之后,基本都是没用的。

不过倒是发现了一个日志,似乎跟闪烁有关系 “unrecognised urb status -1”,因为这个日志也是时不时的出现。

官方还真有这个issue

上面并没有说闪烁,也有想过是不是手上的海康4K摄像头有问题,驱动或者固件有问题。。。手上又没有其他的4k摄像头来测试。。。

但是说到 有没有在linux系统上试过,毕竟android不是真linux….

正好团队有一台ubuntu ,那就在linux上试试呗, 照着readme 把libuvc的example demo 跑起来,调整参数保持跟android一直,保存mjpeg数据到本地。 本地几百张图像没有一张是闪烁画面。。。 海康不背锅。。。

example demo 稍微做了写改造(配置makefile、opencv接入),把 libusb debug log开关打开了,使用 opencv 把画面显示出来了,参考下 demo

issue 里咨询下, 只说是android 内核问题,跟libusb没关系…. 懵逼升级….

uvc/usb

大概了解调用过程是不够的,那就继续了解。。。开始google大法。。。

usb2.0
usb3.0
uvc

无他,唯手熟尔

uvc usb的调用流程,官方的readme 跟 demo 都提到了 。

对着代码、协议、文档, 熟悉 uvc、usb、descriptor、transfer、urb 、ioctl 等等

慢慢开始熟悉 每个函数都在干嘛了 各个参数的意义了

知道 “unrecognised urb status -1”、”bad packet:xxx”、”submiturb failed, errno=-12” 这些日志的出处了,也就大概把范围缩小到几个关键的函数了

happy

在针对这几个函数 ,对比下libusb、libuvc 新老代码的代码实现,很快就发现了几处核心不同。

在新版本的libuvc (0.0.7)中, 有针对usb3.1 superspeed 端口的处理逻辑,老版本没有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//steam.c  

uvc_error_t uvc_stream_start(
uvc_stream_handle_t *strmh,
uvc_frame_callback_t *cb,
void *user_ptr,
uint8_t flags
) {

...

struct libusb_ss_endpoint_companion_descriptor *ep_comp = 0;
libusb_get_ss_endpoint_companion_descriptor(NULL, endpoint, &ep_comp);
if (ep_comp)
{
UVC_DEBUG("packets_per_transfer = %d,config_bytes_per_packet=%d",ep_comp->wBytesPerInterval,config_bytes_per_packet);
endpoint_bytes_per_packet = ep_comp->wBytesPerInterval;
libusb_free_ss_endpoint_companion_descriptor(ep_comp);
break;
}
...

}

新版本的libusb (1.0.26), 单个urb packet len更大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//linux_usbfs.c

static int submit_iso_transfer(struct usbi_transfer *itransfer)
{
...

for (i = 0; i < num_packets; i++) {
packet_len = transfer->iso_packet_desc[i].length;

if (packet_len > max_iso_packet_len) {
usbi_warn(TRANSFER_CTX(transfer),
"iso packet length of %u bytes exceeds maximum of %u bytes",
packet_len, max_iso_packet_len);
return LIBUSB_ERROR_INVALID_PARAM;
}

total_len += packet_len;
}

...
}

static int op_init(struct libusb_context *ctx)
{
...

if (!max_iso_packet_len) {
if (kernel_version_ge(&kversion, 5, 2, 0))
max_iso_packet_len = 98304;
else if (kernel_version_ge(&kversion, 3, 10, 0))
max_iso_packet_len = 49152;
else
max_iso_packet_len = 8192;
}

usbi_dbg("max iso packet length is (likely) %u bytes", max_iso_packet_len);

...
}

老版本的libusb中 ,比新版本有更多的urb请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//linux_usbfs.c

#define MAX_ISO_BUFFER_LENGTH 32768 //32k
...
static int submit_iso_transfer(struct usbi_transfer *itransfer)
{
...

/* calculate how many URBs we need */
for (i = 0; i < num_packets; i++) {
unsigned int space_remaining = MAX_ISO_BUFFER_LENGTH - this_urb_len;
packet_len = transfer->iso_packet_desc[i].length;

if (packet_len > space_remaining) {
num_urbs++;
this_urb_len = packet_len;
} else {
this_urb_len += packet_len;
}
}
usbi_err(TRANSFER_CTX(transfer),"need %d of 32k URBs for transfer", num_urbs);

...
}

首先老的libuvc 没有针对 ss 端口做处理,肯定是有问题的 ,也就解释了懵逼的开始 为什么4k画面花了,只有顶上一点点内容,绝大部分是灰的,因为 usb 2.0算出来 传输 urb少了,只拿到前面的urb数据,也就是顶部的画面。

新版本的libusb中,urb size 比较大,4k参数 算下来只需要一个urb ,也就是出现 “bad packet:xxx”、”submiturb failed, errno=-12”这些异常日志的原因。为什么linux上正常,而andoird有问题,猜测 手机的内存、带宽等等跟pc还是没法比的。。

到这里能想到的可尝试方案 基本就出来了

  1. 老的uvc 增加对ss 的处理逻辑
  2. 新的usb 减少urb packet size,出更多的urb

最终 两种方案 尝试下 都可以解决问题。。

happy …

other

针对这两点 官方代码的提交历史

1
2
3
4
5
libuvc 

Updating LibUVC; Adds support SS USB, Modify to support MSFT UVC metadata, Fix hang during stop, Add NV12 support, Remove debug message in release build

86ad9640 Wes Barcalow <[email protected]> on 2019/5/10 at 07:34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
libusb

linux_usbfs: Improve isochronous transfer submission and error reporting

The Linux kernel has changed the maximum allowed packet length per
isochronous packet numerous times, which can create difficulties in
trying to report appropriate errors back to the user when submitting too
large of a packet on older kernels.

In an attempt to improve this situation, this commit adds logic that
will use different per-packet limits based on the detected kernel
version. Additionally, the logic has been improved to split URBs based
on the number of isochronous packets per URB, which is currently (and
has been forever) limited to 128.

Finally, the error reporting during URB submission has been improved to
catch and report errors relating to the transfer length being too large.


8aad7fd1 Chris Dickens <[email protected]> on 2017/12/27 at 11:15