前面分享了libuvc的工程模板,以及将其修改为支持IAD的组合多UVC设备。为了方便开发我们再详细分享下libuvc的相关内容,包括文件,数据流等。
libuvc项目代码见:https://github.com/libuvc/libuvc.git
libuvc项目文档见:https://libuvc.github.io/libuvc/
前面我们的demo可以看到,libuvc需要以下文件添加到自己的项目中
libuvc/src下
ctrl-gen.c调用libusb_control_transfer控制传输,实现各种类的请求,比如设置曝光时间等
ctrl-gen.py
可以执行python .\ctrl-gen.py def自动更新ctrl-gen.c
ctrl.c调用libusb_control_transfer控制传输,实现各种类的请求,
device.cuvc设备相关处理
diag.c打印错误,帧格式,设备信息等
frame-mjpeg.cmjpeg解码需要库支持,可以不用,不定义LIBUVC_HAS_JPEG即不用
frame.c帧处理,yuv转rgb等接口
init.c初始化接口
misc.capple平台实现strndump
stream.c流处理相关实现,重点
example.c例程
test.c例程
libuvc/include下
utlist.h
libuvc/libuvc.h
libuvc/libuvc_config.h.in复制一份改为libuvc/libuvc_config.h
libuvc/libuvc_internal.h
用户只需要包含
#include "libuvc/libuvc.h"即可。
程序分为以下步骤
uvc_init->uvc_find_device->uvc_open->uvc_get_stream_ctrl_format_size->uvc_start_streaming
对应的数据结构分别是
uvc_context_t->uvc_device_t->uvc_device_handle_t->uvc_stream_ctrl_t
结束流程反过来
uvc_stop_streaming
uvc_close
uvc_unref_device
uvc_exit
uvc_iit:
uvc_context_t *ctx;
uvc_init(&ctx, NULL);
创建实例uvc_context_t,ctx指向该实例,动态分配的,所以最后需要调用,uvc_exit(ctx);释放ctx指向的实例。
根据传入的参数填充实例的成员:
如果传入参数NULL,则初始化libusb环境ctx->usb_ctx;ctx->own_usb_ctx = 1;
如果传入参数usb_ctx,直接使用libusb环境usb_ctx; ctx->own_usb_ctx = 0;
uvc_exit:
uvc_exit(ctx);
1.uvc-exit中先遍历关闭所有打开的uvc_device_handle_t
DL_FOREACH(ctx->open_devices, devh) {
uvc_close(devh);
}
2.这里own_usb_ctx记录libusb环境是不是libuvc初始化的,如果是则需要在
uvc_exit时调用libusb_exit(ctx->usb_ctx);释放对应的libusb环境,否则不需要。
3.uvc_exit中最后free(ctx);释放,init时创建的实例。
这里uvc_init和uvc_exit并不是完全对称,uvc_init只负责了创建uvc_context_t实例和初始化libusb环境,所以uvc_exit理论上也只需要释放uvc_context_t实例和libusb环境,但是uvc_exit这里还多做了uvc_close的操作,理论上这个操作应该由用户去做和uvc_open对应,但是这里接口做了就可以避免用户忘记做, 这种方式的话优劣不同角度考虑衡量。
uvc_find_device通过vid pid和sn查找设备
实际是通过
uvc_get_device_list->libusb_get_device_list 即调用libusb查找设备
会malloc实例
即创建uvc_device_t 实例。
查找的判断标准是,搜索所有接口的接口类和接口子类是14和2(可以参考uvc规格书)
然后还要和用户提供的参数vid pid sn匹配才算。如果用户设置vid pid sn为0,则只需要匹配接口类和接口子类。
/* Video, Streaming */
if (if_desc->bInterfaceClass == 14 && if_desc->bInterfaceSubClass == 2)
{
got_interface = 1;
}
这里还有一些特殊pid,vid的处理,不属于标准可以不管。
uvc_find_device只查找第一个匹配创建设备实例。
uvc_open首先libusb层面打开设备
libusb_open(dev->usb_dev, &usb_devh);
然后调用uvc_open_internal进行uvc设备的处理
这里理论上一个libusb设备可以对应多个uvc设备,比如IAD,见前面我们对libuvc的修改支持IAD多UVC设备。
uvc_open_internal这里就会malloc创建uvc_device_handle_t实例,然后填充该实例
其中更重要的是
uvc_get_device_info获取uvc信息,创建信息实例
,调用uvc_scan_control获取控制接口信息
实际就是查找接口类和子类是14和2(前面实际查找设备时也匹配过)
if (if_desc->bInterfaceClass == 14 && if_desc->bInterfaceSubClass == 1) // Video, Control
break;
记录控制接口的端点号和接口号
info->ctrl_if.bInterfaceNumber = interface_idx;
if (if_desc->bNumEndpoints != 0) {
info->ctrl_if.bEndpointAddress = if_desc->endpoint[0].bEndpointAddress;
}
然后是调用uvc_parse_vc
解析控制接口里面的各种unit,终端等。
记录到结构体uvc_control_interface_t里面。
其中在UVC_VC_HEADER时
uvc_parse_vc_header处理控制接口对应的流接口
uvc_scan_streaming
信息会记录到创建的实例uvc_streaming_interface_t中
比如关键的参数接口号
stream_if->bInterfaceNumber = if_desc->bInterfaceNumber;
是从控制接口VC_HEADER段中的参数中获取的bIncollection记录了开始接口,baInterfaceNr记录了接口数
这样就知道对应的流接口是哪几个了
而uvc_close(devh);是反过程
停止流,释放接口,关闭libusb设备,
调用uvc_unref_device释放devh。
uvc_stream_ctrl_t ctrl;
uvc_get_stream_ctrl_format_size(
devh, &ctrl, UVC_FRAME_FORMAT_MJPEG, 1280, 720, 0
);
会根据前面记录的流接口
匹配guidFormat
if (!_uvc_frame_format_matches_guid(cf, format->guidFormat))
continue;
匹配图像大小
DL_FOREACH(format->frame_descs, frame) {
if (frame->wWidth != width || frame->wHeight != height)
continue;
记录接口,申请libusb的接口
ctrl->bInterfaceNumber = stream_if->bInterfaceNumber;
UVC_DEBUG("claiming streaming interface %d", stream_if->bInterfaceNumber );
uvc_claim_if(devh, ctrl->bInterfaceNumber);
将相关信息,填入uvc_stream_ctrl_t ctrl;
后面就可以使用ctrl进行流的传输了。
uvc_start_streaming首先uvc_stream_open_ctrl设置接口打开对应的流。
会创建实例uvc_stream_handle_t。
uvc_stream_start则
选择altsetting接口libusb_set_interface_alt_setting
根据是isco还是bulk传输,提交对应的libusb传输
Libusb传输的回调是
_uvc_stream_callback
创建线程_uvc_user_caller用于执行用户的回调
pthread_create(&strmh->cb_thread, NULL, _uvc_user_caller, (void*) strmh);
_uvc_user_caller中需要
等待last_seq != strmh->hold_seq即认为收到了一帧进行一帧处理,调用用户回调。
_uvc_stream_callback->_uvc_process_payload->_uvc_swap_buffers中会更新hold_seq
根据payload的fid反转表示帧结束。
if (strmh->fid != (header_info & 1) && strmh->got_bytes != 0)
uvc_stop_streaming则调用uvc_stream_close->uvc_stream_stop先设置strmh->running为0
需要等待_uvc_user_caller线程结束,该线程结束的条件是strmh->running为0
if (!strmh->running) {
pthread_mutex_unlock(&strmh->cb_mutex);
break;
}
停止接口uvc_stream_stop
释放接口uvc_release_if
释放各种malloc的资源,互斥量条件变量等
释放uvc_stream_handle_t本身
首先libusb获取设备列表
libusb_get_device_list(ctx->usb_ctx, &usb_dev_list);
假设匹配某一个设备
usb_dev = usb_dev_list[++dev_idx]
创建uvc_devs实例,其成员->usb_dev指向usb_dev
uvc_dev->usb_dev = usb_dev;
也就是uvc_dev->usb_dev是usb_dev_list中的一个,
但是后面libusb_free_device_list(usb_dev_list, 1);将整个列表都释放了,
那么uvc_dev->usb_dev指向的将是一个释放掉的设备指针。
这里是否有问题待确认。
以上分享了libuvc的主要的程序结构流程,重点是uvc_open中是如何创建uvc设备,查找控制接口流接口的,然后就是stream的start和stop。
流的处理在_uvc_stream_callback进行,维护一个状态机,流的重发,帧结束判断等,判断一帧完成则给stream的线程发送信号,等stream的线程处理。
每一个stream都在start时创建一个线程,进行帧处理回调用户回调,在stream stop时该线程结束。