《网蜂A8实战演练》——12.Linux USB 摄像头驱动

第14章 Linux USB 摄像头驱动

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第1张图片

在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 USB 接口的摄像头。这一章主要是介绍 USB 摄像头的设备驱动程序。在我们印象中,驱动程序都是一个萝卜一个坑,拿到一个硬件就需要去安装它相对应的驱动程序。有时候稍有不对还会导致电脑崩溃,是不是让人很郁闷?这一章我们讲 USB 摄像头设备驱动,那么是不是支持所有的 USB 摄像头驱动呢?带着这个疑问开始我们这一章的摄像头学习之旅吧。

14. 1 确定 USB 摄像头支持 UVC (在 PC 上) 


WEBEE 在某宝上搜索 USB 摄像头,发现了摄像头形状千奇百怪,那到底哪一种适合这一章我们学习呢?摄像头的市场并不仅仅只是针对我们这些程序猿,很多参数并不会在介绍页面上写出来,你去实体店上买,那些卖家很可能也不知道。所以在购买 USB 摄像头要认准这些参数哦,不然,按照这一章的教材,很可能答不到效果哦,那么可能就需要自己对我们的应用层的测试代码进行修改哦。


那什么 USB 摄像头适合我们这一章的教程呢,这里有几个关键字: 1.支持
UVC(免驱), 2.YUV 或者 MJPEG 格式输出。


在写这一章教程的时候, WEBEE 手头刚好有一个 USB 摄像头,想当年还是买电脑的时候送的,不知道现在能不能用上。那拿到摄像头,我们需要怎么做呢?

14.1.1 把摄像头插入 PC 机的 USB 接口,查看 ID


注:如果你是在 Ubuntu 等 linux操作系统下请看 1~2,在 windows 下请直接看看 3 。1. 在 linux 类操作系统下插入 USB 摄像头,用 dmesg 打印信息


#dmesg
[ 8705.813109] uvcvideo: Found UVC 1.00 device USB2.0 UVC PC
Camera (174f:5931)
[ 8705.867695] uvcvideo: UVC non compliance - GET_DEF(PROBE) not
supported. Enabling workaround.
[ 8705.886554] uvcvideo: Found UVC 1.00 device USB2.0 Camera
(1e4e:0102)
[ 8705.888801] uvcvideo: UVC non compliance - GET_DEF(PROBE) not
supported. Enabling workaround.
[8705.889829] input: USB2.0 Camera as
/devices/pci0000:00/0000:00:1a.7/usb1/1 -1/1 -1:1.0/input/input12
[ 8705.890440] usbcore: registered new interface driver uvcvideo
[ 8705.890446] USB Video Class driver (1.1.1)
[ 8827.856129] pool[5982]: segfault at 0 ip (null) sp afabc0ec error 14 in
gnome-screenshot[8048000+12000]


第一个 UVC 1.00 device USB2.0 UVC PC Camera 是笔记本自带的摄像头它的 VID:PID 是 174f:5931 ;第二个 UVC 1.00 device USB2.0 Camera 也就是我们插入的 USB 摄像头他的 VID:PID 是 1e4e:0102。这里的 ID 号可以在下一步 UVC 官方的文档中进一步确定是否被支持。


2. 用 ls /dev/video* 查看设备节点
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第2张图片

这里的 video0 是笔记本自带的摄像头的设备节点, video1 才是我们刚接入的 USB 摄像头。


3. 在 windows 操作系统下插入 USB 摄像头插入,打开设备管理器
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第3张图片
第一个 USB2.0 Camera 是我们接入的 USB 摄像头,第二个 USB2.0 UVCPC Camera 是笔记本自带的摄像头。

右键属性 -> 详细信息 > 属性 选择硬件 ID 查看
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第4张图片
可以得到插入的 USB 摄像头 VID:PID 为 1e4e: 0102 。 这里的 ID 号可以在下一步 UVC 官方的文档中进一步确定是否被支持。

14. 1.2 确定 USB 摄像头种类


