韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析

韦东山第3期嵌入式Linux项目-视频监控-1

硬 件 :电脑、 JZ2440 开发板、 UVC 摄像头(推荐使用) 系 统 : Ubuntu9.10// 即光盘提供的那个虚拟机系统
Uboot : u-boot-2012.04.01 // 即毕业班移植新 uboot 那个
Kernel : linux-3.4.2 // 即毕业班移植新内核那个文件系统:
fs_mini_mdev_new.tar.bz2
① UVC 指 USB Video Class, UVC 摄像头的简单判断标准就是接到 Windows 电脑上后,不用安装驱动
程序就可以使用
② USB 摄像头输出的数据有多种格式,比如原始数据 RGB 或 YUV 格式,压缩的 MJPEG 格式。使用
网络传输视频时,应该传输压缩格式的,否则传输的数据量会非常大。
S3C2440 的主频只有 400M,如果使用的摄像头不支持 MJPEG 输出,就需要使用软件(mjpg-streamer)
来压缩,这极大的耗费 CPU 资源,导致远程视频不流畅

1. 概念和整体框架介绍

视频监控的Linux底层驱动程序属于字符设备驱动,回顾字符设备驱动:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第1张图片
这种分层的结构除了可以让我们更加专注于硬件相关的代码外,还可以让上层应用程序有一套统一的调用接口。

分析v4l2 框架结构:

上图中所说的某结构体,指的是针对于某个具体的驱动的具体结构体,对于LCD来说是fb_info结构体,对于视频来说又是另外一套框架和结构体:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第2张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第3张图片

(1)这个uvc_driver 结构体就是uvc_driver.c 向核心层注册的结构体。一旦在id_table中发现可以支持的设备(USB摄像头),内核就调用probe函数进行下一步操作:

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第4张图片

(2)在uvc_probe函数中就会分配设置某个跟视频相关的结构体:

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第5张图片

(3)首先看到的是v4l2_device_register

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第6张图片

uvc_init -->uvc_driver --> uvc_probe
	uvc_probe
		v4l2_device_register  // 不重要
		uvc_register_chains
			uvc_register_terms
				uvc_register_video
					video_device_alloc
					video_register_device  //重要的注册函数
						__video_register_device 
						//最终调用到这个函数 (该函数位于:v4l-dev.c 这正是v4l2 框架的核心层)

其中,video_register_device 是重要的部分。

video_register_device 可以反推出哪个部分时核心层:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第7张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第8张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第9张图片

对于V4L2框架的细节部分,可以参看:linux-3.4.2_jz2440\Documentation\video4linux\v4l2-framework.txt

2. 源码分析:vivi.c (可以帮助我们快速分析清楚v4l2的框架)

通过分析 vivi.c(Virtual Video driver) 来深入理解视频驱动框架:
1.分配video_device
2.设置
3.注册:video_register_device

1)首先分析入口函数:

vivi_init
    vivi_create_instance
        v4l2_device_register   // 不是主要的, 只是用于初始化一些东西,比如自旋锁、引用计数,这里改为v4l2_device_init更为合适  

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第10张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第11张图片

        vfd=video_device_alloc:
        struct video_device *vfd;
        *vfd = vivi_template;

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第12张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第13张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第14张图片

        // 设置
          1. vfd:
            .fops           = &vivi_fops,
            .ioctl_ops 	= &vivi_ioctl_ops,
            .release	= video_device_release,
          2.
            vfd->v4l2_dev = &dev->v4l2_dev;       
			// v4l2_device_register(NULL, &dev->v4l2_dev); 
			//该函数中没有做什么实质性的事,只是用于初始化一些东西,比如自旋锁、引用计数,这里改为v4l2_device_init更为合适
			//因此这里的 struct v4l2_device v4l2_dev 不是最主要的

重要:3. 设置"ctrl属性"(用于APP的ioctl):
            	v4l2_ctrl_handler_init(hdl, 11);
            	dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
            	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
            	dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_CONTRAST, 0, 255, 1, 16);               
            			``````

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第15张图片
在这里插入图片描述

       video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
            __video_register_device
                vdev->cdev = cdev_alloc(); 
                vdev->cdev->ops = &v4l2_fops;
                cdev_add()
                
                video_device[vdev->minor] = vdev;  //以次设备号为下标,将vdev存放到数组中

        		if (vdev->ctrl_handler == NULL)        //设置ctrl_handler 结构体
        			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

2) 分析驱动程序的读写过程:

分析vivi.c的open,read,write,ioctl过程
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第16张图片

