作者联系方式:冯牮 fengjian0106@yahoo.com.cn
V4L2 较 V4L 有较大的改动,并已成为 2.6 的标准接口,函盖 video/ dvb /FM… ,多数驱动都在向 V4l2 迁移 。 V4L2 采用流水线的方式,操作更简单直观。 一般来说,需要用到的函数就是 open() , close() 和 ioctl () 。
推荐的参考资源:
(1) Video for Linux Two API Specification---Revision 2.6.32
http:// linuxtv.org/downloads/video4linux/API/V4L2_API/spec-single/v4l2.html
(2) capture.c 官方示例程序
说明:强烈建议阅读这两个参考资源!!!如果是初次接触,建议可以先浏览一下第一个参考文档,但是capture.c程序一定要仔细的看一下,至少得先让它能够跑起来(正常运行情况下,在终端下会不断的输出一个 ".")。
下面的讲解,都将是基于capture.c程序。但是,capture.c程序有一点点小的不足,后面的内容中会介绍到,在末尾,会附上我稍微修改过的一个版本。
对照capture.c程序,值得我们注意的主要是以下几点:
(1)3 种 I/O 方式
(1.1)read/write,直接使用 read 和 write 函数进行读写。这种方式最简单,但是这种方式会在 用户空间和内核空间不断拷贝数据 ,同时在用户空间和内核空间占用 了 大量内存, 效率不高。
(1.2)mmap ,把硬件设备 里的内存映射 到位于用户空间的应用程序中的内存地址上, 直接处理设备内存,这是一种有效的 方式。
(1.3) userptr ,内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程序,然后由 v4l2 驱动程序直接将数据填充到用户空间的内存中。
第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于mmap 方式,文档中有这样一句描述 --Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap () function .(使用mmap方法的时候,buffers相当于是在内核空间中分配的,这种情况下,这些buffer是不能被交换到虚拟内存中,虽然这种方法不怎么影响读写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的时候,如果同时运行有大量的进程,则对系统的整体性能会有一定的影响。如果对于这里描述的这些概念还不是很清楚的话,没事,先记住下面给出的结论就行了,以后再详细的去了解)。
所以,对于 I/O 方法的选择,推荐的顺序是 userptr 、 mmap 、 read-write 。
(2) 当使用 mmap 或 userptr 方式的时候,有一个环形缓冲队列的概念,这个队列中,有 n 个 buffer ,驱动程序采集到的视频帧数据,就是存储在每个 buffer 中。
在每次用 VIDIOC_DQBUF 取出一个 buffer ,并且处理完数据后,一定要用 VIDIOC_QBUF 将这个 buffer 再次放回到环形缓冲队列中。
环形缓冲队列,也使得这两种 I/O 方式的效率高于直接 read/write 。
( 3 ) 采集视频的分辨率
(3.1) Cropping ,裁剪 ( 并非所有的摄像头都支持裁剪功能 )
(3.2) Scaling ,缩放
下面有个参考图片,注意图片中的 4 个矩形:
红色 —bounds ,是 最大的能捕捉到的图像 范围。这个是摄像头本身的硬件属性,比如摄像头CCD的分辨率。
蓝色 — defrect ,是 我们的设备能够得到的最大的 范围。要区别于红色的部分,这是在CCD分辨率的基础上,我们的系统通过驱动软件能够获得的最大的分辨率。defrect和bounds可能会有小的差别,也可能是重合的。
绿色 —crop ,是我们希望裁剪的部分 ( 区别裁剪和缩放 ) 。也就是我我们希望获取CCD中的某个矩形部分。
紫色 — fmt ,伸缩,这一部分是我们最终获得的图像的大小。在使用 VIDIOC_S_FMT 时,驱动程序会计算出图像帧数据的大小,并且返回给我们。 ( 后面还会提到这个 )
如果硬件设备不支持 crop ,则相当于就是直接在 defrect 矩形上面设置大小。可以简单的理解为直接设置期望的视频分辨率。普通的摄像头,通常都不支持crop,在capture.c程序中也能看到对crop的处理方式--如果有crop功能,则crop,否则就跳过,直接使用VIDIOC_S_FMT。
(4) capture.c 程序中的 process_image 函数。
capture.c 程序主要是用来演示怎样使用 v4l2 接口,并没有对采集到的视频帧数据做任何实际的处理,仅仅用 process_image 函数表示了处理图像的代码位置。
process_image 函数只有一个参数,就是存储视频帧的内存的地址指针,但是在真正的应用中,通常还需要知道该指针指向的数据的大小。
因此可以修改函数,改成 void process_image ( const void * p, int len ) ,但是每次调用 process_image 的时候,第 2 个参数该传递什么值?
考虑到程序中对 buffer 的定义
struct buffer {
void * start;
size_t length};
如果将 buffer.length 作为第 2 个参数传递到修改后的 process_image 函数中,这样做是不正确的。 process_image 需要的第二个参数应该是每帧图像的大小,仔细阅读代码后会发现, buffer.length 并不一定就等于图像帧的大小。 (buffer 的大小,还需要考虑其他的一些因素,比如内存对齐等 )。
(5) 图像帧的大小和图像的格式
首先要明确一点, RGB 和 YUV 只是两种很笼统的划分方法,还需要知道具体的封装方式,才有办法计算出视频帧数据的实际大小。
对于YUV而言, YUV 格式通常有两大类:打包( packed )格式和平面( planar )格式。前者将 YUV 分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素( macro-pixel );而后者使用三个数组分开存放 YUV 三个分量,就像是一个三维平面一样。
以h.263为例, H.263 编码算法要求图象被编码为一个亮度信号和两个色差成分( Y , Cb 和 Cr ),可以记为 YCbCr , 亮度的取样结构都是 dx 个 象素每行, dy 行每幅图象。 两个色差成分的取样都是 dx/2 个象素每行, dy /2 行每幅 图象。如下图。
H.263编码算法 要求的这种图象格式对应到 v4l2 里面,就是 V4L2_PIX_FMT_YUV420 , (YUV 是 YCbCr , YVU 是 YCrCb ) 。
V4L2_PIX_FMT_YUV420是一种平坦存储格式,也就是说,在内存中,先存储所有的 Y 值,然后是所有的 Cb 值,最后才是 Cr 值。
假设有一个 V 4L2_PIX_FMT_ Y UV 420 格式的图像,分辨率是 4 × 4 像素,那么该图像帧在内存中存储形式就是
根据前面的描述,可以看出一个公式,当使用 V4L2_PIX_FMT_YUV420 格式采集图像的时候,如果图像的宽度为 width ,高度为 height ,那么图像占用的内存的大小就是 imagesize = width * height * 3 / 2。
前面提到过, 使用 VIDIOC_S_FMT 时,驱动程序会计算出图像帧数据的大小,并且返回给 我们。当 width = 640 , height = 480 的时候,根据公式, imagesize = 640 * 480 * 3 / 2 = 460800 。
同样设置的时候,返回的 fmt.fmt.pix.sizeimage 也是 460800 。
如果是使用其他的格式,都可以根据各种格式的定义来计算它们实际占用的内存的大小以及他们在内存中的存储方式。
建议将 Video for Linux Two API Specification 作为手册,里面对视频格式的介绍比较全面。
(6)下面给出修改过的capture.c代码,只改动了一点点