摄像头代码浅析

作者:甘老师

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

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

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

131704453.jpg

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

131644484.jpg

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

给veido_device结构体分配空间

131722271.jpg

设置vedio_device结构体

131736374.jpg

注册vedio_device结构体到内核中

131752359.jpg

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

首先来回答第二个问题:

注册v4l2子设备

131807396.jpg

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

131850658.jpg

131906815.jpg

来看看这个宏吧!

#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

131923297.jpg

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

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

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

131937986.jpg

附录一 :

131957600.jpg

附录二


132155859.jpg


你可能感兴趣的:(嵌入式博文,摄像头应用程序,摄像头代码)