当前位置: > 华清远见教育科技集团 > 嵌入式学习 > 讲师博文 > 摄像头代码浅析
摄像头代码浅析
时间:2016-12-14作者:华清远见

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

首先可以分析虚拟摄像头驱动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)

发现摄像头涉及好多的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中。

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

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

给veido_device结构体分配空间

设置vedio_device结构体

注册vedio_device结构体到内核中

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

首先来回答第二个问题:

注册v4l2子设备

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

来看看这个宏吧!

#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

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

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

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

附录一 :

附录二

发表评论
评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)