Linux摄像头驱动2——UVC

优快云仅用于增加百度收录权重,排版未优化,日常不维护。请访问: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 interfaceVS interface

VC interface用于控制,内部又分为多个unitterminalunit用于内部处理,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的“壳”。

驱动的核心还是fopsioctl_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()ioctl2readmmappoll,这点和前面的虚拟驱动一样。

这其中最重要的就是ioctl2,它使用video_usercopy()获得用户空间传进来的参数,调用__video_do_ioctl()v4l2_ioctls[]数组里找到对应的uvc_ioctl_ops

uvc_ioctl_ops每个函数的实现放在后面写代码里,逐个讲解。

UVC驱动的重点在于:

  • 对描述符的解析;
  • 属性的控制: 通过VideoControl Interface来设置;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值