v4l2的学习建议和流程解析

转自: https://www.cnblogs.com/silence-hust/p/4464291.html

上各种找资料后,才发现其实v4l2已经分装好了驱动程序,只要我们根据需要调用相应的接口和函数,从而实现视频的获取和处理。只要认真的看几篇文章就对v4l2有一定的了解了,由于是第一次接触,网上的资料良莠不齐,难得可以找到几篇自己感觉很不错的。记录下来:(没必要看太多,很多都是一样的意思)

一、Video for Linux two

v4l2为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。只能在linux下使用。它使程序有发现设备和操作设备的能力。它主要是用一系列的回调函数来实现这些功能。像设置摄像头的频率、帧频、视频压缩格式和图像参数等等。当然也可以用于其他多媒体的开发,如音频等。

在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文是/dev/v4l/video0。为了通用,可以建立一个到/dev/video0的链接。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。V4L2从Linux 2.5.x版本的内核中开始出现。

V4L2规范中不仅定义了通用API元素(Common API Elements),图像的格式(Image Formats),输入/输出方法(Input/Output),还定义了Linux内核驱动处理视频信息的一系列接口(Interfaces),这些接口主要有:

视频采集接口——Video Capture Interface;

视频输出接口—— Video Output Interface;

视频覆盖/预览接口——Video Overlay Interface;

视频输出覆盖接口——Video Output Overlay Interface;

编解码接口——Codec Interface。

二、v4l2结构体介绍

1、常用的结构体在内核目录include/linux/videodev2.h中定义

struct v4l2_requestbuffers	//申请帧缓冲,对应命令VIDIOC_REQBUFS 
struct v4l2_capability		//视频设备的功能,对应命令VIDIOC_QUERYCAP 
struct v4l2_input			//视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard		//视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD 
struct v4l2_format			//帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer			//驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF 
struct v4l2_crop			//视频信号矩形边框
v4l2_std_id	stdid			//视频制式

常用结构体的内容:

struct v4l2_capability
{
	u8 driver[16];	// 驱动名字
	u8 card[32];	// 设备名字
	u8 bus_info[32]; // 设备在系统中的位置
	u32 version;	// 驱动版本号
	u32 capabilities;// 设备支持的操作
	u32 reserved[4]; // 保留字段
};

其中域 capability 代表设备支持的操作模式,常见的值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式;另外 driver 域需要和 struct video_device 中的 name 匹配。

struct v4l2_format { 
    enum v4l2_buf_type type; 
    union { 
        struct v4l2_pix_format         pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ 
        struct v4l2_window             win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ 
        struct v4l2_vbi_format         vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */ 
        struct v4l2_sliced_vbi_format  sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ 
        __u8   raw_data[200];                   /* user-defined */ 
    } fmt; 
}; 

其中

enum v4l2_buf_type { 
    V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1, 
    V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2, 
    V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3, 
    ... 
    V4L2_BUF_TYPE_PRIVATE              = 0x80, 
}; 
   
struct v4l2_pix_format { 
    __u32                   width; 
    __u32                   height; 
    __u32                   pixelformat; 
    enum v4l2_field         field; 
    __u32                   bytesperline;   /* for padding, zero if unused */ 
    __u32                   sizeimage; 
    enum v4l2_colorspace    colorspace; 
    __u32                   priv;           /* private data, depends on pixelformat */ 
};

常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。

struct v4l2_requestbuffersVIDIOC_REQBUFSVIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息:

struct v4l2_requestbuffers {
    __u32                   count;
    enum v4l2_buf_type      type;
    enum v4l2_memory        memory;
    __u32                   reserved[2];
};
enum v4l2_memory {
    V4L2_MEMORY_MMAP             = 1,
    V4L2_MEMORY_USERPTR          = 2,
    V4L2_MEMORY_OVERLAY          = 3,
};

count 指定根据图像占用空间大小申请的缓存区个数,type 为视频捕获模式,memory 为内存区的使用方式。

struct v4l2_buffer {
    __u32   index;					// 缓存编号
    enum v4l2_buf_type    type;		// 视频捕获模式
    __u32    bytesused;				// 缓存已使用空间大小
    __u32    flags;					// 缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
    enum v4l2_field  field;
    struct timeval    timestamp;	// 时间戳
    struct v4l2_timecode   timecode;
    __u32     sequence;				// 缓存序号
  
    /* memory location */
    enum v4l2_memory    memory;		// 缓存使用方式
    union {
            __u32   offset;			// 当前缓存与内存区起始地址的偏移
            unsigned long   userptr;
    } m;
    __u32    length;				// 缓存大小
    __u32    input;
    __u32    reserved;				// 一般用于传递物理地址值。
};

另外 VIDIOC_QBUFVIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 indexVIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。

2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

