arm摄像头驱动开发

2014-09-19 09:50:37 farsight2009 阅读数 3382

嵌入式Linux/Android驱动开发揭秘(3)摄像头驱动开发

专题简介:本专题通过对Android手机摄像头部件的深入剖析,让听众了解摄像头背后的相关知识,了解摄像头硬件电路原理、以及Linux、Android系统下摄像头驱动的开发方法。
 1 认识摄像头
  1.1 摄像头分辨率
  1.2 摄像头传感器
  1.3 摄像头输出格式
  1.4 摄像头传输率
  1.5 摄像头应用领域
2 摄像头硬件工作原理
  2.1 OV9650介绍
  2.2 SCCB总线介绍
  2.3 CAMIF接口介绍
3 摄像头驱动结构及典型代码分析
  3.1 v4l2介绍
  3.2 v4l2框架分析
  3.3 android系统层次介绍
  3.4 原生版camera框架分析

主讲老师:潘老师,华清远见成都中心学术经理,金牌讲师,资深工程师 10年以上嵌入式项目开发工程经验,曾就职于在国内某大型研究所,长期从事国内、外嵌入式项目开发工作,一线资深工程师。熟悉windows/Linux/Android开发,熟悉熟悉ARM,PowerPC,MIPS系列等多种平台。长期从事仪表控制和交通运输控制系统的研究,实际参与的数十个国内大型嵌入式项目,已广泛用于工业控制和交通运输产品领域中。

在线收看:http://www.3g-edu.org/news/video016.htm

视频下载:http://pan.baidu.com/s/1sjoBsi5

课件下载:http://download.farsight.com.cn/download/pdf/Farsight2014webcast02-3.pdf

2007-03-23 22:46:00 ShorminHsu 阅读数 2888

好资源转载:http://bbs.mscommunity.com/forums/ShowThread.aspx?PostID=53794

      在Windows CE.net 4.2下使用OV511摄像头,捕获视频,显示画面。其中的执行文件是ARM版本,需要在其他平台调试的再自己编译出来就可以了。
      讲解部分主要讲了如何写流接口的USB驱动,以及如何进行控制和实时传输等内容,结合代码,比较容易理清思路。
      程序我会在以后的时间不断完善,也将依据SPCA代码做中星微301系列驱动。欢迎大家和我交流!
      E-mail:ghsnc@163.com
...
 
2011-11-24 21:22:58 dongyifengzhaowen 阅读数 2740
 

USB摄像头驱动的移植
一、驱动程序中的重要数据结构
    Linux系统下,USB设备驱动程序完全符合通用设备驱动的准则,不同的是Linux操作系统中有一个叫做“USB CORE”的子系统,它的作用是提供支持USB设备驱动程序的API(应用编程接口)和USB的主机驱动程序。它提供了许多数据结构、宏定义和功能函数来对硬件或设备进行支持。

    在Linux下编写USB设备的驱动程序从严格意义上讲,就是使用这些USB核心的子系统定义的数据结构、宏和函数来编写数据的处理功能。
    USB核子系统定义的usb driver结构是与设备驱动程序开发直接相关的核心数据结构。usb driver定义在内核源代码的<linux/usb.h>中。函数probe和disconnect是结构中最重要的两个函数


1、probe函数
    当驱动程序向子系统注册后,插入一个新的USB设备后,总是要自动进入该函数进行设备驱动程序查找。驱动程序会为这个新加入系统的设备向内部的数据结构建立一个新的实例

   在这个函数中,参数dev指定了包含设备说明的内容。而参数interface指明了该设备所含接口的数目。通常情况下,probe函数执行一些功能,来检测新加入的USB设备硬件中的生产厂商和产品定义以及设备所属的类或子类定义是否与驱动程序相符,若相符,再比较接口的数目与本驱动程序支持设备的接口数目是否相符。一般在probe函数中也会解析USB设备的说明,从而确认新加入的USB设备会使用这个驱动程序