通过这个文档《摄像头驱动VID+PID 大全》 来确定芯片类型,这个文件在附带的文件夹下;通过这个网页 http://www.ideasonboard.org/uvc/ 来查看是否支持 UVC,这个网站是 USB Video Class Linux device driver 的主页,里面有 UVC 的详细的介绍。根据前面的打印信息,根据自己的 ID 号, WEBEE 这里是搜索 USB 摄像头的 VID 号: 1e4e 和 PID 号: 0102


通过摄像头的 ID,可以看到该摄像头是否支持 UVC 和其他信息。绿勾代表支持。



14.1.3 安装并使用 xawtv 测试 (Ubuntu 下)


1. 安装 xawtv 测试软件
#sudo apt-get install xawtv


2. 执行 xawtv 后面带 usb 摄像头的设备节点
#xawtv /dev/videoX


《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第5张图片
得到图像, PC 端测试结束。

14. 2 移植到 WEBEE210 开发板


确定 USB 摄像头在 PC 上可以用之后,就需要让它在我们的开发板也能用上这个摄像头。但是接入我们之前板子上的 USB 接口,发现内核却没显示 PC机上打印的信息。


这是因为 UVC 是 Microsoft 与另外几家设备厂商联合推出的为 USB 视频捕获设备定义的协议标准,目前已成为 USB org 标准之一。如今的主流操作系统(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供 UVC 设备驱动,因此符合 UVC 规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用,这也是上面说的免驱的意思。使用 UVC 技术的包括摄像头、数码相机、类比影像转换器、电视棒及静态影像相机等设备。


但是之前在我们板子上的内核并没有把这个驱动包含进来,所以现在为了能在板子上运行,有两种方法, 1.重新配置内核,把 UVC 编进内核,并测试是否可以用 2.自己从零写这个驱动。


因为这个 usb 摄像头涉及到了很多东西,从零写起来比较复杂,字里行间很难让大家理解,所以这里先用第一种方法实现,在后面的章节会分析内核的这个驱动,你也可以明白这个驱动的来龙去脉,再加上你自己的代码阅读和悟性,相信你可以搞懂的。 -.-

注:如果你买的是 webee 配套的摄像头直接跳到 14.3

0. 好了,打开我们的内核目录

注:这里的内核是基于移植好 OHCI 主控制器的内核,用之前的内核配置好也是不能用的,因为 usb 主控制器是会被用到的。 请务必先看第 10 章并移植好内核,该实验需要此基础上开发。(或者可以在文件夹下用我们配置好 ohci 的内核)


#make menuconfig


1. 进入 USB support
Device Drivers --->
   [*] USB support --->


如图配置:

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第6张图片

2. 选中 Multimedia support


Device Drivers --->
      <*> Multimedia support --->


如图配置:

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第7张图片


3. 再进入 Media USB Adapters
Device Drivers --->
    <*> Multimedia support --->
         <*>Media USB Adapters --->


如图配置
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第8张图片

注:如果你不想编译成模块,可以把 UVC 这一项改为*,之后就不用 insmod 了

4. 进入 V4L platform devices


Device Drivers --->

    <*> Multimedia support --->

           <*>V4L platform devices --->


如图配置



5. 编译内核


#make uImage


6. 编译模块并拷贝下面三个 ko 文件到文件系统下


#make modules
# cp ./drivers/media/v4l2-core/videobuf2-memops.ko /nfs/ko
# cp ./drivers/media/v4l2-core/videobuf2-vmalloc.ko /nfs/ko
#cp ./drivers/media/usb/uvc/uvcvideo.ko /nfs/ko


把生成的./arch/arm/boot/uImage 烧进开发板,重新启动,进行下一步。

14.3 官方淘宝店上摄像头的配置
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第9张图片

如果你手头上和 Webee 一样有不知型号的摄像头,你可以按 14.2 节去试一下能不能用。但是如果你没有的话,强烈建议在我们官方的淘宝店购买 USB 摄像头, 也就是上面这一张图片, 之后按照这一节的教程,你是可以很顺利的完成这一章的实验。因为 Webee 已经用这个摄像头实验过了。而且这个摄像头还可以用在接下来第八部分的综合实验上哦。


这一节和上一节( 14.2)差不多, 只是在内核配置中添加了适配这款摄像头的配置而已。 如果你买的不是 webee 的摄像头, 配置完 14.2 后可以跳过这一


0. 打开我们的内核目录
注:这里的内核也是基于移植好 OHCI 主控制器的内核,用之前的内核配置好也是不能用的,因为 usb 主控制器是会被用到的。 请务必先看第 10 章并移植好内核,该实验需要此基础上开发。(或者可以在文件夹下用我们配置好 ohci 的内核)


#make menuconfig


1. 进入 USB support

Device Drivers --->
    [*] USB support --->


如图配置:
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第10张图片

2. 选中 Multimedia support

Device Drivers --->
      <*> Multimedia support --->


如图配置:
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第11张图片

3. 再进入 Media USB Adapters


Device Drivers --->
     <*> Multimedia support --->
           <*>Media USB Adapters --->


如图配置
《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第12张图片

注:如果你不想编译成模块,可以把 UVC 这一项改为*,之后就不用 insmod 了


4. 进入 GSPCA base webcams


Device Drivers --->
   <*> Multimedia support --->
        <*>Media USB Adapters --->
            <*>GSPCA base webcams


如图配置

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第13张图片

5. 进入 V4L platform devices


 Device Drivers --->
      <*> Multimedia support --->
            <*>V4L platform devices --->


如图配置


6. 编译内核


#make uImage


7. 编译模块并拷贝下面三个 ko 文件到文件系统下


#make modules
# cp ./drivers/media/v4l2-core/videobuf2-memops.ko /nfs/ko
# cp ./drivers/media/v4l2-core/videobuf2-vmalloc.ko /nfs/ko
#cp ./drivers/media/usb/uvc/uvcvideo.ko /nfs/ko


把生成的./arch/arm/boot/uImage 烧进开发板,重新启动,进行下一步。

14. 4 在 WEBEE210 的 LCD 上显示 USB 摄像头图像


1. 重启开发板,加载模块。


#cd ./ko
#insmod videobuf2-memops.ko
#insmod videobuf2-vmalloc.ko

#insmod uvcvideo.ko


出现如下信息


2.插入 USB 摄像头到 webee210 板子上,出现如下信息



ls /dev/video* ,如图出现 video0


3. 执行 qt 测试程序这文件夹下有两个 qt 程序: qt_camera_yuv_ts 和 qt_camera_mjpeg_ts。先拷贝生成的 qt_camera_mjpeg_ts 文件到 QT 文件系统下,再执行。


# ./ qt_camera_mjpeg_ts -qws


出现图像:

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第14张图片


这样,我们的 usb 摄像头实验现象就出来了。

14. 5 QT 测试程序浅析


对于 qt 应用程序,除了做开启 v4l2 视频设备的一些初始化工作外,还要注意到一个编码转化的问题。 如果是 YUV 输出的话, 这里的转码是 YUV422 转RGB888, 如果是 MJPE 的话,则不需要这个函数。

14.5.1 YUV 格式输出


VideoDevice *vd;
/* 初始化一个 VideoDevice 设备 */
void ProcessImage::paintEvent(QPaintEvent *)
{
/*捕获图片*/
rs = vd->get_frame((void **)&p,&len);
/*将 yuv442 转为 rgb24 码*/
convert_yuv_to_rgb_buffer(p,pp,WIDTH,HEIGHT/*QWidget::width(),QWidget::height()*/);
frame->loadFromData((uchar *)pp,/*len*/WIDTH * HEIGHT * 3*sizeof(char));
/*用 label 控件将图片显示于 LCD*/
label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor));
// label->show();
rs = vd->unget_frame();
// label->drawFrame();
}