VIDIOC_REQBUFS		//分配内存 
VIDIOC_QUERYBUF		//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址 
VIDIOC_QUERYCAP		//查询驱动功能 
VIDIOC_ENUM_FMT		//获取当前驱动支持的视频格式 
VIDIOC_S_FMT		//设置当前驱动的频捕获格式 
VIDIOC_G_FMT		//读取当前驱动的频捕获格式 
VIDIOC_TRY_FMT		//验证当前驱动的显示格式 
VIDIOC_CROPCAP		//查询驱动的修剪能力 
VIDIOC_S_CROP		//设置视频信号的矩形边框 
VIDIOC_G_CROP		//读取视频信号的矩形边框
VIDIOC_QBUF			//把数据从缓存中读取出来 
VIDIOC_DQBUF		//把数据放回缓存队列 
VIDIOC_STREAMON		//开始视频显示函数 
VIDIOC_STREAMOFF	//结束视频显示函数 
VIDIOC_QUERYSTD		//检查当前视频设备支持的标准,例如PAL或NTSC。

三、调用v4l2的工作流程

打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备

(1)打开设备文件

打开视频设备非常简单,在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:

1. 用非阻塞模式打开摄像头设备

int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK);

2. 如果用阻塞模式打开摄像头设备,上述代码变为:

cameraFd = open("/dev/video0", O_RDWR);

关于阻塞模式和非阻塞模式

应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

(2)取得设备的capability

struct v4l2_capability capability;
int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);

看看设备具有什么功能,比如是否具有视频输入特性。

struct v4l2_capability cap;
 
memset(&cap, 0, sizeof(cap));

/* 获取设备支持的操作 */
if(ioctl(dev->fd, VIDIOC_QUERYCAP, &cap) < 0){
    if(EINVAL == errno){   /*EINVAL为返回的错误值*/
        printf(stderr,"%s is no V4L2 device\n", dev->dev);
        return TFAIL;
    }
    else
    {
        printf(stderr,"%s is not V4L2 device,unknow error\n", dev->dev);
        return TFAIL;
    }
}

//获取成功,检查是否有视频捕获功能
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
    printf(stderr, "%s is no video capture device\n",dev->dev);
    return TFAIL;
}
/* streaming I/O ioctls */
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
    printf(stderr, "%s does not support streaming i/o\n",dev->dev);
    return TFAIL;
}

(3)选择视频输入

struct v4l2_input input;
……初始化input
int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);

一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。

VIDIOC_G_INPUTVIDIOC_S_INPUT 用来查询和选则当前的 input,一个 video 设备节点可能对应多个视频源,比如 saf7113 可以最多支持四路 cvbs 输入,如果上层想在四个cvbs视频输入间切换,那么就要调用 ioctl(fd, VIDIOC_S_INPUT, &input) 来切换。VIDIOC_G_INPUT and VIDIOC_G_OUTPUT 返回当前的 video input和output的index.

struct v4l2_input {
	__u32 index; /* Which input *
	/__u8 name[32]; /* Label */
	__u32 type; /* Type of input */
	__u32 audioset; /* Associated audios (bitfield) */
	__u32 tuner; /* Associated tuner */
	v4l2_std_id std;
	__u32 status;
	__u32 reserved[4];
};

(4)检测视频支持的制式

v4l2_std_id std;

do {
	ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);

switch (std) {
case V4L2_STD_NTSC: 
	//……
case V4L2_STD_PAL:
	//……
}

(5)设置视频捕获格式

v4l2_format 结构体用来设置摄像头的视频制式、帧格式等,在设置这个参数时应先填 好 v4l2_format 的各个域,如 type(传输流类型),fmt.pix.width(宽),fmt.pix.heigth(高),fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2),然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式。

struct v4l2_format fmt;

memset(&fmt, 0, sizeof(fmt));
fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = g_display_width;
fmt.fmt.pix.height      = g_display_height;
fmt.fmt.pix.pixelformat = g_fmt;
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

/* 设置设备捕获视频的格式 */
if(ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0)
{
    printf(stderr, "%s iformat not supported \n",dev->dev);
    close(dev->fd);
    return TFAIL;
}

注意:如果该视频设备驱动不支持你所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该视频设备所支持的图像格式,所以在程序设计中,设定完所有的视频格式后,要获取实际的视频格式,要重新读取struct v4l2_format结构体变量。

(6)向驱动申请帧缓存

一般不超过5个,CAP_BUF_NUM = 4

struct v4l2_requestbuffers req;

/* 申请设备的缓存区 */
memset(&req, 0, sizeof(req));
req.count = CAP_BUF_NUM;  //申请一个拥有四个缓冲帧的缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
 
if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) < 0)
{
	if (EINVAL == errno)
	{
		printf(stderr, "%s does not support "
	 		"memory mapping\n", dev->dev);
		return TFAIL;
	}
	else
	{
		printf(stderr, "%s does not support "
			"memory mapping, unknow error\n", dev->dev);
		return TFAIL;
	}
}

if (req.count < 2)
{
	printf(stderr, "Insufficient buffer memory on %s\n",
		dev->dev);
	return TFAIL;
}

v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。控制命令VIDIOC_REQBUFS

功能: 请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存),V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。