2、disconnect函数

    当一个USB设备从系统拔掉后,为这个设备服务的设备驱动程序的disconnect函数会自动被调用。在该函数中参数dev指定了设备的内容,参数drv_context返回由probe函数注册的结构driver context的指针。在执行了disconnect函数后,所有为USB设备分配的数据结构,内存空间都会被释放。
   本项目驱动程序spca5xx用两个函数调用spca5xx._probe和spca5xx_disconnect来支持USB设备的即插即用功能。如下:
static struct usb_driver spca5xx_driver=

{
"spca5xx",
spca5xx_probe, //注册设备自我侦测功能
spca5xx_disconnect,//注册设备自我断开功能
{NULL,NULL}
);


二、驱动程序模块
    我们以成功移植到系统平台的中星微USB摄像头驱动程序spac5xx为例来说明驱动程序的构架。
    驱动程序spac5xx是按照标准的USB VIDEO设备的驱动框架编写(其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.C文件),采用了显式的模块初始化和卸载函数,即调用module init函数来初始化一个模块,并在卸载时调用moduel exit函数。
   整个源程序由四个主体部分组成:初始化模块卸载模块,上层软件接口模块,数据传输模块。其具体实现如下:
  1、初始化模块:
module——init(usb_spca5xx_init);
static int—init usb_spca5xx_init(void)
{
#ifdef CONFIG—PROe-FS
proc_spca50x_create();//建立PROC设备文件
#endif净CONFIG—PROC—FS^|
if(usbregister(&spca5xx_driver)<0)//注册USB设备驱动
retum-1;
info(”spca5xx driver%s registered”,version);

return O;
)
  2、卸载模块:
module——exit(usb_spca5xx_exit);
tatic void—exit usb_spca5xx_exit(void)
{
usb_deregister(&spca5xx_driver);//注销USB设备驱动
info(”driver spca5xx deregistered”);
#ifdefCONFIG_PROC_FS
proe_spca50xdestroy O;//撤消PROC设备文件
#endif/*CONFIG_PROC_FS*/
}

3、上层软件接口模块:
    该模块通过fileoperations数据结构实现设备的系统调用。由于摄像头的功能在于数据采集,不需要向摄像头输出,因此没有实现write系统调用。其关键的数据结构如下:

static struct file_operations spca5xx fops={
.owner=TmS_MODULE,
.open=spca5xxopen,//open功能
.release=spca5xx_elose,//close功能
.read=spcaSxxread,//read功能
.mmap=spca5xx_mmap,∥内存映射功能
.ioctl=spca5xx_ioctl,//文件信息获取
.1lseek=no llseek,//文件定位功能未实现
);


4、数据传输模块:
源程序采用tasklet来实现同步快速传递数据,并通过spcadecode.C上的软件解码模块实现图形信息的解码。在spcaopen函数中,子函数spcaS0x_init_iSOC实现数据采集功能。当设备被打开时,同步传输数据也开始,并通过spca50xmove_data函数将数据传递给驱动程序,驱动程序通过轮询的办法实现对数据的访问。

三、驱动程序移植过程
    Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块

   如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,不利于调试,所以本项目摄像头驱动使用模块方式加载。在系统平台上安装USB摄像头驱动,首先把USB控制器驱动模块静态编译进内核,使平台中支持USB接口,再在需要使用摄像头采集时使用insmode命令动态加载其驱动模块(spca5xx.o)。具体步骤如下:
