优快云仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/04/22/Linux摄像头驱动2——UVC/#more
Linux摄像头驱动学习第二篇,对USB摄像头驱动USB video class(UVC)进行详细分析、编写。
这次要写一个真正的摄像头驱动,内容有点多。
先简单的介绍了USB接口,了解Linux中USB设备描述符的意义。
然后再移植内核自带的USB摄像头驱动,同时也验证了摄像头的可用。
最后为了学习,逐句写一个摄像头驱动,再总结。
1.UVC基础
UVC是USB video class的简写,也就是USB接口的视频设备。
UVC其实很好理解,就是V4L2+USB。
前面的虚拟摄像头驱动,数据的来源是自己构造的虚拟数据,现在V4L2的数据来源则是通过USB传进来的真实摄像头视频数据。
除了视频数据,摄像头还把自己的特性(比如支持哪几种分辨率)告诉驱动,驱动则要配置摄像头(指定何种分辨率)。
1.1 USB基础知识
USB分主从系统,一般而言,PC中的USB系统就是作主系统,而一般的USB鼠标、U盘则是典型的USB从系统。
为了方便开发,USB定义了一套标准,只要是支持USB的主机,就可以支持任何一个厂商的USB鼠标、U盘,只要是被USB系统包含的设备,只要这些设备支持相应的标准,就无需重新设计驱动而直接使用。
下面简单的列出了USB设备类型,理想情况的USB系统要对这些设备作完整的支持,设备也必须符合USB规范中的要求。
Base Class | Descriptor Usage | Description |
---|---|---|
00h | Device | Use class information in the Interface Descriptors |
01h | Interface | Audio |
02h | Both | Communications and CDC Control(通讯设备) |
03h | Interface | HID (Human Interface Device) |
05h | Interface | Physical |
06h | Interface | Image |
07h | Interface | Printer |
08h | Interface | Mass Storage(存储) |
09h | Device | Hub |
0Ah | Interface | CDC-Data |
0Bh | Interface | Smart Card |
0Dh | Interface | Content Security |
0Eh | Interface | Video |
0Fh | Interface | Personal Healthcare |
10h | Interface | Audio/Video Devices |
11h | Device | Billboard Device Class |
12h | Interface | USB Type-C Bridge Class |
DCh | Both | Diagnostic Device |
E0h | Interface | Wireless Controller |
EFh | Both | Miscellaneous |
FEh | Interface | Application Specific |
FFh | Both | Vendor Specific |
其中UVC就是Video类。
为了更好地描述USB设备的特征,USB提出了设备架构的概念。
从这个角度来看,可以认为USB设备是由一些配置,接口和端点;
即一个USB设备可以含有一个或多个配置,在每个配置中可含有一个或多个接口,在每个接口中可含有若干个端点。