这里要科普一下 YUV 与 RGB 编码的相关知识。YUV 是编译 true-color 颜色空间( color space)的种类, Y'UV, YUV, YCbCr,YPbPr 等专有名词都可以称为 YUV,彼此有重叠。“ Y”表示明亮度( Luminance、Luma),“U”和“V”则是色度、浓度( Chrominance、 Chroma), Y'UV, YUV,YCbCr, YPbPr 常常有些混用的情况,其中 YUV 和 Y'UV 通常用来描述模拟信号,而相反的 YCbCr 与 YPbPr 则是用来描述数位的影像信号,例如在一些压缩格式内 MPEG、 JPEG 中,但在现今, YUV 通常已经在电脑系统上广泛使用。


RGB 颜色模型或红绿蓝颜色模型,是一种加色模型,将红( Red)、绿( Green)、蓝( Blue)三原色的色光以不同的比例相加,以产生多种多样的色光。RGB24(or RGB888)每像素 24 位(比特 s per pixel, bpp)编码的 RGB 值:使用三个 8 位无符号整数( 0 到 255)表示红色、绿色和蓝色的强度。这是当前主流的标准表示方法,用于真彩色和 JPEG 或者 TIFF 等图像文件格式里的通用颜色交换。它可以产生一千六百万种颜色组合,对人眼来说其中很多已经分辨不开。RGB32 模式实际就是 24 比特模式,余下的 8 比特不分配到象素中,这种模式是为了提高数据输送的速度( 32 比特为一个 DWORD, DWORD 全称为 DoubleWord,一般而言一个 Word 为 16 比特或 2 个字节,处理器可直接对其运算而不需额外的转换)。同样在一些特殊情况下,如 DirectX、 OpenGL 等环境,余下的8 比特用来表示象素的透明度( Alpha)。