1、下载spca5xx一20060402.tar.gz解压到当前工作目录。
2、在/spcaSxx.20060402下修改Makefile如下:
(1)KINCLUDE=/arm/yc/linux/include注:/arm/yc/linux为linux源码存放目录
(2)#MODULE_INSTALLDIR=/Iib/modules/$(KERNEL-yERSION)/kernel/drivers/usb/
(3)CC=arm_linux_gcc
(4)LD=ILrlTI··linux··ld
(5)#CFLAGS+=-mpreferred—stack-boundary=2
3、主机上make编译生成可加载驱动文件spca5xx.0。
4、将spca5xx.o烧写到开发板/web目录下
5、在开发板上插入摄像头,终端输入#cd web,#insmod spca5xx.0加载驱动成功。
从驱动程序可以得到摄像头的信息:采用Vimicro Zc301图像处理芯片,JPEG图像压缩输出方式,感光端采用M1360,最大分辨率640X480。
6、查看/dev下出现/dev/v41/videoO,因为采集图象调用内核的V4L函数,需要建立一个连接/dev/vidoO,方法为#In.s/dev/v41/videoO/dev/videoO
7、/dev/videoO表示USB摄像头设备文件。应用程序打开设备文件,通过调用文件操
作函数即可读取视频图像数据

2016-11-21 22:38:53 Stephen_yu 阅读数 4450

http://blog.csdn.net/leo115/article/details/7331349


我要做一个s3c6410 的摄像头视频采集的项目,由于我接手的这套开发板,内核编译的使用可能没有配置摄像头头驱动程序,所以我尝试了好多时间安装摄像头驱动,因为是新手,所以吃的苦头比较多,在前期的诸多尝试之后,经一学长帮助,立马入门了。我这套板子原本自带了一个2X10插口的摄像头模块,是ov9650但是编译驱动的时候怎么都不成功,我实力不到!为了图快,我编译了内核自带的USB摄像头驱动,这个还是很快的,所有的arm板都支持!


http://eatdrinkmanwoman.spaces.live.com/blog/cns!97719476F5BAEDA4!1336.entry

http://weijb0606.blog.163.com/blog/static/131286274201063152423963/