参数说明:参数类型为V4L2的申请缓冲区数据结构体类型struct v4l2_requestbuffers

返回值说明: 执行成功时,函数返回值为 0;V4L2驱动层分配好了视频缓冲区;

(7)获取每个缓存的信息,并mmap到用户空间

应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping)和用户指针。这里只讨论内存映射(memory mapping)。

typedef struct VideoBuffer {   //定义一个结构体来映射每个缓冲帧
	void *start;
	size_t length;
} VideoBuffer;

VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer buf;

//映射所有的缓存
for (numBufs = 0; numBufs < req.count; numBufs++)
{
	memset( &buf, 0, sizeof(buf) );
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.index = numBufs;
	
	//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作
	if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
		return -1;
	}
	buffers[numBufs].length = buf.length;
	// 转换成相对地址
	buffers[numBufs].start = mmap(NULL, buf.length,
                                  PROT_READ | PROT_WRITE,
                                  MAP_SHARED,
                                  fd, buf.m.offset);
	if (buffers[numBufs].start == MAP_FAILED) {
		return -1;
	}
}

addr 映射起始地址,一般为NULL ,让内核自动选择
length 被映射内存块的长度
prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
fd,offset, 确定被映射的内存地址 返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1)

int munmap(void *addr, size_t length);// 断开映射
//addr 为映射后的地址,length 为映射后的内存长度

(8)开始采集视频 (在缓冲区处理好之后就可以获得视频了 )

在开始之前,还应当把缓冲帧放入缓冲队列,应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping)和用户指针。这里只讨论内存映射(memory mapping)。

操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。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 。

//把四个缓冲帧放入队列
for (i = 0; i < CAP_BUF_NUM; i++)
{
       memset(&buf, 0, sizeof(buf));
       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
       buf.memory = V4L2_MEMORY_MMAP;
       buf.index = i;
       buf.m.offset = dev->buffer[i].offset;
       /* 将空闲的内存加入可捕获视频的队列 */
       if(ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0)
       {
           printf("ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n", dev->dev, __FUNCTION__, __LINE__);
           return TFAIL;
       }
}
 
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 打开设备视频流 */
if(ioctl(dev->fd, VIDIOC_STREAMON, &type) < 0)
{
       printf("ERROR: VIDIOC_STREAMON[%s], FUNC[%s], LINE[%d]\n", dev->dev, __FUNCTION__, __LINE__);
       return TFAIL;
}

前期初始化完成后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,即要通过驱动程序在内存中申请几个帧缓冲区来存放视频数据。

应用程序通过API接口提供的方法(VIDIOC_REQBUFS)申请若干个视频数据的帧缓冲区,申请帧缓冲区数量一般不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓冲区在内核空间。

应用程序通过API接口提供的查询方法(VIDIOC_QUERYBUF)查询到帧缓冲区在内核空间的长度和偏移量地址。

应用程序再通过内存映射方法(mmap),将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。

(1)将帧缓冲区在视频输入队列排队,并启动视频采集

在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incoming queues)和视频采集输出队列(outgoing queues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。如图2所示。

应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。

(2)循环往复,采集连续的视频数据

启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。

最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图1所示。

在这里插入图片描述

(9)取出FIFO缓存中已经采样的帧缓存

struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
/* 将已经捕获好视频的内存拉出已捕获视频的队列 */
if (ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf) < 0)
{
       printf("ERROR: VIDIOC_DQBUF[%s], FUNC[%s], LINE[%d]\n", dev, __FUNCTION__, __LINE__);
       return TFAIL;
       }
}
 
image_data_handle(buffer[capture_buf.index].start, capture_buf.bytesused);

(10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
	return -1;
}

(11)停止视频的采集,解除映射

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
munmap(buffer[j].start, buffer[j].length);

(12)关闭视频设备

close(fd);

最后这个是一般mmap形式的使用流程,还有使用read/write方式的内存读写流程,具体的可以参考官方的capture.c这个文档,程序的流程很清楚,也有相关的博文有写到。

文字描述版流程:

(1)打开视频设备文件。int fd=open("/dev/video0",O_RDWR);
(2)查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)
(3)设置视频采集的参数
  设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)
  设置视频图像的采集窗口的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)
  设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)
  设置视频的帧率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)
  设置视频的旋转方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)
(4)向驱动申请视频流数据的帧缓冲区
  请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)
  查询帧缓冲区在内核空间中的长度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)
(5)应用程序通过内存映射,将帧缓冲区的地址映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。
buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);
(6)将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
(7)开始视频流数据的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)
(8) 驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。
(9)应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl (fd_v4l, VIDIOC_DQBUF, &buf),应用程序处理该帧缓冲区的原始视频数据。
(10)处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)

重复上述步骤8到10,直到停止采集数据。
(11)停止视频的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)
(12)释放申请的视频帧缓冲区unmap,关闭视频设备文件close(fd_v4l)

以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。

猜你喜欢

转载自blog.csdn.net/u013554213/article/details/89450545