Uvc 摄像头一般的视频输出格式为 yuv 或 mjpg。 如果你的摄像头是 YUV 格式输出,但是我们的 LCD 显示屏幕是 RGB24 的显示模式,所以我们需要把 YUV格式的图像转为 RGB 格式才能在 LCD 上显示。


YUV 与 RGB 的转换有某种对应关系,所以可以通过算法进行转换。下面是一种 YUV 转 RGB 的算法:

int ProcessImage::convert_yuv_to_rgb_buffer(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out = 0;
unsigned int pixel_16;
unsigned char pixel_24[3];
unsigned int pixel32;
int y0, u, y1, v;
for(in = 0; in < width * height * 2; in += 4) {
pixel_16 =
yuv[in + 3] << 24 |
yuv[in + 2] << 16 |
yuv[in + 1] << 8 |
yuv[in + 0];
y0 = (pixel_16 & 0x000000ff);
u = (pixel_16 & 0x0000ff00) >> 8;
y1 = (pixel_16 & 0x00ff0000) >> 16;
v = (pixel_16 & 0xff000000) >> 24;
pixel32 = convert_yuv_to_rgb_pixel(y0, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
pixel32 = convert_yuv_to_rgb_pixel(y1, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
}
return 0;
}


int ProcessImage::convert_yuv_to_rgb_pixel(int y, int u, int v)

{
unsigned int pixel32 = 0;
unsigned char *pixel = (unsigned char *)&pixel32;
int r, g, b;
r = y + (1.370705 * (v-128));
g = y - (0.698001 * (v-128)) - (0.337633 * (u-128));
b = y + (1.732446 * (u-128));
if(r > 255) r = 255;
if(g > 255) g = 255;
If(b > 255) b = 255;
if(r < 0) r = 0;
if(g < 0) g = 0;
if(b < 0) b = 0;
pixel[0] = r * 220 / 256;
pixel[1] = g * 220 / 256;
pixel[2] = b * 220 / 256;
return pixel32;
}


这里的算法用到了浮点,所以运算起来比较慢,所以在 LCD 显示的时候会有卡顿现象。关于编码的高效率转化,有人多人在研究,包括硬解和软件。想要提供算法的效率,提供刷屏率,可以通过优化算法实现。

14.5. 2 mjpeg 格式输出


VideoDevice *vd;
/* 初始化一个 VideoDevice 设备 */
void ProcessImage::paintEvent(QPaintEvent *)
{
/*捕获图片*/
rs = vd->get_frame((void **)&p,&len);
/*不用转码*/
//convert_yuv_to_rgb_buffer(p,pp,WIDTH,HEIGHT/*QWidget::width(),QWidget::height()*/);
/*直接把捕获的数据传递进去*/
frame->loadFromData((uchar *)p,/*len*/WIDTH * HEIGHT * 3*sizeof(char));
/*用 label 控件将图片显示于 LCD*/
label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor));
// label->show();
rs = vd->unget_frame();

// label->drawFrame();
}