1. open
app:     open("/dev/video0",....)
---------------------------------------------------
drv:     v4l2_fops.v4l2_open   //vdev->cdev->ops = &v4l2_fops 中的open函数
            vdev = video_devdata(filp);  // 根据次设备号从数组中得到video_device  之后就可以使用video_device结构体所提供的各种函数了
                                         // video_register_device 函数中会将video设备放入数组中
            ret = vdev->fops->open(filp);
                        vivi_fops->open = v4l2_fh_open
                            

2. read
app:    read ....
---------------------------------------------------
drv:    v4l2_fops.v4l2_read  //vdev->cdev->ops = &v4l2_fops中的read函数
            struct video_device *vdev = video_devdata(filp);
            ret = vdev->fops->read(filp, buf, sz, off);      // vivi_fops -> v4l2_fh_open

3. ioctl //较为复杂
app:   ioctl
----------------------------------------------------
drv:   v4l2_fops.unlocked_ioctl =  v4l2_ioctl  //vdev->cdev->ops = &v4l2_fops中的ioctl函数
         实际上最终调用到:v4l2_ioctl
                struct video_device *vdev = video_devdata(filp);
                ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                            vivi.c:video_ioctl2
                                          video_usercopy(file, cmd, arg, __video_do_ioctl); // 从用户空间拷贝命令参数,然后调用__video_do_ioctl
                                          __video_do_ioctl :
                                          struct video_device *vfd = video_devdata(file); //同理都是先根据次设备号得到video_device结构体
                                          根据APP传入的cmd来获得、设置"某些属性"

总结:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第17张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第18张图片

3) v4l2_ctrl_handler的使用过程:

    __video_do_ioctl
        struct video_device *vfd = video_devdata(file);

    	case VIDIOC_QUERYCTRL:
    	{
    		struct v4l2_queryctrl *p = arg;
    
    		if (vfh && vfh->ctrl_handler)
    			ret = v4l2_queryctrl(vfh->ctrl_handler, p);
    		else if (vfd->ctrl_handler)  // 在哪设置?在video_register_device
    			ret = v4l2_queryctrl(vfd->ctrl_handler, p);
    			            // 根据ID在ctrl_handler里找到v4l2_ctrl,返回它的值

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第19张图片
之前在vivi.c中创建的Ctrl参数都有一个ID值:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第20张图片

3. 虚拟驱动vivi测试:

1) 测试USB摄像头

在这里插入图片描述
准备工作:安装xawtv
sudo apt-get install xawtv

源码xawtv-3.95.tar.gz: http://www.kraxel.org/releases/xawtv/

在这个网站创建新的sources.list
http://repogen.simplylinux.ch/

  1. 选择国家
  2. 选择相邻的ubuntu版本
  3. 选择"Ubuntu Branches"
  4. 生成sources.list
  5. 把得到内容替换到/etc/apt/sources.list
  6. sudo apt-get update
    sudo apt-get install xawtv

2)测试虚拟摄像头

① 确实ubuntu的内核版本
uname -a
Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux

② 去www.kernel.org下载同版本的内核
解压后把drivers/media/video目录取出后放到服务器上面去(Ubuntu9.10)

修改它的Makefile为:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第21张图片

KERN_DIR = /usr/src/linux-headers-2.6.31-14-generic

all:
        make -C $(KERN_DIR) M=`pwd` modules 

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order

obj-m   += vivi.o
obj-m   += videobuf-core.o
obj-m   += videobuf-vmalloc.o
obj-m   += v4l2-common.o

③ make (只加入obj-m += vivi.o时报错)

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第22张图片
解决方法:在内核中搜索这些函数所在的c文件并将这些文件编译成内核模块:

④ insmod videobuf-core.ko
insmod videobuf-vmalloc.ko
insmod v4l2-common.ko
insmod vivi.ko
⑤ ls /dev/video*
⑥ xawtv -c /dev/videoX

4. 根据虚拟驱动vivi彻底分析USB摄像头驱动:

在事先没有连接USB摄像头时,装载驱动程序会报错,是因为在连接了USB摄像头后,Linux会自动安装一些驱动程序。

解决方法:
在这里插入图片描述
下面分析xawtv 源码:
直接分析源码很麻烦,可以用strace工具来分析xawtv用到了哪些系统调用(open、read、write),就知道xawtv应用程序做了那些事:
在这里插入图片描述

