前言
先前已经分析过V4L2的框架,并且使用了xawtv测试了vivi虚拟驱动程序,对框架的分析并没有深入结构,比如说ioctl有很多,但是并不清楚哪些是必须的,哪些可以不要。并且也不知道测试程序是如何拿到视频数据的。本文将介绍如何根据虚拟驱动vivi的使用过程彻底分析摄像头驱动。
第1章vivi测试补充
1.1加载vivi.ko失败现象
加载vivi.ko驱动之前,先加载
videobuf-core.ko
videobuf-vmalloc.ko
v4l2-common.ko
这3个模块,然后就可以加载vivi.ko成功了。然而换了一台电脑,再执行一遍就不行了,在加载v4l2-common.ko时执行“sudo insmod v4l2-common.ko”打印信息提示如下:Insmod:error inserting ‘v4l2-common.ko’:-1 Unknown symbol in module
使用dmesg命令查看详细信息:
v4l2_common:Unknown symbol v4l2_device_register_subdev
v4l2_common:Unknown symbol v4l2_device_unregister_subdev
那么为什么原来的电脑可以正常加载驱动,现在的电脑就不行了呢?
1.2 加载vivi.ko失败原因
这是因为先前电脑测试时是先插上的UVC摄像头测试的,这时电脑会自动加载对应的v4l2相关的驱动ko,这个时候再加载vivi.ko驱动的时候就不缺少相应的symbol。而换了一台电脑没有插uvc摄像头驱动,所以问题就出现了。
解决办法----依次执行下面三步骤:
sudo modprobe vivi //安装ubuntu自带的驱动程序
sudo rmmod vivi //卸载内核自带的vivi驱动
sudo insmod vivi.ko //安装自己编译出的vivi.ko
1.3 insmod与modprobe
insmod 和modprobe 都是载入kernel module,不过一般差别于modprobe 能够处理module 载入的相依问题。比方你要载入  a module,不过  a module 需求系统先载入  b module 时,直接用insmod 挂入通常都会出现错误讯息,不过modprobe 倒是能够知道先载入b module  后才载入a module,如此相依性就会满足。不过modprobe 并不是大神,不会厉害到知道module 之间的相依性为何,该程式是读取/lib/modules//modules.dep 档案得知相依性的。而该档案是透过depmod 程式所建立。
第2章分析xawtv源码
2.1 获取xawtv源码
要想分析详细的使用过程,我们需要得到应用程序xawtv的源码,可以从下面网站获得:/releases/xawtv/
下载源码后建立一个sourceinsight工程,当然可以从main开始分析,看看涉及到哪些系统调用。但是这样会非常复杂,因为xawtv除了调用vivi驱动程序之外,还有其他很多的工作,如何快捷的知道有哪些调用呢?
有一个办法,使用strace工具可以看出xawtv使用了哪些系统调用。
2.2 strace工具
使用方法:strace –o xxx.log cmd
cmd为命令,执行cmd命令时会将涉及到的系统调用(open,read,write)都会记录到到xxx.log中。
执行”strace –o xawtv.log xawtv”,在当前目录生成xawtv.log,将该文件拷到PC机上,下面对该文件进行分析。
2.3 分析xawtv.log
2.3.1 提取xawtv.log中的系统调用信息
使用UltraEdit打开xawt.log,xawtv不指定设备打开时是默认打开video0,因此上节操作的是video0,因为文件内容比较多,我们需要先得到我们关系的东西,搜索“/dev/video0”,得到如下图:
可以看出open打开两次,第一次打开后发现ioctl用不了,因此close,然后打开第2次。可以看出open之后,得到一个文件句柄“4”,后面接了一大堆ioctl。搜索“(4,”,就可以看出涉及到哪些调用了。勾选搜索对话框的“列出包含字符串的行”,搜索如下图:
复制搜索到的结果,存为新的文件“xawtv涉及的vivi驱动的系统调用.txt”。
下面根据这个文本文件把里面涉及的ioctl等等系统调用全部分析一遍。实验使用的ubuntu的内核版本是2.6.31.14,需要结合这版本内核的vivi.c来分析,先看一下它所涉及的系统调用。
从xawtv.log可以看出,open之后,涉及到的第一个系统调用是:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0x8f0b998) = -1 EINV AL (Invalid argument)
close(4)
ioctl失败,然后关闭。
我们重点关注第2次open后面的内容,如下:
详细如下:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbf90a2e4) = 0
ioctl(4, VIDIOC_G_FMT or VT_SENDSIG, 0xbf90a218) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, 0xc02c564a, 0xbf90a0f8) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, 0xc02c564a, 0xbf90a0f8) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, 0xc02c564a, 0xbf90a0f8) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbf90a18c) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbf90a124) = 0
ioctl(4, VIDIOC_G_INPUT, 0xbf909fcc) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0xbf909fcc) = 0
fstat64(4, {st_mode=S_IFCHR|0660, st_rdev=makedev(81, 0), ...}) = 0
ioctl(4, MA TROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL, 0xbf90a018) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0x8f0b998) = 0
fcntl64(4, F_SETFD, FD_CLOEXEC) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0x8f0bacc) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0x8f0bb18) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0x8f0bb64) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0x8f0bbb0) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0x8f0bbfc) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_ENUMSTD, 0x8f0bf8c) = 0
水调歌头原文ioctl(4, VIDIOC_ENUMSTD, 0x8f0bfcc) = 0
ioctl(4, VIDIOC_ENUMSTD, 0x8f0c00c) = 0
ioctl(4, VIDIOC_ENUMSTD, 0x8f0c04c) = 0
ioctl(4, VIDIOC_ENUMSTD, 0x8f0c08c) = 0
ioctl(4, VIDIOC_ENUMSTD, 0x8f0c0cc) = 0
waterioctl(4, VIDIOC_ENUMSTD, 0x8f0c10c) = 0
ioctl(4, VIDIOC_ENUMSTD, 0x8f0c14c) = -1 EINV AL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0x8f0c38c) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0x8f0c3cc) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0x8f0c40c) = 0
ioctl(4, VIDIOC_G_PARM, 0x8f0ba00) = 0
ioctl(4, MATROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL, 0x8f0cb8c) = 0
ioctl(4, MATROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL, 0x8f0cbd0) = 0
ioctl(4, MATROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL, 0x8f0cc14) = 0
ioctl(4, VIDIOC_G_STD, 0xbf90a3a8) = 0
ioctl(4, VIDIOC_G_INPUT, 0xbf90a3bc) = 0
cba大牌外援ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL, 0xbf90a3b4) = 0
ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL, 0xbf90a3b4) = 0
ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL, 0xbf90a3b4) = 0
ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL, 0xbf90a3b4) = 0
ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL, 0xbf90a3c4) = 0
ioctl(4, VIDIOC_TRY_FMT, 0x8f0dca4) = 0
ioctl(4, VIDIOC_S_FMT or VT_RELDISP, 0xbf909cf4) = 0
ioctl(4, VIDIOC_TRY_FMT, 0x8f0dca4) = 0
ioctl(4, VIDIOC_REQBUFS or VT_DISALLOCA TE, 0x8f0dd80) = 0
ioctl(4, VIDIOC_QUERYBUF or VT_RESIZE, 0x8f0dd94) = 0
ioctl(4, VIDIOC_QUERYBUF or VT_RESIZE, 0x8f0ddd8) = 0
ioctl(4, VIDIOC_QBUF, 0x8f0dd94) = 0
ioctl(4, VIDIOC_QBUF, 0x8f0ddd8) = 0
ioctl(4, VIDIOC_STREAMON, 0xbf909fcc) = 0
ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL, 0xbf90a3b0) = 0
低值易耗品摊销ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL, 0xbf90a3b0) = 0
ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL, 0xbf90a3b0) = 0
ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL, 0xbf90a3b0) = 0
ioctl(4, VIDIOC_S_INPUT, 0xbf90a424) = 0
ioctl(4, VIDIOC_S_STD, 0x8f0bf90) = 0
ioctl(4, VIDIOC_DQBUF, 0xbf90a2a0) = 0
ioctl(4, VIDIOC_QBUF, 0x8f0dd94) = 0
ioctl(4, VIDIOC_DQBUF, 0xbf90a2a0) = 0
ioctl(4, VIDIOC_QBUF, 0x8f0ddd8) = 0
ioctl(4, VIDIOC_DQBUF, 0xbf90a2a0) = 0
…..
2.3.2涉及的系统调用
由上小节可得系统调用顺序:
1、open("/dev/video0", O_RDWR|O_LARGEFILE) = 4-------打开设备
2、ioctl(4, VIDIOC_QUERYCAP
3、ioctl(4, VIDIOC_G_FMT-----获得摄像头数据的格式
4、for() ---从上图可以看出连续多次调用,从下面分析xawtv源码也可以看出。
ioctl(4, VIDIOC_ENUM_FMT-----列举出摄像头支持的格式
5、ioctl(4, 0xc02c564a, 0xbf90a0f8) = -1 EINV AL (Invalid argument)-----Invalid先不管
6、ioctl(4, VIDIOC_QUERYCAP
7、ioctl(4, VIDIOC_G_INPUT-----------获得当前使用的输入源
8、ioctl(4, VIDIOC_ENUMINPUT------列举出输入源,数据可能有多个通道
9、ioctl(4, VIDIOC_QUERYCTRL-------查询属性,比如亮度,对比度
10、ioctl(4, VIDIOC_QUERYCAP
11、fcntl64(4, F_SETFD, FD_CLOEXEC)-----当应用程序关闭,并且不是直接调用close
来关闭这个文件的话,系统帮忙来关闭
亚克力水族箱12、for()
ioctl(4, VIDIOC_ENUMINPUT------列举出输入源,数据可能有多个通道
13、for()
ioctl(4, VIDIOC_ENUMSTD----------列举标准制式
14、for()
ioctl(4, VIDIOC_ENUM_FMT---------列举支持的格式
15、ioctl(4, VIDIOC_G_PARM---------获取参数
16、for()
ioctl(4, VIDIOC_QUERYCTRL -----查询属性,比如亮度,最小值,最大值,默认值
17、ioctl(4, VIDIOC_G_STD------获得当前使用的标准制式,摄像头所支持的制式是PAL,还是NTSC等
18、ioctl(4, VIDIOC_G_INPUT----当前使用的哪一个通道
19、for()
ioctl(4, VIDIOC_G_CTRL------获得当前的属性,比如亮度,对比度
20、ioctl(4, VIDIOC_TRY_FMT-----试试能否支持某种格式,比如RGB,YUV
21、ioctl(4, VIDIOC_S_FMT-----设置摄像头使用某种格式(尝试成功后)
22、ioctl(4, VIDIOC_REQBUFS-----请求系统分配缓冲区
23、ioctl(4, VIDIOC_QUERYBUF----查询所分配的缓冲区
24、for()
ioctl(4, VIDIOC_QBUF-----把缓冲区放入队列
25、ioctl(4, VIDIOC_STREAMON------启动摄像头
26、for()
ioctl(4, VIDIOC_S_CTRL------设置属性,比如亮度
27、ioctl(4, VIDIOC_S_INPUT---------设置输入源
28、ioctl(4, VIDIOC_S_STD------------设置标准(制式)
29、
for()
{
select(5, [4], NULL, NULL, {5, 0}) //查询是否有数据
ioctl(4, VIDIOC_DQBUF-----------de-queque,把缓冲区从队列中取出
/*处理*/
ioctl(4, VIDIOC_QBUF-------------处理完后把缓冲区放入队列
}
VIDIOC_ENUM_FMT有多次使用,在sourcesight里面搜索下xawtv源码,发现在drv0-v4l2.c中有用到VIDIOC_ENUM_FMT,是一个for循环。
for (h->nfmts = 0; h->nfmts < MAX_FORMA T; h->nfmts++) {
h->fmt[h->nfmts].index = h->nfmts;
h->fmt[h->nfmts].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(h->fd, VIDIOC_ENUM_FMT, &h->fmt[h->nfmts], EINVAL))
break;
卤牛肉的家常做法}