14. 6 V4L2 架构浅析


14. 6.1 什么是 V4L2


V4L2 即 Video4Linux2,它是 Linux 内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。


V4L2 支持三类设备:视频输入输出设备、 VBI 设备和 radio 设备,分别会在/dev 目录下产生 videoX、 radioX 和 vbiX 设备节点。我们常见的视频输入设备主要是摄像头。 V4L2 在 Linux 系统中的架构如图 14.1 所示:

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第15张图片

Linux 系统中视频输入设备主要包括以下四个部分:
字符设备驱动程序核心: V4L2 本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间。
V4L2 驱动核心: 主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数。
平台 V4L2 设备驱动: 在 V4L2 框架下,根据平台自身的特性实现与平台相关的 V4L2 驱动部分,包括注册 video_device v4l2_dev
具体的 sensor 驱动: 主要上电、提供工作时钟、视频图像裁剪、流 IO 开启等,实现各种设备控制方法供上层调用并注册 v4l2_subdev

14. 6.2 定位 USB 摄像头驱动源码


在前面的测试中,发现插入 USB 摄像头后,会打印出下面这些信息:



认真学习的读者,应该对前面几行信息相当熟悉,这就是第十章讲过的 OHCI驱动和 hub.c 的相关知识,这里我们只关心如图上最后三行信息。。


使用 Source Insight打开内核工程,搜索Found UVC,搜索结果如图 14.2:

《网蜂A8实战演练》——12.Linux USB 摄像头驱动_第16张图片

点击进去,在\drivers\media\usb\uvc\uvc_driver.c 文件的第 1864 行:


uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",


这看上去好像有点熟悉,没错,这就是内核输出信息的倒数第三行打印信息。


uvcvideo: Found UVC 1.00 device USB2.0 Camera (1e4e:0102)


14. 6.3 USB 摄像头设备驱动(uvc_driver.c) 


前面说过 Webee 使用的摄像头是符合 UVC(免驱)和 YUV 格式输出。 这种摄像头相对没有那么复杂,非常适合初学者学习 USB 摄像头驱动。通过 14.5.2小节的分析知道 uvc_driver.c 这个文件就是我们的 UVC 摄像头设备驱动了。


14. 6.3.1 入口函数


还是老样子,分析一个驱动,从它的入口函数开始:

static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
if (ret < 0) {
uvc_debugfs_cleanup();
return ret;
}
printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
return 0;
}


uvc_init()函数主要通过 usb_register(driver)宏来注册一个 usb_driver,这个宏其实是调用了 usb_register_driver 函数。这与第十章的 10.4 小节的 USB 鼠标驱动的入口函数做的工作基本一致


#define usb_register(driver) \
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)


14. 6.3.2 uvc_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,
},
};
struct uvc_driver {
struct usb_driver driver;
};


当发现内核有与 uvc_ids 匹配的 USB 摄像头就会调用 uvc_probe 函数


static struct usb_device_id uvc_ids[] = {
/* LogiLink Wireless Webcam */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE

| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0416,
.idProduct = 0xa91a,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
……
/* Generic USB Video Class */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },
{}
};


uvc_ids 是 usb_device_id 类型的,在 usb 鼠标驱动的章节里也讲解过,具体怎么匹配,自己回去看看 10.4 小节的内容吧,这里不重复了,不是重点。

14. 6.3.3 uvc_probe 函数


当内核发现当前插入的 USB 摄像头被匹配后,最终就会调用 uvc_probe 函数,下面是 uvc_probe 函数的主体,为了方便分析主干,把不重要的省略掉。


/* 参考: \drivers\media\usb\uvc\Uvc_driver.c */
static int uvc_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
/* 通过接口获取 usb_device */
struct usb_device *udev = interface_to_usbdev(intf);
struct uvc_device *dev;
/* 为 uvc_device 分配内存 */
dev = kzalloc(sizeof *dev, GFP_KERNEL)
/* 初始化各种锁,初始化 uvc_device 成员 ... */
......
/* 初始化自旋锁、引用计数等 */
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
goto error;
/* Initialize controls. */
if (uvc_ctrl_init_device(dev) < 0)
goto error;
/* Scan the device for video chains. */