//这部分代码是应用程序中读出来的,但是跟源码没有对应关系,说明应用程序跟源码还是有差别的
3. ioctl(4, VIDIOC_G_FMT
4. for()
        ioctl(4, VIDIOC_ENUM_FMT
5. ioctl(4, VIDIOC_QUERYCAP    // 列举性能
6. ioctl(4, VIDIOC_G_INPUT     // 获得当前使用输入源
7. ioctl(4, VIDIOC_ENUMIN PUT   // 列举输入源
8. ioctl(4, VIDIOC_QUERYCTRL   // 查询属性,比如亮度、对比度
9. ioctl(4, VIDIOC_QUERYCAP
10. ioctl(4, VIDIOC_ENUMINPUT

xawtv源码中对应的ioctl操作:

// 1~7都是在v4l2_open里调用
1. open
2. ioctl(4, VIDIOC_QUERYCAP   //open之后就开始查询性能,必不可少


// 3~7 都是在get_device_capabilities里调用
3. for()
        ioctl(4, VIDIOC_ENUMINPUT   // 列举输入源,VIDIOC_ENUMINPUT/VIDIOC_G_INPUT/VIDIOC_S_INPUT不是必需的
4. for()
        ioctl(4, VIDIOC_ENUMSTD  // 列举标准(制式), 不是必需的
5. for()        
        ioctl(4, VIDIOC_ENUM_FMT // 列举所支持的格式

6. ioctl(4, VIDIOC_G_PARM      //获得参数
7. for()
        ioctl(4, VIDIOC_QUERYCTRL    // 查询属性(比如说亮度值最小值、最大值、默认值)


// 8~10都是通过v4l2_read_attr(读取属性)来调用的        
8. ioctl(4, VIDIOC_G_STD            // 获得当前使用的标准(制式), 不是必需的
9. ioctl(4, VIDIOC_G_INPUT 
10. ioctl(4, VIDIOC_G_CTRL           // 获得当前属性, 比如亮度是多少

// 11~12在v4l2_overlay中调用,暂时没有用到该功能
11. ioctl(4, VIDIOC_TRY_FMT          // 试试能否支持某种格式
12. ioctl(4, VIDIOC_S_FMT            // 如果支持的话就设置摄像头使用某种格式


// 13~16在v4l2_start_streaming
13. ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
14. for()
        ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
        mmap         //调用mmap来映射各个缓冲区的地址
                           //之后将所有的buf都放入队列中:v4l2_queue_all 
15. for ()
        ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列    
    
16. ioctl(4, VIDIOC_STREAMON             // 启动摄像头


// 17里都是通过v4l2_write_attr来调用的
17. for ()
        ioctl(4, VIDIOC_S_CTRL           // 设置属性
    ioctl(4, VIDIOC_S_INPUT              // 设置输入源
    ioctl(4, VIDIOC_S_STD                // 设置标准(制式), 不是必需的


// v4l2_nextframe > v4l2_waiton    
18. v4l2_queue_all
    v4l2_waiton    
        for ()
        {
            select(5, [4], NULL, NULL, {5, 0})      = 1 (in [4], left {4, 985979})   //查询是否有数据
            //如果一旦驱动程序有了数据,它就会将应用程序唤醒,唤醒之后应用程序调用DQBUF把数据取出获取buf的信息 

            ioctl(4, VIDIOC_DQBUF                // de-queue, 一旦有数据就把缓冲区从队列中取出

            // 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据 
       
            ioctl(4, VIDIOC_QBUF                 // 把缓冲区放入队列
        }

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第23张图片
总结:
(1)xawtv的几大函数:

  1. v4l2_open
    打开摄像头设备,获取其性能参数

  2. v4l2_read_attr/v4l2_write_attr

  3. v4l2_start_streaming
    其中会请求buf并mmap buf

  4. v4l2_nextframe --> v4l2_waiton
    v4l2_waiton 函数来等待查询摄像头数据。

接下来要做的是将众多的ioctl进行精简,留下最少的部分完成摄像头应用程序的基本功能:修改vivi.c
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第24张图片
应用程序 --> ioctl --> video_ioctl2 (根据ioctl不同的CMD)–> v4l2_ioctl_ops 中的对应函数

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第25张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第26张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第27张图片

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第28张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第29张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第30张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第31张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第32张图片
在这里插入图片描述
经过分析后选出必不可少的ioctl函数:

摄像头驱动程序必需的11个ioctl:

    // 表示它是一个摄像头设备
	.vidioc_querycap      = vidioc_querycap,

    /* 用于列举、获得、测试、设置摄像头的数据的格式 */
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,

    /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
	.vidioc_reqbufs       = vidioc_reqbufs,
	.vidioc_querybuf      = vidioc_querybuf,
	.vidioc_qbuf          = vidioc_qbuf,
	.vidioc_dqbuf         = vidioc_dqbuf,

	// 启动/停止
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,	

继续分析数据的获取过程:

  1. 请求分配缓冲区:(应用程序调用)
ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
     videobuf_reqbufs(队列, v4l2_requestbuffers) // 队列在open函数用videobuf_queue_vmalloc_init初始化
                                               // 注意:这个IOCTL只是分配缓冲区的头部信息,真正的缓存还没有分配呢
  1. 查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
        videobuf_querybuf        // 获得缓冲区的数据格式、大小、每一行长度、高度(此时还未分配缓存)            
mmap(参数里有"大小")   // 在这里才分配缓存
        v4l2_mmap
            vivi_mmap
                videobuf_mmap_mapper
                    videobuf-vmalloc.c里的__videobuf_mmap_mapper
                            mem->vmalloc = vmalloc_user(pages);   // 在这里才给缓冲区分配空间

  1. 把缓冲区放入队列:
ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列        
    videobuf_qbuf
        q->ops->buf_prepare(q, buf, field);  // 调用驱动程序提供的函数做些预处理
        list_add_tail(&buf->stream, &q->stream);  // 把缓冲区放入队列的尾部
        q->ops->buf_queue(q, buf);           // 调用驱动程序提供的"入队列函数"

  1. 启动摄像头
ioctl(4, VIDIOC_STREAMON
    videobuf_streamon
        q->streaming = 1;

  1. 用select查询是否有数据
          // 驱动程序里必定有: 产生数据、唤醒进程
          v4l2_poll
                vdev->fops->poll
                    vivi_poll   
                        videobuf_poll_stream
                            // 从队列的头部获得缓冲区
                			buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
                            
                            // 如果没有数据则休眠                			
                			poll_wait(file, &buf->done, wait);

    谁来产生数据、谁来唤醒它?
    内核线程vivi_thread每30MS执行一次,它调用
    vivi_thread_tick  //产生数据
        vivi_fillbuff(fh, buf);  // 构造数据 
        wake_up(&buf->vb.done);  // 唤醒进程

注意:真实的摄像头是硬件摄像头产生数据,而vivi虚拟摄像头是由一个内核线程来产生数据:

  1. 有数据后从队列里取出缓冲区
// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF 
    vidioc_dqbuf   
        // 在队列里获得有数据的缓冲区
        retval = stream_next_buffer(q, &buf, nonblocking);
        
        // 把它从队列中删掉
        list_del(&buf->stream);
        
        // 把这个缓冲区的状态返回给APP
        videobuf_status(q, b, buf, q->type);

  1. 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据
    就去读对应的地址(该地址来自前面的mmap)

总结:
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第33张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第34张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第35张图片
怎么写摄像头驱动程序:

  1. 分配video_device:video_device_alloc
  2. 设置
    .fops
    .ioctl_ops (里面需要设置11项)
    如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
  3. 注册: video_register_device

5. USB摄像头驱动框架:

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第36张图片
UVC: USB Video Class
UVC驱动:drivers\media\video\uvc\

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第37张图片
id_table 表示能够支持的所有USB设备,probe函数是当接入所能支持的设备后调用的函数,在probe函数中就会像上述描述的那样进行有关视频的设置。

uvc_driver.c分析:

1. usb_register(&uvc_driver.driver);
2. uvc_probe
        uvc_register_video
            vdev = video_device_alloc();
            vdev->fops = &uvc_fops;
            video_register_device

在www.usb.org下载 uvc specification,
UVC 1.5 Class specification.pdf : 有详细描述
USB_Video_Example 1.5.pdf : 有示例

通过VideoControl Interface来控制(比如亮度、白平衡等参数的控制)
通过VideoStreaming Interface来读视频数据,

在这里插入图片描述
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第38张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第39张图片
韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第40张图片
VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度

分析UVC驱动的调用过程:

const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.ioctl		= uvc_v4l2_ioctl,
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
};
  1. open: 当应用程序调用open函数的时候就会调用到uvc_fops 结构体中的uvc_v4l2_open函数
    uvc_v4l2_open //其中基本上是一些状态的设置

之后一个一个来跟踪ioctl函数的操作过程:当应用程序调用到ioctl函数时就会调用到uvc_fops 结构体中的uvc_v4l2_ioctl函数:
(整个过程跟xawtv打开vivi虚拟摄像头的原理类似)

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析_第41张图片
uvc_v4l2_ioctl函数中的video_usercopy函数就是将应用程序传来的cmd参数拷贝至内核态然后调用uvc_v4l2_do_ioctl函数。
uvc_v4l2_do_ioctl函数中有一系列的ioctl的调用。

2. VIDIOC_QUERYCAP   // video->streaming->type 应该是在设备被枚举时分析描述符时设置的
		if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
					  | V4L2_CAP_STREAMING;
		else
			cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
					  | V4L2_CAP_STREAMING;
3. VIDIOC_ENUM_FMT // format数组应是在设备被枚举时设置的
        format = &video->streaming->format[fmt->index];
        
4. VIDIOC_G_FMT
        uvc_v4l2_get_format  // USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)
            	struct uvc_format *format = video->streaming->cur_format;
            	struct uvc_frame *frame = video->streaming->cur_frame;
            	
5. VIDIOC_TRY_FMT
        uvc_v4l2_try_format
            /* Check if the hardware supports the requested format. */

        	/* Find the closest image size. The distance between image sizes is
        	 * the size in pixels of the non-overlapping regions between the
        	 * requested size and the frame-specified size.
        	 */
        	 
6. VIDIOC_S_FMT  // 只是把参数保存起来,还没有发给USB摄像头
        uvc_v4l2_set_format
            uvc_v4l2_try_format
        	video->streaming->cur_format = format;
        	video->streaming->cur_frame = frame;
        	
7. VIDIOC_REQBUFS  //请求缓冲区
        uvc_alloc_buffers
           	for (; nbuffers > 0; --nbuffers) {
        		mem = vmalloc_32(nbuffers * bufsize); 
        		if (mem != NULL)
        			break;
        	}
        	
8. VIDIOC_QUERYBUF  //查询buf参数
        uvc_query_buffer
            __uvc_query_buffer
                memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);  // 复制参数
                
9. mmap
        uvc_v4l2_mmap
            
10. VIDIOC_QBUF  //数据入队列
        uvc_queue_buffer
        	list_add_tail(&buf->stream, &queue->mainqueue);
        	list_add_tail(&buf->queue, &queue->irqqueue);

11. VIDIOC_STREAMON
        uvc_video_enable(video, 1)  // 把所设置的参数发给硬件,然后启动摄像头
        
            /* Commit the streaming parameters. */  把参数提交给硬件设备
            uvc_commit_video
                uvc_set_video_ctrl  /* 设置格式fromat, frame */
                    	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);
                    
            /* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
            uvc_init_video(video, GFP_KERNEL);
                    uvc_init_video_isoc / uvc_init_video_bulk
                        urb->complete = uvc_video_complete; (收到数据后此函数被调用,它又调用video->decode(urb, video, buf); ==> uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)
                        
                    usb_submit_urb 
// 当应用程序将一个buf放入队列之后,调用streamon 来启动数据传输,在streamon 中做了一些初始化。
之后当应用程序调用poll函数来查询是否数据已经就绪,这时就会有poll_wait 在这里休眠等待数据,
当USB驱动程序获得了数据之后,每个USB请求块(urb)完成之后,其uvc_video_complete 函数被调用,
这个complete函数最终就会调用到wake_up 函数将应用程序唤醒。
                   	
12. poll     //poll函数返回以后,数据就可以使用了,接下来就是从队列中取出数据
        uvc_v4l2_poll            
            uvc_queue_poll
                poll_wait(file, &buf->wait, wait);  // 休眠等待有数据

13. VIDIOC_DQBUF
        uvc_dequeue_buffer
        	list_del(&buf->stream);

14. VIDIOC_STREAMOFF            
        uvc_video_enable(video, 0);
    		usb_kill_urb(urb);
    		usb_free_urb(urb);

分析设置亮度过程:
ioctl: VIDIOC_S_CTRL
uvc_ctrl_set
uvc_ctrl_commit
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
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);

总结:

  1. UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface

  2. VideoControl Interface用于控制,比如设置亮度。它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
    可以通过类似的函数来访问:
    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);

  3. VideoStreaming Interface用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format, 一个format支持多种frame, frame用来表示分辨率等信息)
    可以通过类似的函数来访问:
    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);

  4. 我们在设置FORMAT时只是简单的使用video->streaming->format[fmt->index]等数据,
    这些数据哪来的?
    应是设备被枚举时设置的,也就是分析它的描述符时设置的。

  5. UVC驱动的重点在于:
    描述符的分析
    属性的控制: 通过VideoControl Interface来设置
    格式的选择:通过VideoStreaming Interface来设置
    数据的获得:通过VideoStreaming Interface的URB来获得

你可能感兴趣的:(嵌入式Linux)