此外,驱动是绑定到USB接口上,而不是整个设备。
体现到驱动上,就是一个一个的结构体,对应设备、配置、接口、端点。
其中USB video class它是在在标准的USB协议上进行了扩展,扩展的部分称为Class Specific。
- 标准的设备描述符:
{% codeblock lang:c %}
typedef struct Device_Descriptor
{
uchar bLength; //设备描述符的字节数
uchar bDescriptorType; //设备描述符类型编号
uint bcdUSB; //USB版本号
uchar bDeviceClass; //USB分配的设备类
uchar bDeviceSubClass; //USB分配的设备子类
uchar bDeviceProtocol; //USB分配的设备协议代码
uchar bMaxPacketSize0; //端点0的最大包大小
uint idVendor; //厂商编号
uint idProduct; //产品编号
uint bcdDevice; //设备出厂编号
uchar iManufacturer; //设备厂商字符串索引
uchar iProduct; //产品字符串索引
uchar iSerialNumber; //设备序列号索引
uchar bNumConfigurations; //可能的配置数
}Device_Descriptor,*pDevice_Descriptor;
{% endcodeblock %}
- 配置描述符:
{% codeblock lang:c %}
typedef struct Configuration_Descriptor
{
uchar bLength; //配置描述符 的字节数
uchar bDescriptorType; //配置描述符类型编号
uint wTotalLength; //此配置返回的所有数据大小
uchar bNumInterfaces; //此配置支持的接口数量
uchar bConfigurationValue;//Set_Configuration命令所需要的参数
uchar iConfiguration; //描述该配置的字符串索引
uchar bmAttributes; //供电模式的选择
uchar bMaxPower; //设备从总线获取的最大电流
}Configuration_Descriptor,*pConfiguration_Descriptor;
{% endcodeblock %}
-
接口描述符:
{% codeblock lang:c %}
typedef struct Interface_Descriptor
{
uchar bLength; //接口描述符的字节数
uchar bDescriptorType; //接口描述符的类型编号
uchar bInterfaceNumber; //该接口的编号
uchar bAlternateSetting; //备用的接口描述符的编号
uchar bNumEndPoints; //该接口使用 的端点数,不包括端点0
uchar bInterfaceClass; //接口类
uchar bInterfaceSubClass; //接口子类
uchar bInterfaceProtocol; //接口类协议
uchar iInterface; //描述该接口的字符串索引值
}Interface_Descriptor,*pInterface_Descriptor;
{% endcodeblock %} -
端点描述符:
{% codeblock lang:c %}
typedef struct EndPoint_Descriptor
{
uchar bLength; //端点描述符字节数
uchar bDescriptorType; //端点描述符类型编号
uchar bEndpointAddress; //端点地址及输入输出类型
uchar bmAtrributes; //端点的传输类型
uint wMaxPacketSize; //端点收发的最大包大小
uchar bInterval; //主机查询端点的时间间隔
}EndPoint_Descriptor,*pEndPoint_Descriptor;
{% endcodeblock %}
1.2 UVC硬件模型
首先从USB官网下载标准协议相关资料:Video Class -> Video Class 1.5 document set (.zip format, size 6.58MB)
。
在USB_Video_Example 1.5.pdf
里,可以得知硬件模型分为两部分:VC interface
和VS interface
。
VC interface
用于控制,内部又分为多个unit
和terminal
,unit
用于内部处理,terminal
用于内外链接;
VS interface
用于传输,内部包括视频数据传输的端点以及摄像头支持的视频格式等信息;
每个视频有且仅有一个Vieo Control
接口和可有多个Video Streaming
接口;
一个接口,就相当于一个逻辑上的USB设备。
现在,想象一下当USB摄像头插上主机,就相当于同时插上了两个设备,可通过函数去选中其中一个设备,从而去操作它。
一个设备用于控制,比如设置亮度等;
一个设备用于获取数据,选择所支持的某个格式等;
这样就基本把控制和数据分开,要控制则操作控制接口,要数据则通过数据接口。
VideoControl Interface
用于控制,比如设置亮度。
它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
可以通过uvc_query_ctrl
类似的函数来访问:
ret = uvc_query_ctrl(dev /*哪一个USB设备*/, SET_CUR, ctrl->entity->id /*哪一个unit/terminal*/, dev->intfnum /*哪一个接口:VC interface*/, ctrl->info->selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), ctrl->info->size);
VideoStreaming Interface
用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format,一个format支持多种frame,frame用来表示分辨率等信息)
可以通过__uvc_query_ctrl
类似的函数来访问:
ret = __uvc_query_ctrl(video->dev /*哪一个USB设备*/, SET_CUR, 0, video->streaming->intfnum /*哪一个接口: VS*/, probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size, uvc_timeout_param);
这里的参数VS_PROBE_CONTROL
只是枚举尝试,并不是设置,真正要设置需要使用参数VS_COMMIT_CONTROL
。
1.3 USB描述符
前面提到摄像头要把自己的特性(比如支持哪几种分辨率)告诉驱动,这个特性就是被放在USB描述符里面。
在前面下载的USB_Video_Example 1.5.pdf
文档里,有个UVC描述符层次结构例子:
将USB插在Ubuntu主机上,执行lsusb
可以看到当前的USB设备:
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 012: ID 1b3b:2977 iPassion Technology Inc.
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
可根据厂家名字iPassion Technology Inc
知道ID为1b3b:2977
的USB设备就是摄像头。
再使用-v
(显示USB设备的详细信息)和-d
(仅显示指定厂商和产品编号的设备)获取指定设备的详细信息:
lsusb -v -d 1b3b:2977
此时会打印出许多信息,精简去掉详细的数据,只留下大致框架如下:
Device Descriptor:
Configuration Descriptor:
Interface Association:
Interface Descriptor:
VideoControl Interface Descriptor:
VideoControl Interface Descriptor:
Endpoint Descriptor:
Interface Descriptor:
VideoStreaming Interface Descriptor:
VideoStreaming Interface Descriptor:
Interface Descriptor:
Endpoint Descriptor:
Interface Descriptor:
Endpoint Descriptor:
Interface Association:
Interface Descriptor:
AudioControl Interface Descriptor:
AudioControl Interface Descriptor:
Interface Descriptor:
AudioStreaming Interface Descriptor:
AudioStreaming Interface Descriptor:
Endpoint Descriptor:
AudioControl Endpoint Descriptor:
Interface Descriptor:
AudioStreaming Interface Descriptor:
AudioStreaming Interface Descriptor:
Endpoint Descriptor:
AudioControl Endpoint Descriptor:
可以看到设备描述符下有一个配置描述符,配置描述符下有两个联合接口(IAD),一个是视频的,一个是音频的。
同级的还有若干接口描述符,接口描述符下有若干VC、VS和端点,与前面的框架是完全对应的。
任取其中一个描述符:
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 7 (FRAME_MJPEG)
bFrameIndex 1
bmCapabilities 0x01
Still image supported
wWidth 640
wHeight 480
dwMinBitRate 2304000
dwMaxBitRate 2304000
dwMaxVideoFrameBufferSize 76800
dwDefaultFrameInterval 333333
bFrameIntervalType 1
dwFrameInterval( 0) 333333
就可以得知该摄像头支持一种叫FRAME_MJPEG的格式,分辨率为640*480
等信息。
因此,从上面的一系列描述符,就可完全得知摄像头的特征,后面驱动用用到具体的特性再说明。
2.内核摄像头驱动
对UVC进行学习,步骤大致如下:
首先分析内核自带的UVC是如何实现的;
然后让手里的摄像头工作起来,可能内核自带的驱动可以直接用,也可能需要移植;
最后再尝试写一个精简版的UVC驱动,深入理解。
2.1分析内核摄像头驱动
在4.13.9内核中,UVC驱动在drivers/media/usb/uvc/
文件夹里,下面对uvc_driver.c
进行分析。
a.构造usb_driver
{% codeblock lang:c %}
struct uvc_driver {
struct usb_driver driver;
};
struct uvc_driver uvc_driver = {
.driver = {
.name = “uvcvideo”,
.probe = uvc_probe,
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
{% endcodeblock %}
其中.id_table
里列举了驱动支持哪些USB设备。
b.设置usb_driver
uvc_probe
kzalloc //分配video_device
uvc_register_chains
uvc_register_terms
uvc_register_video
vdev->v4l2_dev = &dev->vdev; //设置video_device
vdev->fops = &uvc_fops;
vdev->ioctl_ops = &uvc_ioctl_ops;
vdev->release = uvc_release;
video_register_device //注册video_device
c.注册usb_driver
uvc_init
usb_register
可以看到,probe()
函数里面的操作就是前面vivid
驱动里一样的操作方式。
然后在外面加了一个usb的“壳”。
驱动的核心还是fops
和ioctl_ops
。下面对这两个操作函数的实现进行分析。
首先是v4l2_file_operations
:
{% codeblock lang:c %}
const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU
.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};
{% endcodeblock %}
里面有open()
、release()
、ioctl2
、read
、mmap
、poll
,这点和前面的虚拟驱动一样。
这其中最重要的就是ioctl2
,它使用video_usercopy()
获得用户空间传进来的参数,调用__video_do_ioctl()
在v4l2_ioctls[]
数组里找到对应的uvc_ioctl_ops
。
uvc_ioctl_ops
每个函数的实现放在后面写代码里,逐个讲解。
UVC驱动的重点在于:
- 对描述符的解析;
- 属性的控制: 通过
VideoControl Interface
来设置;