7 摄像头V4L2编程
7.1 V4L2简介
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下一套用于采集图片、视频和音频数据的通用API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。V4L2像一个优秀的快递员,将视频采集设备的图像数据安全、高效的传递给不同需求的用户。
在Linux中,一切皆文件,所有外设都被看成一种特殊的文件,称为“设备文件”。视频设备也不例外,也可以可以看成是设备文件,可以像访问普通文件一样对其进行读写。V4L2驱动的摄像头的设备文件一般是/dev/videoX(X为任意数字,要与自己的设备相对应)。
V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。由于内存映射方式的应用更广泛,所以本文重点讨论内存映射方式的视频采集。
7.2 V4L2视频采集原理
在通过V4L2采集图像之前,我们需要做的很多,但是很重要的一步是分配帧缓冲区,并将分配的帧缓冲区从内核空间映射到用户空间,然后将申请到的帧缓冲区在视频采集输入队列排队,剩下的就是等待视频数据的到来。但是,万一视频数据真的来了是怎么个流动过程呢?这个我们有必要了解一下。
当启动视频采集后,驱动程序开始采集一帧图像数据,会把采集的图像数据放入视频采集输入队列的第一个帧缓冲区,一阵图像数据就算采集完成了。第一个帧缓冲区存满一帧图像数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出,应用程序取出图像数据可以对图像数据进行处理或存储操作,然后将帧该缓冲区放入视频采集输入队列的尾部。驱动程序接下来采集下一帧数据,放入第二个缓冲区,同样的帧缓冲区存满一帧数据后,驱动程序将该缓冲区移至视频采集输出队列,应用程序将该帧缓冲区的图像数据取出后又将该帧缓冲区放入视频输入队列尾部,这样循环往复就实现了循环采集。流程如下图所示:
为了更好的理解这个过程,我们可以把“应用程序处理数据”比喻成“西瓜加工商加工西瓜”,“V4L2驱动程序采集数据”比喻成“西瓜采集员采集西瓜”,事先“西瓜加工商”会给“西瓜采集员”准备几个空篮子,然后“西瓜采集员”守着几个空篮子等待“瓜农”(图像采集设备,例如:摄像头)将空篮子装满,当“空篮子1”被“瓜农”装满以后,“西瓜采集员”会将装满西瓜的篮子放到“西瓜加工队列”等待“西瓜加工商”取走加工,当“西瓜加工商”取走装满西瓜的篮子中的西瓜的时候,“西瓜加工商”会将空篮子放回到事先给“西瓜采集员”准备好的西瓜采集队列的尾部。当“瓜农”装满下一个空篮子的时候,“西瓜采集员”同样的将装满西瓜的篮子放到“西瓜加工队列”等待“西瓜加工商”取走加工。这样,整个过程会持续不断的继续下去。
7.3 V4L2程序实现流程
使用V4L2进行视频采集,一般分为5个步骤:
(1)打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
(2)申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
(3)将帧缓冲进行入队操作,启动视频采集;
(4)驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
(5)释放资源,停止采集工作。
在进行V4L2开发中,常用的命令标识符如下:
(1)VIDIOC_REQBUFS:分配内存;
(2)VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
(3)VIDIOC_QUERYCAP:查询驱动功能;
(4)VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式;
(5)VIDIOC_S_FMT:设置当前驱动的视频捕获格式;
(6)VIDIOC_G_FMT:读取当前驱动的视频捕获格式;
(7)VIDIOC_TRY_FMT:验证当前驱动的显示格式;
(8)VIDIOC_CROPCAP:查询驱动的修剪功能;
(9)VIDIOC_S_CROP:设置视频信号的边框;
(10)VIDIOC_G_CROP:读取视频信号的边框;
(11)VIDIOC_QBUF:把数据从缓存中读取出来;
(12)VIDIOC_DQBUF:把数据放回缓存队列;
(13)VIDIOC_STREAMOP:开始视频显示函数;
(14)VIDIOC_STREAMOFF:结束视频显示函数;
(15)VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC;
这些IO调用,有些是必须的,有些是可选择的。
具体流程如下图所示:
7.4 V4L2程序实例
V4L2的代码主要位于video2lcd/video/v4l2.c文件中,接下来就针对上文 V4L2程序实现流程和流程中使用的重要数据结构,结合v4l2.c文件中的代码进行说明。代码支持内存映射和直接读取两种方式,由于内存映射方式应用更广泛,本文只详细说明内存映射方式,直接读取方式与内存映射方式类似,可自行研究。
7.4.1 打开设备
应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。如果使用非阻塞的方式打开摄像头设备,第2行代码中open函数的第二个参数修改为O_RDWR | O_NONBLOCK 即可。
70 iFd = open(strDevName, O_RDWR);
71 if (iFd < 0)
72 {
73 DBG_PRINTF("can not open %s\n", strDevName);
74 return -1;
75 }
7.4.2 查询设备属性
查询设备属性需要使用struct v4l2_capability结构体,该结构体描述了视频采集设备的driver信息。
01 struct v4l2_capability
02 {
03 __u8 driver[16];
04 __u8 card[32];
05 __u8 bus_info[32];
06 __u32 version;
07 __u32 capabilities;
08 __u32 reserved[4];
09 };
通过VIDIOC_QUERYCAP命令来查询driver是否合乎规范。因为V4L2要求所有driver和device都支持这个ioctl。所以,通过VIDIOC_QUERYCAP命令是否成功来判断当前device和driver是否符合V4L2规范。当然,这个命令执行成功的同时还能够得到设备足够的信息,如struct v4l2_capability结构体所示内容。86~98行代码检查当前设备是否为capture设备,并检查使用内存映射还是直接读的方式获取图像数据。
78 iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
79 memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
80 iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
81 if (iError) {
82 DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
83 goto err_exit;
84 }
85
86 if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
87 {
88 DBG_PRINTF("%s is not a video capture device\n", strDevName);
89 goto err_exit;
90 }
91