if (uvc_scan_device(dev) < 0)
goto error;
/* Register video device nodes. */
if (uvc_register_chains(dev) < 0)
goto error;
......
error:
uvc_unregister_video(dev);
return -ENODEV;
}


14. 6.3.4 uvc_register_chains 函数


/* 参考: \drivers\media\usb\uvc\Uvc_driver.c */
static int uvc_register_chains(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
int ret;
list_for_each_entry(chain, &dev->chains, list) {
ret = uvc_register_terms(dev, chain);
if (ret < 0)
return ret;
}
......
return 0;
}


uvc_register_chains 函数遍历所有 chains 对链表上的每个 chain 都调用uvc_register_terms()函数。

14. 6.3.5 uvc_register_terms 函数


/* 参考: \drivers\media\usb\uvc\Uvc_driver.c */
/* Register all video devices in all chains. */
static int uvc_register_terms(struct uvc_device *dev,
struct uvc_video_chain *chain)
{
...
uvc_register_video(dev, stream);
...
return 0;

}


uvc_register_terms()函数调用 uvc_register_video(),它是一个主体函数。

14. 6.3.6 uvc_register_video 函数


static int uvc_register_video(struct uvc_device *dev, struct uvc_streaming *stream)
{
struct video_device *vdev;
/* 初始化流接口 */
uvc_video_init(stream);
/* 分配 video_device 结构体 */
video_device_alloc();
/* 设置 video_device 的 v4l2_device、 v4l2_file_operations 等成员 */
vdev->v4l2_dev = &dev->vdev;
vdev->fops = &uvc_fops; //后面再分析,暂且记住它
vdev->release = uvc_release;
/* 注册一个 video_devices 结构体 */
video_register_device(vdev, VFL_TYPE_GRABBER, -1);
}
struct video_device *video_device_alloc(void)
{
return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}


uvc_register_video()函数主要做了三件事:

1. 分配 video_device 结构体

2. 初始化 video_device v4l2_file_operations 成员,里面包含各种函数指针,里面的 open、 read、 write、 ioctl 就是底层 USB 摄像头驱动的重点工作。

3. 注册一个 video_devices 结构体

14. 6.3.7 video_register_device 函数


/* 参考: \include\media\v4l2-dev.h */
static inline int __must_check
video_register_device(struct video_device *vdev,int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}


video_register_device()函数又是通过调用 __video_register_device()函数来实现的,它的实现位于\drivers\media\v4l2-core\v4l2-dev.c。从路径上可以看到,这属于 v4l2 核心的事了。对,没错,是 v4l2 核心本分工作。

14. 6.4 V4L2 核心(v4l2-dev.c) 


14. 6.4.1 __video_register_device 函数


/* 参考: \drivers\media\v4l2-core\v4l2-dev.c */
/* __video_register_device:register video4linux devices */
int __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{
...
const char *name_base;
...
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
...
}
/* 设置各种 ctrl 属性,用于用户程序 ioctl 设置摄像头属性 */
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* Part 4: register the device with sysfs */
...
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
...

/* Part 6: Activate this minor. The char device can now be used. */
...
/* 以次设备号为下标,将 video_device 型 vdev 实例存到
* video_device[]数组里,以供其他函数提取,如在 v4l2_open
* 函数通过 video_devdata(filp)提取 video_device
*/
video_device[vdev->minor] = vdev;
}


__video_register_device()函数首先根据 type 来确定设备节点,如何知道type 是什么呢?很简单,还记得 uvc_register_video()函数?里面就将 type 设置为 VFL_TYPE_GRABBER 了,所以我们的设备节点是/dev/vide0%d.


然后设置各种 ctrl 属性,用于用户程序 ioctl 设置摄像头属性,后面会分析如何调用到这里设置的 ctrl 属性。


其次创建了 VIDEO_MAJOR = 81 ,即主设备号为 81 的字符设备,我们说过USB 摄像头驱动其实就是一个字符设备驱动,重点关注 v4l2_fops 结构体。


