libuvc不支持IAD枚举的组合多设备,只支持多个独立设备。所以本文分享将libuvc改造为支持IAD枚举的组合多设备。
备注:复合设备其实就是几个物理设备通过一个USB Hub形成的单一设备;
组合设备是通过IAD实现,也就是具有多个接口的设备,不同接口代表不同功能设备。
代码见:https://github.com/qinyunti/uvc_demo.git
我们前面看到libuvc的操作流程如下
1.先调用uvc_init进行初始化
uvc_error_t uvc_init(uvc_context_t **pctx, struct libusb_context *usb_ctx)
其中参数pctx需要用户传入一个uvc_context_t *的地址,所以是指向指针的指针
即
uvc_context_t *ctx;
uvc_init(&ctx, NULL);
uvc_init时会动态分配一个uvc_context_t *实例,赋值给ctx。
其中参数usb_ctx表示libusb的上下文环境
若usb_ctx为NULL则先初始化libusb环境
libusb_init(&ctx->usb_ctx);
否则直接使用传进来的libusb
ctx->usb_ctx = usb_ctx;
ctx->own_usb_ctx为0表示使用传进来的上下文环境,为1表示这里libuvc重新初始化了一个libusb环境。
最终的结果就是uvc_init初始化了一个libuvc的环境即ctx变量。
2.然后调用uvc_find_device查找设备
uvc_error_t uvc_find_device(
uvc_context_t *ctx, uvc_device_t **dev,
int vid, int pid, const char *sn)
查找到设备赋值给 uvc_device_t *dev;
res = uvc_find_device(
ctx, &dev,
0x1993, 0x0101, NULL);
uvc_find_devices则可以查找多个设备
uvc_error_t uvc_find_devices(
uvc_context_t *ctx, uvc_device_t ***devs,
int vid, int pid, const char *sn)
由于IAD多设备是需要系统的组合设备驱动实现的,使用libusb实际就是没有使用系统的驱动了,libusb此时看到的就是只有一个设备了,即操作裸的接口,端点等。
即此时dev对应的就是一个IAD多设备,而每一个IAD需要自行去处理。
3.调用uvc_open打开设备
uvc_error_t uvc_open(
uvc_device_t *dev,
uvc_device_handle_t **devh)
res = uvc_open(dev, &devh);
打开赋值给
uvc_device_handle_t *devh;
通过代码可以看到uvc_open实际就是调用
uvc_open_internal
创建实例uvc_device_handle_t
然后调用uvc_get_device_info获取控制接口,和流接口等信息,填充uvc_device_handle_t实例,
最终将该实例返回给devh参数。
这里可以看到
uvc_get_device_info实际是调用的uvc_scan_control的扫描接口
匹配控制接口类和子类参数,匹配了就记录该接口号
if_desc->bInterfaceClass == 14 && if_desc->bInterfaceSubClass == 1
这里代码可以看到找到一个就结束了。
即一个uvc_device_t设备只对应一个uvc设备uvc_device_handle_t
实际上对于IAD多UVC设备,逻辑上应该是
一个uvc_device_t设备只对应多个uvc设备uvc_device_handle_t
所以自然的可以想到我们可以增加一个函数
uvc_error_t uvc_opens(
uvc_device_t *dev,
uvc_device_handle_t ***devh, int* n)
即根据uvc_device_t设备,打开n个uvc设备 uvc_device_handle_t
自然也会想到将改为uvc_open_internal
static uvc_error_t uvc_open_internals(
uvc_device_t *dev,
struct libusb_device_handle *usb_devh,
uvc_device_handle_t ***devh, int* n)
自然还会想到将uvc_get_device_info改为
uvc_error_t uvc_get_device_info(uvc_device_handle_t *devh,
uvc_device_info_t **info, int* if_idx)
即默认从接口0开始搜寻,改为从指定接口处开始搜寻,这样可以搜寻完所有的接口而不是只搜寻到一个就算完成。
对应的uvc_scan_control改为
uvc_scan_control(uvc_device_handle_t *devh, uvc_device_info_t *info, int* if_idx)
if_idx即表示当前从那个接口开始搜寻,返回搜寻到的接口,这样下一次就可以从if_idx+1处继续搜索,则对应下一个 uvc_device_handle_t设备。
以上即可实现找到n个 uvc_device_handle_t设备
4.然后获取流uvc_get_stream_ctrl_format_size
uvc_error_t uvc_get_stream_ctrl_format_size(
uvc_device_handle_t *devh,
uvc_stream_ctrl_t *ctrl,
enum uvc_frame_format cf,
int width, int height,
int fps)
res = uvc_get_stream_ctrl_format_size(
devh, &ctrl, UVC_FRAME_FORMAT_MJPEG, 1280, 720, 0
);
赋值给 uvc_stream_ctrl_t ctrl;
这样一个uvc设备 uvc_device_handle_t设备再对应一个流
uvc_stream_ctrl_t,
上面提供了多个uvc设备,这里自然就对应多个流,无需修改即可支持多设备。
详见源码,具体修改如下
Libuvc.h中增加接口
uvc_error_t uvc_opens(
uvc_device_t *dev,
uvc_device_handle_t ***devh, int* n);
Device.c中
以下函数声明增加了参数if_idx
uvc_error_t uvc_get_device_info(uvc_device_handle_t *devh, uvc_device_info_t **info, int* if_idx);
void uvc_free_device_info(uvc_device_info_t *info);
uvc_error_t uvc_scan_control(uvc_device_handle_t *devh, uvc_device_info_t *info, int* if_idx);
增加接口
static uvc_error_t uvc_open_internals(uvc_device_t *dev, struct libusb_device_handle *usb_devh, uvc_device_handle_t ***devh, int* n);
增加uvc_opens实现
/** @brief Open a UVC device
* @ingroup device
*
* @param dev Device to open
* @param[out] devh Handle List on opened device
* @return Error opening device or SUCCESS
*/
uvc_error_t uvc_opens(
uvc_device_t *dev,
uvc_device_handle_t ***devh, int* n) {
uvc_error_t ret;
struct libusb_device_handle *usb_devh;
UVC_ENTER();
ret = libusb_open(dev->usb_dev, &usb_devh);
UVC_DEBUG("libusb_open() = %d", ret);
if (ret != UVC_SUCCESS) {
UVC_EXIT(ret);
return ret;
}
ret = uvc_open_internals(dev, usb_devh, devh, n);
UVC_EXIT(ret);
return ret;
}
uvc_open_internal修改
增加uvc_open_internals实现
static uvc_error_t uvc_open_internals(
uvc_device_t *dev,
struct libusb_device_handle *usb_devh,
uvc_device_handle_t ***devh, int* n) {
uvc_error_t ret;
uvc_device_handle_t *internal_devh;
struct libusb_device_descriptor desc;
int if_idx = 0;
int dev_num = 0;
UVC_ENTER();
uvc_ref_device(dev);
while(1)
{
internal_devh = calloc(1, sizeof(*internal_devh));
internal_devh->dev = dev;
internal_devh->usb_devh = usb_devh;
ret = uvc_get_device_info(internal_devh, &(internal_devh->info), &if_idx);
if_idx++;
if (ret != UVC_SUCCESS){
if(dev_num == 0){
goto fail;
}else{
goto done;
}
}
UVC_DEBUG("claiming control interface %d", internal_devh->info->ctrl_if.bInterfaceNumber);
ret = uvc_claim_if(internal_devh, internal_devh->info->ctrl_if.bInterfaceNumber);
if (ret != UVC_SUCCESS){
if(dev_num == 0){
goto fail;
}else{
goto done;
}
}
libusb_get_device_descriptor(dev->usb_dev, &desc);
internal_devh->is_isight = (desc.idVendor == 0x05ac && desc.idProduct == 0x8501);
if (internal_devh->info->ctrl_if.bEndpointAddress) {
internal_devh->status_xfer = libusb_alloc_transfer(0);
if (!internal_devh->status_xfer) {
ret = UVC_ERROR_NO_MEM;
if(dev_num == 0){
goto fail;
}else{
goto done;
}
}
libusb_fill_interrupt_transfer(internal_devh->status_xfer,
usb_devh,
internal_devh->info->ctrl_if.bEndpointAddress,
internal_devh->status_buf,
sizeof(internal_devh->status_buf),
_uvc_status_callback,
internal_devh,
0);
ret = libusb_submit_transfer(internal_devh->status_xfer);
UVC_DEBUG("libusb_submit_transfer() = %d", ret);
if (ret) {
fprintf(stderr,
"uvc: device has a status interrupt endpoint, but unable to read from it\n");
if(dev_num == 0){
goto fail;
}else{
goto done;
}
}
}
if (dev->ctx->own_usb_ctx && dev->ctx->open_devices == NULL) {
/* Since this is our first device, we need to spawn the event handler thread */
uvc_start_handler_thread(dev->ctx);
}
DL_APPEND(dev->ctx->open_devices, internal_devh);
(*devh)[dev_num] = internal_devh;
dev_num++;
if(dev_num >= *n){
goto done;
}
}
done:
*n = dev_num;
UVC_EXIT(ret);
return ret;
fail:
if ( internal_devh->info ) {
uvc_release_if(internal_devh, internal_devh->info->ctrl_if.bInterfaceNumber);
}
libusb_close(usb_devh);
uvc_unref_device(dev);
uvc_free_devh(internal_devh);
UVC_EXIT(ret);
return ret;
}
修改uvc_get_device_info
修改uvc_scan_control
代码见
test_iad.c
static void cb(uvc_frame_t *frame, void *ptr) {
uvc_frame_t *bgr;
uvc_error_t ret;
printf("callback! length = %zu, ptr = %p\n", frame->data_bytes, ptr);
bgr = uvc_allocate_frame(frame->width * frame->height * 3);
if (!bgr) {
printf("unable to allocate bgr frame!");
return;
}
/* 格式解析 */
ret = uvc_any2bgr(frame, bgr);
if (ret) {
uvc_perror(ret, "uvc_any2bgr");
uvc_free_frame(bgr);
return;
}
// 显示等操作
uvc_free_frame(bgr);
}
int test_iad_main(int argc, char **argv) {
(void)argc;
(void)argv;
uvc_context_t *ctx;
uvc_error_t res;
uvc_device_t *dev;
uvc_device_handle_t *devh[2]={NULL,NULL};
uvc_device_handle_t **pdevh = devh;
uvc_stream_ctrl_t ctrl[2];
int dev_num = sizeof(devh)/sizeof(devh[0]);
res = uvc_init(&ctx, NULL);
if (res < 0) {
uvc_perror(res, "uvc_init");
return res;
}
printf("UVC initialized\r\n");
res = uvc_find_device(
ctx, &dev,
0x1993, 0x0101, NULL);
if (res < 0) {
uvc_perror(res, "uvc_find_device");
} else {
printf("Device found\r\n");
res = uvc_opens(dev, &pdevh, &dev_num);
if (res < 0) {
uvc_perror(res, "uvc_open");
} else {
printf("Device opened %d\r\n",dev_num);
for(int i=0; i
{
uvc_print_diag(devh[i], stderr);
res = uvc_get_stream_ctrl_format_size(
devh[i], &ctrl[i], UVC_FRAME_FORMAT_MJPEG, 1280, 720, 0
);
uvc_print_stream_ctrl(&ctrl[i], stderr);
if (res < 0) {
uvc_perror(res, "get_mode");
} else {
res = uvc_start_streaming(devh[i], &ctrl[i], cb, (void*)i, 0);
if (res < 0) {
uvc_perror(res, "start_streaming");
} else {
printf("Streaming for 10 seconds...");
}
}
}
Sleep(10000);
for(int i=0; i
{
uvc_stop_streaming(devh[i]);
printf("Done streaming %d.\r\n",i);
}
for(int i=0; i
{
uvc_close(devh[i]);
printf("Device %d closed\r\n",i);
}
}
uvc_unref_device(dev);
}
uvc_exit(ctx);
printf("UVC exited");
return 0;
}
这里测试的一个IAD双UVC设备,打印如下
UVC initialized
Device found
Device opened 2
DEVICE CONFIGURATION (1993:0101/[none]) ---
Status: idle
VideoControl:
bcdUVC: 0x0110
bInterfaceNumber: 0x0000
VideoStreaming(1):
bEndpointAddress: 129
Formats:
UncompressedFormat(1)
bits per pixel: 12
GUID: 4e56313200001000800000aa00389b71 (NV12)
default frame: 1
aspect ratio: 0x0
interlace flags: 00
copy protect: 00
FrameDescriptor(1)
capabilities: 02
size: 1280x720
bit rate: 24576000-24576000
max frame size: 460800
default interval: 1/25
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
FrameFormat(2)
bits per pixel: 16
GUID: 4832363400001000800000aa00389b71 (H264)
default frame: 1
aspect ratio: 0x0
interlace flags: 00
copy protect: 00
FrameDescriptor(1)
capabilities: 00
size: 1280x720
bit rate: 24576000-24576000
max frame size: 0
default interval: 1/25
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
MJPEGFormat(3)
bits per pixel: 0
GUID: 4d4a5047000000000000000000000000 (MJPG)
default frame: 1
aspect ratio: 0x0
interlace flags: 00
copy protect: 00
FrameDescriptor(1)
capabilities: 02
size: 1280x720
bit rate: 24576000-24576000
max frame size: 460800
default interval: 1/25
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
END DEVICE CONFIGURATION
bmHint: 0001
bFormatIndex: 3
bFrameIndex: 1
dwFrameInterval: 400000
wKeyFrameRate: 0
wPFrameRate: 0
wCompQuality: 0
wCompWindowSize: 0
wDelay: 0
dwMaxVideoFrameSize: 460800
dwMaxPayloadTransferSize: 1024
bInterfaceNumber: 1
Streaming for 10 seconds...DEVICE CONFIGURATION (1993:0101/[none]) ---
Status: idle
VideoControl:
bcdUVC: 0x0110
bInterfaceNumber: 0x0002
VideoStreaming(1):
bEndpointAddress: 130
Formats:
UncompressedFormat(1)
bits per pixel: 12
GUID: 4e56313200001000800000aa00389b71 (NV12)
default frame: 1
aspect ratio: 0x0
interlace flacgasl:l b0a0c
k ! l ecnogptyh p=r o1t8e5c2t5:, 0p0t
r = F0r0a0m0e0D0e0s0c0r0i0p0t0o0r0(01
)
capabilities: 02
size: 1280x720
bit rate: 24576000-24576000
max frame size: 460800
default interval: 1/25
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
FrameFormat(2)
bits per pixel: 16
GUID: 483236uvc_any2bgr: Not supported (-12)
3c4a0l0l0b0a1c0k0!0 8l0e0n0g0t0ha a=0 02338098b47,1 p(tHr2 6=4 )0
0 0 0 0 0de0f0a0u0l0t0 0f0r0a0m
e: 1
aspect ratio: 0x0
interlace flags: 00
copy protect: 00
FrameDescriptor(1)
capabilities: 00
size: 1280x720
bit rate: 24576000-24576000
max frame size: 0
default interval: 1/25
uvc_any2bgr: Not supported (-12)
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
MJPEGFormat(3)
bits per pixel: 0
GUID: 4d4a5047000000000000000000000000 (MJPG)
default frame: 1
aspect ratio: 0x0
interlace flagcsa:l l0b0a
c k ! lceonpgyt hp r=o t2e2c8t1:9 0,0
p t r F=r a0m0e0D0e0s0c0r0i0p0t0o0r0(010)0
capabilities: 02
size: 1280x720
bit rate: 24576000-24576000
max frame size: 460800
default interval: 1/25
interval[0]: 1/40
interval[1]: 1/25
interval[2]: 1/10
END DEVICE CONFIGURATION
uvc_any2bgr: Not supported (-12)
bmHint: 0001
bFormatIndex: 3
bFrameIndex: 1
dwFrameInterval: 400000
wKeyFrameRate: 0
wPFrameRate: 0
wCompQuality: 0
wCompWindowSize: 0
wDelay: 0
dwMaxVideoFrameSize: 460800
dwMaxPayloadTransferSize: 1024
bInterfaceNumber: 3
callback! length = 22746, ptr = 0000000000000000
Suvc_any2bgrt:r eNaomti nsgu pfpoorr t1e0d s(e-c1o2n)d
s...callback! length = 21833, ptr = 0000000000000001
uvc_any2bgr: Not supported (-12)
callback! length = 23077, ptr = 0000000000000000
uvc_any2bgr: Not supported (-12)
callback! length = 22567, ptr = 0000000000000001
uvc_any2bgr: Not supported (-12)
callback! length = 22613, ptr = 0000000000000000
uvc_any2bgr: Not supported (-12)
callback! length = 22054, ptr = 0000000000000001
uvc_any2bgr: Not supported (-12)
callback! length = 23084, ptr = 0000000000000000
uvc_any2bgr: Not supported (-12)
callback! length = 22819, ptr
以上分析了uvc的程序流,以简单的方式修改即支持IAD多UVC设备。这里使用增量方式增加uvc_opens接口,这样不影响以前的接口,并且其他依赖接口的修改也修改时保持兼容。