V4L2(video 4 linux 2)
可以支持多种设备,它可以有以下几种接口:
1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.
2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.
3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.
4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.
5. 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.
Video4linux下视频编程的流程:
(1)打开视频设备:
(2)读取设备信息
(3)更改设备当前设置(没必要的话可以不做)
(4)进行视频采集,两种方法:
a.内存映射
b.直接从设备读取
(5)对采集的视频进行处理
(6)关闭视频设备。
/* 这样的流程地球人都晓得 */
关键步骤介绍 :
(1)打开视频:
Open(”/dev/video0”,vdàfd);
关闭视频设备用close(”/dev/video0”,vdàfd);
(2)读video_capability中信息
ioctl(vd->fd, VIDIOCGCAP, &(vd->capability))
成功后可读取vd->capability各分量 eg.
(3)读video_picture中信息
ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));
(4)改变video_picture中分量的值 (可以不做的)
先为分量赋新值,再调用VIDIOCSPICT
Eg.
vd->picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
{
perror("VIDIOCSPICT");
return -1;
}
(5)初始化channel (可以不做的)
必须先做得到vd->capability中的信息
for (i = 0; i < vd->capability.channels; i++)
{
vd->channel[i].channel = i;
if (ioctl(vd->fd, VIDIOCGCHAN, &(vd->channel[i])) < 0)
{
perror("v4l_get_channel:");
return -1;
}
}
/* 通过ioctl,将struct v4l_struct作为参数的设备操作而已,那么重点将是整个结构体:struct v4l_struct*/
typedef struct v4l_struct
{
int fd;
struct video_capability capability;
struct video_channel channel[4];
struct video_picture picture;
struct video_window window;
struct video_capture capture;
struct video_buffer buffer;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
int frame;
int framestat[2];
}vd;
/* 那么,对该结构体的了解便成了关键*/
Video4linux支持的数据结构及其用途
(1)video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨率、信号源信息等),包含的分量:
name[32] //设备名称
maxwidth ,
maxheight,
minwidth,
minheight
Channels //信号源个数
type //是否能capture,彩色还是黑白,是否能裁剪等等。值如VID_TYPE_CAPTURE等
(2)video_picture设备采集的图象的各种属性
brightness 0~65535
hue
colour
contrast
whiteness
depth // 24
palette // VIDEO_PALETTE_RGB24
(3)video_channel 关于各个信号源的属性
Channel //信号源的编号
name
tuners
Type // VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm制式
(4)video_window //包含关于capture area的信息
x x windows 中的坐标.
y x windows 中的坐标.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf //利用mmap进行映射的帧的信息
size //每帧大小
Frames //最多支持的帧数
Offsets //每帧相对基址的偏移
(6)video_buffer 最底层对buffer的描述
void *baseBase // physical address of the buffer
int height // Height of the frame buffer
int width // Width of the frame buffer
int depth // Depth of the frame buffer
int bytesperline // Number of bytes of memory between the start of two adjacent lines
实际显示的部分一般比它描述的部分小
(7)video_mmap //用于mmap
重点:
用mmap(内存映射)方式截取视频
mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝
(1)设置picture的属性
(2)初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和帧状态的当前设置
Eg. vd->mmap.format = VIDEO_PALETTE_RGB24
vd->framestat[0] = vd->framestat[1] = 0; vd->frame = 0;
(4)将mmap与video_mbuf绑定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len // 映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起
Prot // 指定共享内存的访问权限 PROT_READ(可读), PROT_WRITE (可写), PROT_EXEC (可执行)
flags // MAP_SHARED MAP_PRIVATE中必选一个 // MAP_ FIXED不推荐使用addr //共内存享的起始地址,一般设0,表示由系统分配
Mmap( ) // 返回值是系统实际分配的起始地址
if((vd->map = (unsigned char*)mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0)
{
perror("v4l_mmap mmap:");
return -1;
}
(5)Mmap方式下真正做视频截取的 VIDIOCMCAPTURE
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若调用成功,开始一帧的截取,是非阻塞的,
是否截取完毕留给VIDIOCSYNC来判断
(6)调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
frame是当前截取的帧的序号。
****关于双缓冲:
video_mbuf mbuf.frames = 2; //一帧被处理时可以采集另一帧
int frame; //当前采集的是哪一帧
int framestat[2]; //帧的状态 没开始采集 | 等待采集结束
帧的地址由 vd->map+ vd->mbuf.offsets[vd->frame]得到
采集工作结束后调用munmap取消绑定
munmap(vd->map, vd->mbuf.size)
/* 关于直接读取的方式,太落伍,不再赘述 */
区分video为 顺序式和交错式:
顺序式video顺序的传输video image所有的行,
交错式video则把一个video划分成两个fields,分别保存video image的奇数行和偶数行,被称作奇数field和偶数field.
阴极射线电视机需要交替的显示行来组成一个完整的帧,交替的时延需要我们交替的传输奇数field和偶数field.
这个奇怪的技术的引用是因为:在刷新率接近电影时,图片会消退的过快。使用field可以避免使用double buffer以及额外的带宽需求。
首先要明确camera并不是在同一时间曝光一帧,camera通过许多fields来传输这些帧的,这些field是在不同瞬间拍照。屏幕上的一个对象因此会在两个field之间产生动画效果。这种情况下需要识别哪一帧更老一点,也称作“瞬间序”
当驱动通过fields 提供或者接收images,应用层应该知道如何通过这些fields组合成帧,通过划分为top bottom field, “空间序”:
top field的第一行是帧的第一行,
bottom field的第一行是帧的第二行。
/* 问题:多个fields和一个完整的帧的关系 */
然而因为field是一个跟着一个拍的,争论帧是由top还是bottom开始的是没意义的,任何两个相邻的top bottom field, 或者 bottom top field都可以组成一个有效的帧。
与直觉相反top field不一定是older field, older filed是否包含top 或者bottom lines是由video标准决定的. 因此要区分瞬间序和空间序。下面的图会给出清晰的解释。
所有的video capture和out devices必须汇报当前的field顺序。 一些驱动可能允许选择不同的序,end应用可以在调用VIDIOC_S_FMT前初始化struct v4l2_pix_format的 field成员。否则可以使用V4L2_FIELD_ANY
下面列出了可能的field类型
V4L2_FIELD_ANY | 0 | Application 可以请求使用这个参数,如果V4L2_FIELD_NONE, V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM V4L2_FIELD_INTERLACE 中任何一个格式都支持.驱动选择使用哪一个格式依赖于硬件能力,以及请求的image尺寸,驱动选择一个然后返回这个格式。struct_buffer的field成员不可以为V4L2_FIELD_ANY.
|
V4L2_FIELD_NONE | 1 | Images是顺序式格式,驱动可以指定这种order,当驱动无法区分V4L2_FIELD_TOP和V4L2_FIELD_BOTTOM
|
V4L2_FIELD_TOP | 2 | Images仅仅包含top field
|
V4L2_FIELD_BOTTOM | 3 | Images仅仅包含bottom field. 应用可能希望防止设备捕获interlaced的图片,因为这种图片会在运动物体周围产生毛状特效
|
V4L2_FIELD_INTERLACED | 4 | Images包含top和bottom field, 隔行交替,fields的瞬间序依赖于当前video的标准。M/NTSC首先传输bottom field, 其他的则先传输top field
|
V4L2_FIELD_SEQ_TB | 5 | Images包含top和bottom field, top field的行首先存放在memory中,然后紧跟着bottom field的行。 Fields一直以瞬间序存储,较老的放在内存前面。Images的尺寸和帧相关,而不是field
|
V4L2_FIELD_SEQ_BT | 6 | Images包含top和bottom field, bottom field的行首先存放在memory中,然后紧跟着top field的行。 Fields一直以瞬间序存储,较老的放在内存前面。Images的尺寸和帧相关,而不是field
|
V4L2_FIELD_ALTERATE | 7 | 一个帧的两个field分别放在不同的buffer, 按照瞬间序,也就是说老的一个是第一个。driver或者应用指明field的奇偶性(奇偶性:当前的field是top 还是bottom field). 任何两个连续的field构成一个frame,是否两个field是连续的,不需要drop掉他们,可以通过v4l2_buffer中的sequence 成员判定。Images的尺寸和frame而不是fields相关
|
V4L2_FIELD_INTERLACED_TB | 8 | Images 包含top和bottom field, 每行交替, top field在前面。top field首先传送
|
V4L2_FIELD_INTERLACED_BT | 9 | Images 包含top和bottom field, 每行交替, bottom field在前面。bottom field首先传送 |
Field Order, Top Field First Transmitted
Field Order, Bottom Field First Transmitted
from: http://blog.csdn.net/kickxxx/article/details/6367669
相关链接:
http://blog.csdn.net/xiao888lin/article/details/5727446
===========================================================================================================
***采集程序实现过程***
首先打开视频设备,摄像头在系统中对应的设备文件为/dev/video0,采用系统调用函数grab_fd=open("/dev/video0", O_RDWR),grab_fd是设备打开后返回的文件描述符(打开错误返回-1),以后的系统调用函数就可使用它来对设备文件进行操作了。
接着,利用 ioctl(grab_fd,VIDIOCGCAP,&grab_cap)函数读取struct video_capability中有关摄像头的信息。该函数成功返回后,这些信息从内核空间拷贝到用户程序空间grab_cap各成员分量中,使用 printf函数就可得到各成员分量信息,例如printf("maxheight=%d",grab_fd.maxheight)获得最大垂直分辨率的大小。
用 ioctl(grab_fd,VIDIOCGPICT,&grab_pic) 函数读取摄像头缓冲中video_picture信息。在用户空间程序中可以改变这些信息,具体方法为先给分量赋新值,再调用VIDIOCSPICT ioctl函数,例如:
grab_pic.depth=3;
if(ioctl(grab_fd, VIDIOCSPICT, &grab_pic)<0)
{
perror("VIDIOCSP[1]ICT");
return -1;
};
完成以上初始化设备工作后,就可以对视频图像截取了,有两种方法:
一种是read()直接读取; // 通过内核缓冲 区来读取数据
另外一种mmap()内存映射。
/* mmap()通过把设备文件映射到内存中,绕过了内核缓冲区,最快的磁盘访问往往还是慢于最慢的内存访问,所以mmap()方式加速了 I/O访问。
另外,mmap()系统调用使得进程之间通过映射同一文件实现共享内存,各进程可以像访问普通内存一样对文件进行访问,访问时只需要使用指针 而不用调用文件操作函数。因为mmap()的以上优点,所以在程序实现中采用了内存映射方式,即mmap()方式。 */
利用mmap()方式视频裁取具体进行操作如下。
①先使用ioctl(grab_fd, VIDIOCGMBUF, &grab_vm) 函数获得摄像头存储缓冲区的帧信息,
之后修改video_mmap中的设置,例如重新设置图像帧的垂直及水平分辨率、彩色显示格式。可利用如下语句
grab_buf.height=240;
grab_buf.width=320;
grab_buf.format=VIDEO_PALETTE_RGB24;
②接着把摄像头对应的设备文件映射到内存区,具体使用
grab_data=(unsigned char*)mmap(0, grab_vm.size, PROT_READ|PROT_WRITE, MAP_SHARED, grad_fd, 0)
这样设备文件的内容就映射到内存区,该映射内容区可读可写并且不同进程间可共享。该函数成功时返回映像内存区的指针,挫败时返回值为-1。
下面对单帧采集和连续帧采集进行说明:
*单帧采集
在上面获取的摄像头存储缓冲区帧信息中,最多可支持的帧数(frames的值)一般为两帧。对于单帧采集只需设置 grab_buf.frame=0,即采集其中的第一帧,使用ioctl(grab_fd,VIDIOCMCAPTURE,&grab_buf) 函数,若调用成功,则激活设备真正开始一帧图像的截取,是非阻塞的。
接着使用ioctl(grab_fd,VIDIOCSYNC,&frame) 函数判断该帧图像是否截取完毕,成功返回表示截取完毕,之后就可把图像数据保存成文件的形式。
ioctl (grab_fd, VIDIOCMCAPTURE, &grab_buf)
ioctl (grab_fd, VIDIOCSYNC, &frame)
*连续帧采集
在单帧的基础上,利用grab_fd.frames值确定采集完毕摄像头帧缓冲区帧数据进行循环的次数。
在循环语句中,也是使用VIDIOCMCCAPTURE ioct1和VIDIOCSYNC ioctl函数完成每帧截取,但要给采集到的每帧图像赋地址,利用语句buf=grab_data+grab_vm.offsets[frame],然后保存文件的形式。
若要继续采集可再加一个外循环,在外循环语句只要给原来的内循环再赋frame=0即可。
------------------------------------------------------------------------------------------------------------------------------------
http://blog.csdn.net/evilcode/article/category/932884
博客扫荡
---------------------------------------------------------------------------------------------------------------
原文地址: http://www.eoeandroid.com/thread-34671-1-1.html
作者的博客:http://blog.csdn.net/wxzking
关于V4L2驱动的code,可以去下面两个路径下查看:
/kernel/drivers/media/video/
/kernel/include/media/
关于camera的code,可以去下面路径查看:
/frameworks/base/camera/libcameraservice/
/frameworks/base/incude/ui/
/hardware/mx5x/libcamera/
V4L2的移植
vivi(drivers/media/video/vivi.c)讲解一个V4L2驱动的编写。注意它是一个虚拟的设备驱动,没有与实际的硬件打交道。
1、分析几个重要数据结构:
vivi.c包含头文件v4l2-device.h和v4l2-ioctl.h,
其中v4l2-device.h中包含了v4l2-subdev.h,
v4l2-subdev.h中又包含了v4l2-common.h,
v4l2-common.h中包含了v4l2-dev.h。
关于视频采集方式
操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。
一共有三种视频采集方式:
1)使用read、write方式:直接使用 read 和 write 函数进行读写。这种方式最简单,但是这种方式会在 用户空间和内核空间不断拷贝数据 ,同时在用户空间和内核空间占用 了 大量内存,效率不高。
2)内存映射方式(mmap):把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。
3)用户指针模式:内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程序,然后由 v4l2 驱动程序直接将数据填充到用户空间的内存中。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。
第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于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是不能被交换到虚拟内存中,虽然这种方法不怎么影响读写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的时候,如果同时运行有大量的进程,则对系统的整体性能会有一定的影响。)
所以,对于三种视频采集方式的选择,推荐的顺序是 userptr 、 mmap 、 read-write 。当使用 mmap 或 userptr 方式的时候,有一个环形缓冲队列的概念,这个队列中,有 n 个 buffer ,驱动程序采集到的视频帧数据,就是存储在每个 buffer 中。在每次用 VIDIOC_DQBUF 取出一个 buffer ,并且处理完数据后,一定要用 VIDIOC_QBUF 将这个 buffer 再次放回到环形缓冲队列中。环形缓冲队列,也使得这两种视频采集方式的效率高于直接 read/write 。
---------------------------------------------------------------------------------------------------------------------------------------------------------------
YUV信号有很多种,一般YUV420和YUV422用的比较多,
YUV422格式 ,又分为很多小类,按照U、V的排列可以有YUYV, YVYU, UYVY, VYUY四种,其中,YUYV一般又称作yuv2格式。
而这四种YUV422格式 ,每种又可以分为2小类,按Y和UV的排列可以有打包格式和平面格式。
例如,一个640×480×2的YUV文件,
打包格式 就是YUYVYUYV这样一直排列下去,
平面格式 就是先640×480个Y排列完,然后是 640×240个U,然后是640×240个V这样排列,如下图:
假如有一幅640×480的图片,用yuv422来表示,那么,采样方式就是每个像素采样Y信号,U,V信号隔一个采样,这样算下来,就有640×480 个Y,640×240个U,640×240个V,一幅640×480大小的YUV图片占的总字节数为640×480×2个字节,每像素2个字节,也就是 16位。
在内存种这样排列:Y0U0Y1V0 Y2U1Y3V1...
第一个像素的YUV值为: Y0 U0 V0
第二个像素的YUV值为: Y1 U0 V0
第三个像素的YUV值为: Y2 U1 V1
.....其他以此推类,也就是说每两个像素是共用了UV的;在一行上来看,每个像素的YUV值种Y值被采样,UV值采样0后,跳到3,然后5,所以每行上Y有640个,U,V各320个