最后将 video_device 型 vdev 实例存到 video_device[]数组里, 以供其他函数提取,如在 v4l2_open 函数通过 video_devdata(filp)提取 video_device。

14. 6.4.2 v4l2_fops 实例


/* 参考: \drivers\media\v4l2-core\v4l2-dev.c */
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
...
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};


v4l2_fops 实例是一个 file_operations 结构体型的,这不,又回到熟悉字符设备驱动的那套架构了吗?


14. 6.4.3 v4l2_open 函数


当应用程序调用 open 函数,例如: open("/dev/video0",....),首先就会调用到 v4l2 核心里的 open 函数,也就是 v4l2_open 函数。我们来看看 v4l2_open函数做了什么工作呢?

static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
...
/* 根据次设备号从数组中得到 video_device */
vdev = video_devdata(filp);
...
/* 如果 vdev->fops->open 存在,并且 video_device 已经注册,
* 就调用 vdev->fops->open(filp)函数,即调用到底层驱动的 open 方法
*/
if (vdev->fops->open) {
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);
else
ret = -ENODEV;
}
}
/* 将__video_register_device 函数里设置好的 video_device[]返回 */
struct video_device *video_devdata(struct file *file)
{
return video_device[iminor(file->f_path.dentry->d_inode)];
}


那这个 vdev->fops->open(filp),将调用底层的驱动里的 open 方法,对于我们的 USB 摄像头驱动里的什么函数呢?

14. 6.4.4 uvc_v4l2_open 函数


前面的答案就是, vdev->fops->open(filp)相当于调用 uvc_v4l2_open()函数。这个函数的实现在\drivers\media\usb\uvc\uvc_v4l2.c 里。


/* ------------------------------------------------------------------------
* V4L2 file operations
*/
static int uvc_v4l2_open(struct file *file)
{
struct uvc_streaming *stream;
struct uvc_fh *handle;
int ret = 0;

uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
stream = video_drvdata(file);
if (stream->dev->state & UVC_DEV_DISCONNECTED)
return -ENODEV;
ret = usb_autopm_get_interface(stream->dev->intf);
if (ret < 0)
return ret;
/* Create the device handle. */
handle = kzalloc(sizeof *handle, GFP_KERNEL);
if (handle == NULL) {
usb_autopm_put_interface(stream->dev->intf);
return -ENOMEM;
}
if (atomic_inc_return(&stream->dev->users) == 1) {
ret = uvc_status_start(stream->dev);
if (ret < 0) {
usb_autopm_put_interface(stream->dev->intf);
atomic_dec(&stream->dev->users);
kfree(handle);
return ret;
}
}
v4l2_fh_init(&handle->vfh, stream->vdev);
v4l2_fh_add(&handle->vfh);
handle->chain = stream->chain;
handle->stream = stream;
handle->state = UVC_HANDLE_PASSIVE;
file->private_data = handle;
return 0;
}


关于 USB 摄像头驱动里的 uvc_v4l2_open()函数, webee 就不再继续分析下去了,相对比较复杂,我们的目的是抓 V4L2 的框架,有兴趣的读者自己研究一下呗。

14. 6.4.5 v4l2_read 函数


static ssize_t v4l2_read(struct file *filp, char __user *buf, size_t sz, loff_t *off)
{
struct video_device *vdev = video_devdata(filp);
...
/* 如果底层驱动的 read 方法不存在,则返回错误 */
if (!vdev->fops->read)
return -EINVAL;
/* 如果 video_device 已经注册,则调用到底层驱动的 read 方法 */
if (video_is_registered(vdev))
ret = vdev->fops->read(filp, buf, sz, off);
...
}


vdev->fops->read(filp, buf, sz, off)最后就相当于调用 uvc_v4l2_read()函数,这个函数也是在\drivers\media\usb\uvc\uvc_v4l2.c 文件里实现,我们不继续分析,有兴趣的读者自己研究研究哈。

14. 6.4.6 v4l2_ioctl 函数


