作者:甘老师,华清远见嵌入式学院讲师。

一、从软件层面上来跟踪摄像头应用程序所涉及的系统调用

首先可以分析虚拟摄像头驱动vivi.c所涉及的系统调用

测试虚拟摄像头vivi:

1. 确定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

2. 去www.kernel.org下载同版本的内核
        解压后把drivers/media/video目录取出
        修改它的Makefile为:

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一下
        接下来轮到安装虚拟摄像头驱动了,因为安装vivi.ko的时候要涉及到很多的依赖,也就是要先加载其他的一些模块,才能正常的加载vivi.ko

在这里我们执行下面的命令
        sudo modprobe vivi
        sudo rmmod vivi
        sudo insmod ./vivi.ko

这个时候在/dev下生成对应的设备文件,假如现在的设备文件是/dev/video0

执行应用程序
        xawtv -c /dev/video0
        (其中, xawtv 是一个应用程序.自行下载安装,在执行上面的指令来启动应用程序之前,首先执行下面的命令,他通过strace来获得应用程序调用的系统调用)

strace -o xawtv.log xawtv (将xawtv所涉及的系统调用记录在xawtv.log文件)

通过分析xawtv.log文件

1. open
         2. ioctl(4, VIDIOC_QUERYCAP
         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. ioctl(4, VIDIOC_G_STD // 获得当前使用的标准(制式)
         9. ioctl(4, VIDIOC_G_INPUT 
         10. ioctl(4, VIDIOC_G_CTRL // 获得当前属性, 比如亮度是多少
         11. ioctl(4, VIDIOC_TRY_FMT // 试试能否支持某种格式
         12. ioctl(4, VIDIOC_S_FMT // 设置摄像头使用某种格式
         13. ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
         14. for()
                 ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
                 mmap 
         15. for ()
                 ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列 
         16. ioctl(4, VIDIOC_STREAMON // 启动摄像头
         17. for ()
                 ioctl(4, VIDIOC_S_CTRL // 设置属性
                 ioctl(4, VIDIOC_S_INPUT // 设置输入源
                 ioctl(4, VIDIOC_S_STD // 设置标准(制式), 
         18. v4l2_queue_all
                 v4l2_waiton 
                         for ()
                         {
                                 select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {4, 985979})
                                 ioctl(4, VIDIOC_DQBUF // de-queue, 把缓冲区从队列中取出
                                 // 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据 
                                 ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
                         }

有如下这么多ioctl(一部分重要的,也是最基本的ioctl)

zabbix 摄像头码率 摄像头码率对照表_系统调用

发现摄像头涉及好多的ioctl。
        上面的ioctl将对应到下面驱动中对应的函数。

二、分析驱动层面上ioctl和v4l2子设备

请结合源码sun5i_drv_csi.c 和gc0308.c

在sun5i_drv_csi.c中我们首先要构建vedio_decice两个重要的ops结构体
        1、 设置vedio_decice 结构体中两个重要的ops结构体

其中v4l2_file_operations 是文件操作结构体。当上层调用 open read write poll等系统调用的时候,将最终调用到这个结构体中函数指针所对应的函数csi_open csi_read csi_write等。

在这里的ioctl 设置为 video_ioctl2 后,内核中其实有一个类似方法的重写。意思就是说当发现vedio_device结构体中没有注册自己去实现的csi_ioctl_ops结构体时,将调用内核自带的ioctl_ops默认结构体。如下所示,定义了一个v4l2_ioctl_ops csi_ioctl_ops 结构体。然后将其注册到了vedio_device csi_template中。

zabbix 摄像头码率 摄像头码率对照表_ide_02

设置两个重要的ops到vedio_device结构体

zabbix 摄像头码率 摄像头码率对照表_应用程序_03

对于比较复杂的驱动程序,一般都要采用分层的思想,摄像头驱动程序就是这样一类比较复杂的驱动程序,内核已经写好了核心层,核心代码为v4l2-dev.c ,所以我们要做的仅仅是下面的内容,就会让应用程序操作最终对应到驱动中的操作。

给veido_device结构体分配空间

zabbix 摄像头码率 摄像头码率对照表_系统调用_04

设置vedio_device结构体

zabbix 摄像头码率 摄像头码率对照表_ide_05

注册vedio_device结构体到内核中

zabbix 摄像头码率 摄像头码率对照表_应用程序_06

那现在还有两个疑问,ioctrl到底是怎么样的一个顺序来控制的呢,那又是怎么设置到gc0308的寄存器中的呢?

首先来回答第二个问题:

注册v4l2子设备

zabbix 摄像头码率 摄像头码率对照表_系统调用_07

通过宏v4l2_subdev_call来调用gc0308中的函数

zabbix 摄像头码率 摄像头码率对照表_zabbix 摄像头码率_08

zabbix 摄像头码率 摄像头码率对照表_zabbix 摄像头码率_09

来看看这个宏吧!

#define v4l2_subdev_call(sd, o, f, args...) \
                 (!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ? \
                         (sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD))

联系到gc0308

zabbix 摄像头码率 摄像头码率对照表_ide_10

这样就可以通过调用i2c_transfer将数据发出去,这是通过iic控制器来控制来操作的

关于具体gc0308的内部寄存器请自己理解吧?

ioctrl到底是怎么样的一个顺序来控制的呢

zabbix 摄像头码率 摄像头码率对照表_zabbix 摄像头码率_11

附录一 :

zabbix 摄像头码率 摄像头码率对照表_ide_12

附录二

zabbix 摄像头码率 摄像头码率对照表_应用程序_13