一、xawtv所涉及的vivi驱动的系统调用
在Ubuntu系统中,当我们接上usb摄像头设备时,系统会自动给我们安装对应的usb设备驱动程序。
我们现在要使用自己编译的vivi驱动,该怎么办呢?
1.先安装系统自带的vivi驱动和它所依赖的所有驱动:sudo modprobe vivi ;
2.卸载原有的vivi驱动 : sudo rmmod vivi ;
3.装载自己的驱动 :sudo insmod ./vivi.ko ;
然后 ls /dev/video* ,可以看到有一个video设备节点 /dev/video0 ,即对应的是vivi虚拟出来的视频设备。
我们可以直接阅读xawtv源码,从main函数开始一路分析它调用vivi驱动的过程,但是这个过程会非常漫长,因为它除了调用vivi驱动之外
,还会做许多其他的准备工作。我们可以通过strace 这个命令来跟踪调用过程。
使用方法 :执行 strace -o xawtv.txt xawtv ,生成了调用过程xawtv.txt。
搜索 /dev/video0,得到如下:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0x95b8998) = -1 EINVAL (Invalid argument)
close(4) = 0
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
.....
发现打开了两次,open成功之后得到file_fd =4,后面有一大堆ioctl,把所有的ioctl列举出来,即可得到ioctl的过程:
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c704) = 0
ioctl(4, VIDIOC_G_FMT or VT_SENDSIG, 0xbff6c638) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518) = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518) = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, 0xc02c564a, 0xbff6c518) = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0
ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = -1 EINVAL (Invalid argument)
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c544) = 0
ioctl(4, VIDIOC_G_INPUT, 0xbff6c3ec) = 0
ioctl(4, VIDIOC_ENUMINPUT, 0xbff6c3ec) = 0
。。。。
xawtv涉及的vivi驱动的系统调用:
1.open
2.ioctl(4, VIDIOC_QUERYCAP 列举性能
3.ioctl(4, VIDIOC_G_FMT
4.ioctl(4, VIDIOC_ENUM_FMT 枚举出所支持的格式,可以看出这里支持多种格式
5.ioctl(4, VIDIOC_QUERYCAP
6.ioctl(4, VIDIOC_G_INPUT 获得当前的输入源
7.ioctl(4, VIDIOC_ENUMINPUT 枚举输入源,可能有多个输入源
8.ioctl(4, VIDIOC_QUERYCTRL 查询属性,比如亮度、对比度等等
9.ioctl(4, VIDIOC_QUERYCAP
10.ioctl(4, VIDIOC_ENUMINPUT
11.ioctl(4, VIDIOC_ENUMSTD 列举制式
12.ioctl(4, VIDIOC_ENUM_FMT 列举格式
13.ioctl(4, VIDIOC_G_PARM 获得参数
14.ioctl(4, MATROXFB_TVOQUERYCTRL or VIDIOC_QUERYCTRL 查询属性
15.ioctl(4, VIDIOC_G_STD 获得当前使用的制式
16.ioctl(4, VIDIOC_G_INPUT 当前输入
17.ioctl(4, MATROXFB_G_TVOCTRL or VIDIOC_G_CTRL 获得属性
18.ioctl(4, VIDIOC_TRY_FMT 尝试某种格式
19.ioctl(4, VIDIOC_S_FMT 尝试成功就使用某种格式
20.ioctl(4, VIDIOC_REQBUFS 请求系统分配缓冲区域
for(i=0;i<count;i++)
{
21.ioctl(4, VIDIOC_QUERYBUF 查询分配的缓冲区
mmap 映射地址
22.ioctl(4, VIDIOC_QBUF 放入队列
}
23.ioctl(4, VIDIOC_STREAMON 启动摄像头
24.ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL 设置属性 亮度等等
25.ioctl(4, VIDIOC_S_INPUT 设置输入源
26.ioctl(4, VIDIOC_S_STD 设置制式
for() //大循环处理数据
{
27.select(5, [4], NULL, NULL, {5, 0}) 采用阻塞方式查询有没有数据,在驱动程序会用到poll
28.ioctl(4, VIDIOC_DQBUF 把缓冲区从队列里取出
29.ioctl(4, VIDIOC_QBUF 放入队列
}
........
从以上分析可以看出,xawtv设计的vivi驱动的系统调用实在是太多了,可以删掉一些不必要的系统调用,找到最精简的版本。
二、vivi驱动必不可少的几个ioctl
在linux 2.6.31.14 版本的vivi.c 中,当应用程序调用v4l2框架里的ioctl时,最终调用到vivi.c里的vivi_ioctl_ops,它包含了以下的ioctl:
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_s_std = vidioc_s_std,
.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_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
};
它们的主要作用如下:
1.vidioc_querycap对应应用程序的VIDIOC_QUERYCAP,它的作用是告诉应用程序它是一个摄像头设备,是必不可少的。
2.vidioc_enum_input对应应用程序的VIDIOC_ENUMINPUT,列举输入源,及即使只有输入源也不影响使用,不是必须的
3.vidioc_g_input 得到当前使用的输入源,不是必需的
vidioc_s_input 设置使用哪个输入源,不是必须的
4.vidioc_s_std, 用于设置tv制式,不是必须的
tvnorms = V4L2_STD_525_60,这些对应模拟信号制式,可以去掉
current_norm = V4L2_STD_NTSC_M,
5.vidioc_g_fmt_vid_cap,
vidioc_try_fmt_vid_cap,
vidioc_s_fmt_vid_cap,
用于获得、尝试、设置摄像头的数据格式,很重要,不可省略
6.vidioc_reqbufs
vidioc_querybuf
vidioc_qbuf
vidioc_dqbuf
用于申请、查询、入队列、出队列,不可省略
7.vidioc_queryctrl,
vidioc_g_ctrl,
vidioc_s_ctrl,
对应界面查询、获得、设置亮度、对比度等等信息,可以省略
8.vidioc_streamon,
vidioc_streamoff,
启动、停止摄像头,不可省略
9.vidiocgmbuf, 兼容v4l1驱动框架,可以省略
总结:v4l2_ioctl_ops 中对于vivi驱动必不可少的ioctl有以下11个:
/*告诉应用程序它是一个摄像头设备,是必不可少的*/
.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,
三、深入分析数据的获取过程
在驱动程序vivi.c里缓冲区操作主要涉及以下几个ioctl:
1.请求分配缓冲区 APP:ioctl(4, VIDIOC_REQBUFS ------> Drv : vidioc_reqbufs
vidioc_reqbufs这个函数最终会调用到videobuf-core.c 里的vidioc_reqbufs函数。它含有两个参数(队列,v4l2_requestbuffers)
这个队列在open函数里用videobuf_queue_vmalloc_init初始化 。注意:这个函数只是分配缓冲区的头部信息,并没有分配实际的缓存,只有用到了缓存才分配。
2.查询缓冲区 APP:ioctl(4, VIDIOC_QUERYBUF ------> Drv :vidioc_querybuf
vidioc_querybuf 这个函数最终会调用到videobuf-core.c 里的videobuf_status函数。它主要获得的是所分配缓冲区的状态信息,如数据格式和大小,每一行的数据长度
3.映射 mmap
当应用程序调用mmap 函数时,最终会调用到vivi.c的fops里的vivi_mmap,它调用的是videobuf-core.c 里的videobuf_mmap_mapper函数
在这里才分配缓冲区,参数里含有大小。它调用videobuf-vmalloc.c里的__videobuf_mmap_mapper分配,在vmalloc_user(pages)再给缓冲区分配空间。
4.把缓冲区放入队列 APP:ioctl(4, VIDIOC_QBUF ------> Drv :vidioc_qbuf
它调用到vivi.c 里的vidioc_qbuf,最终调用到videobuf-core.c里的videobuf_qbuf,它把缓冲区放入队列的尾部list_add_tail(&buf->stream, &q->stream);调驱动程序提供的入队列函数q->ops->buf_queue(q, buf)做一些初始化工作;
5.启动摄像头 APP:ioctl(4, VIDIOC_STREAMON ------> Drv :vidioc_streamon
它最终调用到videobuf-core.c里的videobuf_streamon函数,先做一些启动处理,然后启动摄像头
6.用select函数查询是否有数据 APP:select(5, [4], NULL, NULL, {5, 0}) -------> Drv : poll
它会调用到驱动程序提供的vivi_poll函数,它最终调用到videobuf-core.c里的videobuf_poll_stream函数,首先从队列的头部取出一个buf: buf = list_entry(q->stream.next,struct videobuf_buffer, stream);如果缓冲区里没有数据的话,就执行poll_wait(file, &buf->done, wait)在buf->done里休眠。在vivi.c里面有它的唤醒函数 wake_up(&buf->vb.done)。在vivi虚拟驱动里采用一个内核线程 vivi_thread ,使用vivi_sleep函数休眠,每隔一段时间使用vivi_thread_tick里的vivi_fillbuff来产生数据,并唤醒进程wake_up(&buf->vb.done)。
7.把缓冲区从队列里取出 APP:ioctl(4, VIDIOC_DQBUF ------> Drv :vidioc_dqbuf
它最终调用到videobuf-core.c里的videobuf_dqbuf函数,获得数据已经就绪的缓冲区。在这个函数里首先调用retval = stream_next_buffer(q, &buf, nonblocking) 取出这个缓冲区,然后调用list_del(&buf->stream)把这个缓冲区从队列里删除,最后调用videobuf_status(q, b, buf, q->type)把这个缓冲区的状态返回给应用程序。
8.应用程序根据缓冲区的状态得到是哪个缓冲区有数据,就去相应的mmap函数读取相应的地址。
总结:从以上分析过程可以看出,应用程序调用某个ioctl,驱动程序必须提供相应的ioctl,它们实质上是调用系统提供的函数(例如videobuf-core.c和videobuf-vmalloc.c等)来实现相应的ioctl。这里的重点是,用select函数查询是否有数据,select函数在休眠等待有数据,在某段时间里没有数据会超时返回,因此vivi驱动里必定也要提供虚拟产生数据的函数,唤醒进程的函数。
四、怎么写自己的摄像头驱动程序?
1.分配一个video_device结构体
video_device_alloc
2.设置video_device里的
.fops
.ioctl_ops (包含上面11个重要的ioctl)
如果要用内核的buf函数,就构造这个结构体videobuf_queue_ops
3.注册
video_register_device