本文所说的摄像头(Webcam),特指USB摄像头。 


  在Windows下,摄像头驱动由厂商开发并提供。但在Linux下,因商业利益有限,只有极少厂商愿意提供摄像头驱动支持。这并不妨碍Linux下摄像头的使用——广大第三方志愿者维护着大大小小的驱动。之前做过一个嵌入式皮毛项目,虽然摄像头驱动不关我的事,在好奇心驱使下简要探索了一番。结果是有些胸闷,它们像蜘蛛丝一样杂乱。写本文的动机是想理清一下主线,给后来者提供一点有限的参考,但无法保证下面的文字完全正确。 

  常见的摄像头驱动有以下几个系列: OVCam drivers(ov5xx) 

  该系列驱动是针对OmniVision OV5xx系列芯片,此类芯片被广泛运用于各种USB摄像头中,在嵌入式开发板上尤其常见。凡是使用了OV511/OV511+/OV518 /OV6620/OV6630/OV7610/OV7620/OV7 620AE等图像传感器的摄像头都可以在该驱动下工作。 

  项目主页:http://alpha.dyndns.org/ov511/ 

  Philips USB Webcam Driver(pwc) 该驱动主要用于Philips及pwc芯片兼容摄像头。由于原开发者与Philips公司签有保密协定,该驱动以二进制形式提供,后来被踢出内核,引起一场口水战:是用户需求重要还是保持内核纯洁重要?所幸后续开发者从原项目上创建了一个新的分支,使得该项目继续存活。 

  项目主页:http://www.saillard.org/linux/pwc/ 

  QuickCam USB camera driver (qc-usb) 该驱动主要用于Logitech公司的QuickCam Express系列和其它兼容型号摄像头。最初由Georg Acher开发,当时命名为qce-ga。Jean-Frederic Clere参考该驱动创建出了第一个Video4Linux兼容驱动。从那开始,不断有开发者加入,使得该驱动逐渐能够支持新的摄像头和芯片。在此期间,该驱动更名为qc-usb,更为广泛地支持其它QuickCam USB摄像头,而不再是局限于Express系列。 

  项目主页:http://qce-ga.sourceforge.NET

  QuickCam Messenger & Communicate driver(quickcam) 这是另一个针对QuickCam摄像头的驱动,它仅支持某一些型号,并且与qc-usb驱动不兼容。项目主页:http://home.mag.cx/messenger/ 

  SPCA webcam driver(gspca/spca5xx) 该系列驱动适用于Sunplus芯片摄像头,也适用于其它芯片,如目前国内山寨摄像头一哥“中星微”(Z-Star)芯片。Michel Xhaard是该项目的维护者,他在60岁左右的时候(大约是2003年)从一种普通常见的驱动入手,进而不断修改开发成一个支持250种以上摄像头的通用驱动。他还写了流媒体服务器spcaserv与客户端spcaview。不少高校所谓的嵌入式无线网络视频传输项目,就是从这两个东西来的。 

  项目主页:http://mxhaard.free.fr/spca5xx.html 

  Linux UVC driver(uvc) 该驱动适用于符合USB视频类(USB Video Class)规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。大多数大容量存储器设备(如优盘)都遵循USB规范,因而仅用一个单一驱动就可以操作它们。与此类似,UVC兼容外设只需要一个通用驱动即可。 

  USB摄像头大体上可以二分为UVC cameras和non-UVC cameras。推荐购买UVC cameras。UVC是一个开放的标准,拥有维护良好的驱动,它属于内核代码的一部分。插入摄像头后就可以工作,而无须编译或安装额外的驱动。non- UVC cameras通常情况下不比UVC cameras工作出色,前者的驱动并不遵循通用的协议,需要针对每种摄像头做出单独的处理,这往往需要一个逆向工程的探索过程。 

  判断一个摄像头是否属于UVC规范可以使用下面方法: 

  1.使用lsusb命令或其它硬件信息查看工具找出摄像头的设备号(Vendor ID)和产品号(Product ID)。如Logitech Quickcam for Notebooks Pro摄像头是046d:08cb; 

  2.查找是否有视频类接口信息 

  lsusb -d 046d:08cb -v | grep "14 Video" 

  如果该摄像头兼容UVC,则会输出类似信息 

  bFunctionClass 14 Video 

  bInterfaceClass 14 Video 

  bInterfaceClass 14 Video 

  bInterfaceClass 14 Video 

  若无以上信息,则是non-UVC设备。 

  项目主页:http://linux-uvc.berlios.de/ 

  在Linux下摄像头驱动有三种存在形式,内置于内核(within the kernel),做为一个外挂的模块(module),或者是预编译的二进制程序(pre-compiled binary)。 

  Linux内核树会不断合并优秀的驱动。从2.4内核起,ov5xx驱动就已经是内核代码的一部分。从2.6.26开始,Linux内核原生包含uvc 驱动。2.6.27内核又吸收进了gspca/spca5xx系列驱动。常见的Linux发行版所配置的内核,一般都已将这些驱动选项打开,而无需用户另外编译。内核的.config文件中有许多配置变量等式,用来说明内核配置的结果。y表示本编译选项对应的内核代码被静态编译进 Linux内核;m表示本编译选项对应的内核代码被编译成模块;n表示不选择此编译选项。 

  以Fedora12/boot下的config文件为例 

  cat /boot/config-2.6.31.12-174.2.3.fc12.i686 | grep CONFIG_USB_GSPCA 

  CONFIG_USB_GSPCA=m 

  CONFIG_USB_GSPCA_CONEX=m 

  CONFIG_USB_GSPCA_ETOMS=m 

  CONFIG_USB_GSPCA_FINEPIX=m 

  CONFIG_USB_GSPCA_MARS=m 

  CONFIG_USB_GSPCA_MR97310A=m 

  CONFIG_USB_GSPCA_OV519=m 

  CONFIG_USB_GSPCA_OV534=m 

  CONFIG_USB_GSPCA_PAC207=m 

  CONFIG_USB_GSPCA_PAC7311=m 

  CONFIG_USB_GSPCA_SN9C20X=m 

  CONFIG_USB_GSPCA_SN9C20X_EVDEV=y 

  CONFIG_USB_GSPCA_SONIXB=m 

  CONFIG_USB_GSPCA_SONIXJ=m 

  CONFIG_USB_GSPCA_SPCA500=m 

  CONFIG_USB_GSPCA_SPCA501=m 

  CONFIG_USB_GSPCA_SPCA505=m 

  CONFIG_USB_GSPCA_SPCA506=m 

  CONFIG_USB_GSPCA_SPCA508=m 

  CONFIG_USB_GSPCA_SPCA561=m 

  CONFIG_USB_GSPCA_SQ905=m 

  CONFIG_USB_GSPCA_SQ905C=m 

  CONFIG_USB_GSPCA_STK014=m 

  CONFIG_USB_GSPCA_SUNPLUS=m 

  CONFIG_USB_GSPCA_T613=m 

  CONFIG_USB_GSPCA_TV8532=m 

  CONFIG_USB_GSPCA_VC032X=m 

  CONFIG_USB_GSPCA_ZC3XX=m 

  可以看到gspca系列驱动被编译为模块。当插入摄像头后,使用dmesg命令可以打印出以下信息: 

  usb 1-1.2: new full speed USB device using ehci_hcd and address 6 

  usb 1-1.2: New USB device found, idVendor=046d, idProduct=08af 

  usb 1-1.2: New USB device strings: Mfr=0, Product=0, SerialNumber=0 

  usb 1-1.2: configuration #1 chosen from 1 choice 

  gspca: probing 046d:08af 

  zc3xx: probe 2wr ov vga 0x0000 

  zc3xx: probe sensor -> 0011 

  zc3xx: Find Sensor HV7131R(c) 

  gspca: probe ok 

  这说明该摄像头被识别,且自动挂载了gspca系列下的zc3xx驱动。 

  如果由于某种原因,已配置的内核中没有包括摄像头驱动,可以重新配置内核选项,用新编译的内核替换原有的旧内核。或者是依据设备号及产品号,直接到对应驱动的项目主页,下载源码进行编译。 

  最后总结一下 

  如果你想买一个摄像头,推荐买Logitech的,买符合UVC驱动的,可以到这里挑一款。 

  如果你已经有了一个摄像头,先插进去看看,Linux对它有没有反应。记录dmesg输出信息,记录lsusb输出设备号,以设备号为关键字上Google搜索。 

  参考资源 http://www.tldp.org/HOWTO/Webcam-HOWTO/ 

  http://www.chineselinuxuniversity.Net/courses/kern el/articles/19988.shtml 

  http://www.quickcamteam.net/


2017-12-10 11:33:59 qq_26093511 阅读数 9953

 

 

 

 

在 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* 查看设备节点

 

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

 

3. 在 windows 操作系统下插入 USB 摄像头插入,打开设备管理器

第一个 USB2.0 Camera 是我们接入的 USB 摄像头,第二个 USB2.0 UVCPC Camera 是笔记本自带的摄像头。

右键属性 -> 详细信息 –> 属性 选择硬件 ID 查看

可以得到插入的 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



得到图像, 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 --->

 

如图配置:



2. 选中 Multimedia support


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

 

如图配置:

 

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


如图配置


注:如果你不想编译成模块,可以把 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 官方淘宝店上摄像头的配置

 

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

 

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

 

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


#make menuconfig

 

1. 进入 USB support

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

 

如图配置:

 

2. 选中 Multimedia support

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

 

如图配置:

 

3. 再进入 Media USB Adapters


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

 

如图配置

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


4. 进入 GSPCA base webcams


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

 

如图配置

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

 

出现图像:

 

这样,我们的 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 所示:



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:

点击进去,在\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 架构的知识没有分析的太深,有机会以后再慢慢分析。

 

 

arm上打开摄像头

阅读数 4840