static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
...
if (vdev->fops->unlocked_ioctl) {
...
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
}
else if (vdev->fops->ioctl) {
/* Linux3.8 已经将 ioctl 改为 unlocked_ioctl,
* 为了兼容低版本的内核所以存在 else 分支
*/
...
}
}


同 样 地 , vdev->fops->unlocked_ioctl(filp, cmd, arg); 最 后 相 当 于 调 用uvc_v4l2_ioctl() 函 数 , 它 又 调 用 video_usercopy(file, cmd, arg,uvc_v4l2_do_ioctl);函数, video_usercopy()函数的作用从名字上可以猜测,它是根据用户空间传递过来的 cmd 命令,调用 uvc_v4l2_do_ioctl()函数来解析 arg参数。

14. 6.4.7 uvc_v4l2_do_ioctl 函数


uvc_v4l2_do_ioctl()函数是一个非常庞大的函数,有将近 600 行的代码,这里,我们只摘取部分代码。


static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_fh *handle = file->private_data;
struct uvc_video_chain *chain = handle->chain;
struct uvc_streaming *stream = handle->stream;
long ret = 0;
switch (cmd) {
/* Query capabilities */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap, 0, sizeof *cap);
strlcpy(cap->driver, "uvcvideo", sizeof cap->driver);
strlcpy(cap->card, vdev->name, sizeof cap->card);
usb_make_path(stream->dev->udev,
cap->bus_info, sizeof(cap->bus_info));
cap->version = LINUX_VERSION_CODE;
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING | chain->caps;
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
else
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
break;
}
case VIDIOC_G_PRIORITY:

case VIDIOC_S_PRIORITY:
...
case VIDIOC_QUERYCTRL:

case VIDIOC_G_CTRL:

}


uvc_v4l2_do_ioctl 根据应用程序传进来的 cmd 命令来判断要执行哪个switch 分 支 。 cmd 命 令 有 很 多 种 了 , 这 里 随 便 列 举 几 种 , 比 如 :VIDIOC_QUERYCAP、 VIDIOC_G_PRIORITY、 VIDIOC_S_PRIORITY 等等。那这些 cmd 是什么时候被设置的呢?


14. 6.4.8 ctrl 属性的函数调用流程
  uvc_probe
       uvc_register_chains
           uvc_register_terms
               uvc_register_video
                    video_register_device
                          __video_register_device
                               determine_valid_ioctls


14. 6.4.9 determine_valid_ioctls 函数


这些 ctrl 属性就是 USB 摄像头的各种属性,比如亮度的调节,打开、关闭STREAM 等等操作,这些是 v4l2 核心最最复杂的工作了,没有之一。


/* 参考: \drivers\media\v4l2-core\v4l2-dev.c */
static void determine_valid_ioctls(struct video_device *vdev)
{
...
SET_VALID_IOCTL(ops, VIDIOC_QUERYCAP, vidioc_querycap);
SET_VALID_IOCTL(ops, VIDIOC_REQBUFS, vidioc_reqbufs);
SET_VALID_IOCTL(ops, VIDIOC_QUERYBUF, vidioc_querybuf);
SET_VALID_IOCTL(ops, VIDIOC_QBUF, vidioc_qbuf);
SET_VALID_IOCTL(ops, VIDIOC_EXPBUF, vidioc_expbuf);
SET_VALID_IOCTL(ops, VIDIOC_DQBUF, vidioc_dqbuf);
SET_VALID_IOCTL(ops, VIDIOC_STREAMON, vidioc_streamon);
SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff);
...
}


14. 7 本章小结


相信本章是很多读者最感兴趣的一章之一,本章首先动手移植了 USB 摄像头驱动到 webee210 开发板,成功显示在 7 寸 LCD 上,有木有高大上的感觉啊?其次分析了 qt 测试程序。最重点是 V4L2 架构的分析,主要包括符合 UVC 架构的 USB 摄像头设备驱动,还有 V4L2 核心的主要核心工作。限于时间和文章的篇幅, V4L2 架构的知识没有分析的太深,有机会以后再慢慢分析。


到此,网蜂的 Linux 驱动教程基本到此结束,后会有期啊,兄弟们。






你可能感兴趣的:(驱动程序,网蜂)