V4L2学习笔记

V4L2

定义

linux视频设备驱动,V4L2(Video for linux2)是Linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写

接口

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

v4l12结构体介绍

常用的结构体在内核目录include/linux/videodev2.h中定义,要加上头文件<linux/videodev2.h>

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_requestbuffers        //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_buffer        //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF 
struct v4l2_crop        //视频信号矩形边框
v4l2_std_id        //视频制式

内容:

1. 查看视频设备功能的结构体

struct v4l2_capability		 //视频设备的功能,对应命令VIDIOC_QUERYCAP
{
	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匹配
//  V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取

2. 查询并显示支持的所有视频格式

struct v4l2_fmtdesc
{
    u32 index;// 要查询的格式序号,应用程序设置
    enum v4l2_buf_type type; // 帧类型,应用程序设置
    u32 flags;// 是否为压缩格式
    u8 description[32]; // 格式名称
    u32 pixelformat;// 格式
    u32 reserved[4]; // 保留
};

3. 帧的格式结构体,数据流类型结构体

struct v4l2_format			//帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
{
    enum v4l2_buf_type type; 	  // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
    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;	 	// 宽,必须是16的倍数
    __u32                   height; 	// 高,必须是16的倍数
    __u32                   pixelformat; // 采样类型,如YUV 4:2:2
    enum v4l2_field         field; 		// 采样区域,如隔行采样
    __u32                   bytesperline;   // 一行图像占用的字节数 
    __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 指定设备的颜色空间。

4. 申请帧缓存结构体

struct v4l2_requestbuffers {		 //申请帧缓冲,对应命令VIDIOC_REQBUFS
    __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,
};
// struct v4l2_requestbuffers,VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息。

5. 驱动中的一帧图像缓存结构体

struct v4l2_buffer {		//驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF 
    __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_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。

常用的IOCTL接口

也在include/linux/videodev2.h中定义

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。

在Linux设备中,一般使用ioctl函数来对设备的I/O通道进行管理

extern int ioctl(int _fd, unsigned long int _request, ...) _THROW;
_fd:设备的ID,例如刚才使用open函数打开视频通道后返回的cameraFd;
_request: 具体的命令标志符
在进行V4L2开发中,一般会用到以下的命令标志符:
 VIDIOC_QUERYCAP:查询驱动功能
 VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC
 VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
 VIDIOC_TRY_FMT: 验证当前驱动的显示格式
 VIDIOC_S_FMT:  设置当前驱动的频捕获格式
 VIDIOC_G_FMT:  读取当前驱动的频捕获格式
 VIDIOC_CROPCAP: 查询驱动的修剪能力
 VIDIOC_S_CROP: 设置视频信号的边框
 VIDIOC_G_CROP: 设置视频信号的边框
 VIDIOC_REQBUFS:分配内存
 VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
 VIDIOC_QBUF: 把数据放入缓存队列
 VIDIOC_DQBUF: 把数据从缓存中读取出来
 VIDIOC_STREAMON:开始视频显示函数
 VIDIOC_STREAMOFF:结束视频显示函数
  这些IO调用,有些是必须的,有些是可选择的

操作流程

1. 打开设备文件

a.查看设备名

ls /dev/video*

b.相关函数

#include<fcntl.h>
int open(const char* device_name, int flags);

c.示例

int fd_video = open("/dev/video0", O_RDWR)  // 将O_RDWR 换为 O_RDWR | O_NONBLOCK ,转化为非阻塞模式
//  应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。
if (fd_video < 0)
{
    perror("video0 open fail");
    return fd_video;
} 

2. 查看设备功能

看看设备具有什么功能,比如是否具有视频输入或者音频输入输出等。VIDIOC_QUERYCAP

a.相关函数

int ioctl(int fd, int request, struct v4l2_capability *argp);

b.示例

struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));  // 初始化
// 查询驱动功能
if (ioctl(fd_video, VIDIOC_QUERYCAP, &cap) < 0){
    perror("requre VIDIOC_QUERYCAP fialed! \n");
    return -1;
}
//获取成功,检查是否有视频捕获功能
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
    perror("device is no video capture device\n");
    return -1;
}
// 检查是否具有数据流控制模式
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
    perror("device does not support streaming i/o\n");
    return -1;
}
// 打印信息
cout << "Driver Name: " << cap.driver << endl;              // 驱动名字
cout << "Card Name: " << cap.card << endl;                  // 设备名字
cout << "Bus info: " << cap.bus_info << endl;               // 设备在系统中的位置
printf("Driver Version:%u.%u.%u\n",(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF);      //驱动版本号, %u是十进制无符号整数

3. 显示设备支持的所有视频格式

//  4.查询设备支持哪种视频格式
struct v4l2_fmtdesc fmt;       //查询设备格式所用结构体
memset(&(fmt), 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.index = 0;
printf("显示当前驱动支持的视频格式format:\n");
// VIDIOC_ENUM_FMT: 获取当前驱动支持的视频格式
while(ioctl(fd_video, VIDIOC_ENUM_FMT, &fmt) != -1)
{
    printf("\t%d.%s\n",fmt.index+1,fmt.description);  // index是查询的格式序号, description是格式名称
    fmt.index++;
}

4. 检测视频支持的制式(不用用到)

v4l2_std_id std;
 do {
     //  VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC 
      ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC: 
 //……
 case V4L2_STD_PAL:
//……
}

NTSC和PAL属于全球两大主要的电视广播制式 , 但是由于系统投射颜色影像的频率而有所不同 .

NTSC即正交平衡调幅制。PAL为逐行倒像正交平衡调幅制。

PAL电视标准

PAL电视标准,每秒25帧,电视扫描线为625线,奇场在前,偶场在后,标准的数字化PAL电视标准分辨率为720*576, 24比特的色彩位深,画面的宽高比为4:3, PAL电视标准用于中国、欧洲等国家和地区。

NTSC电视标准

NTSC电视标准,每秒29.97帧(简化为30帧),电视扫描线为525线,偶场在前,奇场在后,标准的数字化NTSC电视标准分辨率为720*486, 24比特的色彩位深,画面的宽高比为4:3。NTSC电视标准用于美、日等国家和地区。

5. 设置视频的制式和帧格式

制式包括PAL,NTSC, 帧的格式包括宽度和高度等。

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

// 5.设置视频帧的格式
struct v4l2_format s_fmt;
memset(&(s_fmt), 0, sizeof(s_fmt));
s_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;       // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
s_fmt.fmt.pix.width = 1440;      
s_fmt.fmt.pix.height = 800;
s_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  // 采样类型 V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUYV, 
s_fmt.fmt.pix.field=V4L2_FIELD_ANY; // 采样区域,如隔行采样。系统自动设置: 帧属性

// 设置图片格式,VIDIOC_S_FMT:设置当前驱动的频捕获格式 
if(ioctl(fd_video,VIDIOC_S_FMT,&s_fmt) != 0)
{
    printf("set format error\n");
    return -1;
}
// 得到图片格式
if(ioctl(fd_video, VIDIOC_G_FMT, &s_fmt) == -1){
    perror("set format failed!");
    return -1;
}
cout << "实际得到的图片格式如下:"<< endl;
// 判断摄像头是否支持YUV格式图像输出
if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
{
    printf("当前摄像头支持YUV格式图像输出!\n");
}
else
{
    printf("当前摄像头不支持YUV格式图像输出!\n");
    return -3;
}
printf("fmt.type:\t\t%d\n",s_fmt.type);
// %c表示输出单个字符
printf("pix.pixelformat:\t%c%c%c%c\n", \
        s_fmt.fmt.pix.pixelformat & 0xFF,\
        (s_fmt.fmt.pix.pixelformat >> 8) & 0xFF, \
        (s_fmt.fmt.pix.pixelformat >> 16) & 0xFF,\
        (s_fmt.fmt.pix.pixelformat >> 24) & 0xFF);
printf("pix.width:\t\t%d\n",s_fmt.fmt.pix.width);
printf("pix.height:\t\t%d\n",s_fmt.fmt.pix.height);
printf("pix.field:\t\t%d\n",s_fmt.fmt.pix.field);

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

// 设置摄像头采集的帧率
struct v4l2_streamparm streamparm;
streamparm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/
streamparm.parm.capture.timeperframe.numerator=1;
streamparm.parm.capture.timeperframe.denominator=30;
printf("设置当前摄像头采集帧率: %d秒%d帧\n",streamparm.parm.capture.timeperframe.numerator,streamparm.parm.capture.timeperframe.denominator);
if(ioctl(uvc_video_fd,VIDIOC_S_PARM,&streamparm)) /*设置摄像头的帧率*/
{
    printf("设置摄像头采集的帧率失败!\n");
    return -3;
}
if(ioctl(uvc_video_fd,VIDIOC_S_PARM,&streamparm)) /*获取摄像头的帧率*/
{
    printf("获取摄像头采集的帧率失败!\n");
    return -3;
}
printf("当前摄像头实际采集帧率: %d秒%d帧\n",streamparm.parm.capture.timeperframe.numerator,streamparm.parm.capture.timeperframe.denominator);

6. 向驱动申请帧缓冲区

一般不超过5个,通过VIDIOC_REQBUFS控制命令申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。

//此处作用为申请缓冲区
struct v4l2_requestbuffers req;
memset(&(req), 0, sizeof(req));
req.count=4;    	//申请一个拥有四个缓冲帧的缓冲区
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;
//  VIDIOC_REQBUFS:分配内存, 申请一片连续的内存
ioctl(fd_video,VIDIOC_REQBUFS,&req);
printf("摄像头缓冲区申请的数量: %d\n",req.count);

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

7. 申请物理内存

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

将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制

使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列

// 定义一个结构体来映射每个缓冲帧
struct buffer
{
    void* start;
    unsigned int length;
};
//申请4个struct buffer空间
buffers = (struct buffer*)calloc (req.count, sizeof (struct buffer));
if (!buffers)
{
    perror ("Out of memory");
    exit (EXIT_FAILURE);
}

for (unsigned int i = 0; i < req.count; i++)
{
     struct v4l2_buffer buf;  // 驱动中的一帧图像缓存
     memset(&buf, 0, sizeof(buf));
     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     buf.memory = V4L2_MEMORY_MMAP;
     buf.index = i;
     // 读取缓存
     if (-1 == ioctl (fd_video, VIDIOC_QUERYBUF, &buf))
        exit(-1);
     // 转化成相应地址
     buffers[i].length = buf.length;
     buffers[i].start = mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_video, buf.m.offset);

     // 失败时,mmap()返回MAP_FAILED[其值为(void *)-1]
     if (MAP_FAILED == buffers[i].start)
     exit(-1);
}
enum v4l2_buf_type type;
int i;
for (i = 0; i < 4; ++i)
{
    struct v4l2_buffer buf;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    // VIDIOC_QBUF: 把数据放入缓存队列
    ioctl (fd_video, VIDIOC_QBUF, &buf);
}

8. 开始视频的采集

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

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

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

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

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

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

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

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始捕获图像
ioctl (fd_video, VIDIOC_STREAMON, &type);

9. 出队列以取得已采集数据的帧缓冲,取得原始采集数据

VIDIOC_DQBUF: 把数据从缓存中读取出来.

    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    //取出图像数据,把数据从缓存中读取出来
    ioctl(fd_video, VIDIOC_DQBUF, &buf);

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

 ioctl (fd_video,VIDIOC_QBUF,&buf);

11. 停止视频的采集,解除映射, 关闭设备

printf("success\n");
int i;
for (i = 0; i < 4; i++)
{
    munmap(buffers[i].start, buffers[i].length);  // 解除映射
}
free(buffers);			// 释放内存缓冲区
munmap(pfb, 1440*800*4);  // 解除显示器设备的内存映射
close(fd_video);	 // 关闭摄像头
close(fd_fb);		// 关闭显示器

流程描述

(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)释放申请的视频帧缓冲区mummap,关闭视频设备文件close(fd_v4l)。

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

参考文章

参考代码仓库

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值