V4L2视频采集原理
一、简介
Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。可以对uvc免驱摄像头直接操作。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
二、V4L2视频采集原理
V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集。我们一般使用内存映射方式来进行视频采集。
V4L2采集视频数据的五个步骤:
首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
第五,停止视频采集。

其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示

三、基于v4l2的远程监控测试程序
测试程序属于未完成的阶段,v4l2部分已经完成。
V4l2各项函数定义在测试程序的camera.cpp中。
程序设计师按照以上流程设计,查看源码的时候可以对照调用流程图,对于其中一些参数理解可以参考参考文献的第一篇文章。
3.1打开摄像头
<pre>
void open_camera(Camera* cam)
{
cam->fd=open(cam->device_name,O_RDWR);
if(cam->fd==-)
{
cout<<"Cannot open the device."??endl;
exit();
}
else
{
cout<<"Open the device."??endl;
}
}
</pre>
3.2查看摄像头支持的模式已经初始化
需要用到的结构体:
<pre>
struct v4l2_capability
{
__u8 driver[]; // 驱动名字
__u8 card[]; // 设备名字
__u8 bus_info[]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 reserved[]; // 保留字段
};
</pre>
capabilities常用值:
<pre>
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
struct v4l2_format
{
enum v4l2_buf_type type;// 帧类型,应用程序设置
union fmt
{
struct v4l2_pix_format pix;// 视频设备使用
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
__u8 raw_data[];
};
</pre>
实现函数:
<pre>
void init_camera(Camera* cam){
struct v4l2_capability cap; if (- == ioctl(cam->fd, VIDIOC_QUERYCAP, &cap))
{
if (EINVAL == errno)
{
fprintf(stderr, "%s is no V4L2 device\n", cam->device_name);
exit(EXIT_FAILURE);
}
else
{
errno_exit("VIDIOC_QUERYCAP");
}
} if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
fprintf(stderr, "%s is no video capture device\n", cam->device_name);
exit(EXIT_FAILURE);
} if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
fprintf(stderr, "%s does not support streaming i/o\n",cam->device_name);
exit(EXIT_FAILURE);
} //#ifdef DEBUG_CAM
printf("\nVIDOOC_QUERYCAP\n");
printf("the camera driver is %s\n", cap.driver);
printf("the camera card is %s\n", cap.card);
printf("the camera bus info is %s\n", cap.bus_info);
printf("the version is %d\n", cap.version); struct v4l2_format fmt;
memset(&fmt, , sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = cam->width;
fmt.fmt.pix.height = cam->height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < )
{
close(cam->fd);
}
cout<<"init the camera."<<endl;
init_mmap(cam);
}
</pre>
3.3内存映射
需要用到的结构体:
<pre>
structv4l2_requestbuffers
{
__u32count;//缓冲区内缓冲帧的数目
enumv4l2_buf_typetype;//缓冲帧数据格式
enumv4l2_memorymemory;//区别是内存映射还是用户指针方式
__u32 reserved[];
};
struct v4l2_buffer
{
__u32index;//buffer序号
enumv4l2_buf_typetype;//buffer类型
__u32byteused;//buffer中已使用的字节数
__u32flags;//区分是MMAP还是USERPTR
enumv4l2_fieldfield;
structtimevaltimestamp;//获取第一个字节时的系统时间
structv4l2_timecode timecode;
__u32sequence;//队列中的序号
enum v4l2_memorymemory;//IO方式,被应用程序设置
union m
{
__u32 offset;//缓冲帧地址,只对MMAP有效
unsignedlonguserptr;
};
__u32length;//缓冲帧长度
__u32input;
__u32reserved;
};
</pre>
自己定义的一个结构体来映射每个缓存帧:
<pre>
struct buffer
{
void* start;
unsigned int length;
}buffers;
</pre>
实现函数:
<pre>
void init_mmap(Camera cam)
{
struct v4l2_requestbuffers req;
memset(&req, , sizeof(req));
req.count = ;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < )
{
fprintf(stderr, "Request buffers failure.\n");
exit(EXIT_FAILURE);
}
if (req.count < )
{
fprintf(stderr, "Insufficient buffer memory on %s\n",
cam->device_name);
return;
}
cam->buffers = (Buffer *)calloc(req.count, sizeof(*cam->buffers));
struct v4l2_buffer buf;
for (unsigned int numBufs = ; numBufs < req.count; numBufs++)
{
memset(&buf, , sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) == -)
{
return ;
}
cam->buffers[numBufs].length = buf.length;
cam->buffers[numBufs].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE,
MAP_SHARED,
cam->fd, buf.m.offset);
if (cam->buffers[numBufs].start == MAP_FAILED)
{
return ;
}
}
cout<<"mmap the camera."<<endl;
}
</pre>
3.4开启流
<pre>
void start_capturing(Camera* cam)
{
struct v4l2_buffer buf;
enum v4l2_buf_type type;
for (int i = ; i < ; i++)
{
memset(&buf, , sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
// buf.m.offset = buffer[i].offset; if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < )
{ }
} type = V4L2_BUF_TYPE_VIDEO_CAPTURE;;
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < )
{ }
cout<<"STREAMON"<<endl;
}
</pre>
3.5读取一帧并交给用户程序处理
<pre>
int read_and_encode_frame(Camera* cam)
{
struct v4l2_buffer capture_buf;
memset(&capture_buf, , sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(cam->fd, VIDIOC_DQBUF, &capture_buf) < )
{
cout<<"cannot get buf"<<endl;
} cout<<"read_and_encode_frame"<<endl;
encode_frame(cam,capture_buf.index,capture_buf.length); if (- == ioctl(cam->fd, VIDIOC_QBUF, &capture_buf))
return -;
return ;
}
</pre>
3.6自定义处理程序:
在这里,我把获得的帧数据保存到自己定义的队列中,相对应的可以将此函数改为你所需的功能。
<pre>
void encode_frame(Camera* cam,unsigned int i,unsigned int length)
{
unsigned char *yuv_frame=static_cast<unsigned char *>(cam->buffers[i].start);
if(yuv_frame[]=='\0')
{
cout<<"yuv_frame[0]=='\0' "<<endl;
return;
}
//fwrite(yuv_frame,length,1,cam->yuv_fp); mBuffer *inBuffer=(mBuffer*)malloc(sizeof(mBuffer));
inBuffer->mpBuffer=(char*)malloc(length);
memcpy(inBuffer->mpBuffer, yuv_frame, length);
//inBuffer->mpBuffer=(char*)yuv_frame;
inBuffer->mSize=length;
putBufferWithData(&cam->buffer_list,inBuffer ); //fwrite(inBuffer->mpBuffer,length,1,outfile);
free(yuv_frame);
cout<<"fwrite done."<<endl;
}
</pre>
3.7结束采集
后面就是采集结束后的释放过程,原先demo程序在释放资源过程中一直存在问题,一直还没解决。
参考资料
1.v4l2参数和机构体说明http://blog.sina.com.cn/s/blog_602f87700100znq7.html
2.V4l2采集流程http://blog.csdn.net/eastmoon502136/article/details/8190262
作者:onesixthree
链接:https://www.jianshu.com/p/fd5730e939e7
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
V4L2视频采集原理的更多相关文章
- Linux之V4L2视频采集编程详解
V4L2(Video For Linux Two) 是内核提供给应用程序访问音.视频驱动的统一接口. Linux系统中,视频设备被当作一个设备文件来看待,设备文件存放在 /dev目录下,完整路径的设 ...
- v4l2视频采集摄像头
v4l2 --是Linux内核中关于视频设备的内核驱动框架,为上层访问底层的视频设备提供了统一的接口./dev/vidioX 1.打开设备文件 fd=open("/dev/video3&qu ...
- 基于Linux的v4l2视频架构驱动编写(转载)
转自:http://www.linuxidc.com/Linux/2011-03/33022.htm 其实,我刚开始一直都不知道怎么写驱动,什么都不懂的,只知道我需要在做项目的过程中学习,所以,我就自 ...
- 基于Linux的v4l2视频架构驱动编写
其实,我刚开始一直都不知道怎么写驱动,什么都不懂的,只知道我需要在做项目的过程中学习,所以,我就自己找了一个关于编写Linux下的视频采集监控项目做,然后上学期刚开学的时候听师兄说,跟院长做项目,没做 ...
- 基于PCIe DMA的8通道视频采集&显示IP,兼容V4L2
基于PCIe DMA的8通道视频采集&显示IP,兼容V4L2 Video Capture&Display IP for V4L2 在主机端视频设备内核驱动V4L2 的控制和调度下,Vi ...
- DAVINCI DM6446 开发攻略——V4L2视频驱动和应用分析
针对DAVINCI DM6446平台,网络上也有很多网友写了V4L2的驱动,但只是解析Montavista linux-2.6.10 V4L2的原理.结构和函数,深度不够.本文决定把Montavis ...
- 嵌入式LINUX环境下视频采集知识
V4L2是Linux环境下开发视频采集设备驱动程序的一套规范(API),它为驱动程序的编写提供统一的接口,并将所有的视频采集设备的驱动程序都纳入其的管理之中.V4L2不仅给驱动程序编写者带来极大的方便 ...
- Linux 下V4l2摄像头采集图片,实现yuyv转RGB,RGB转BMP,RGB伸缩,jpeglib 库实现压缩RGB到内存中,JPEG经UDP发送功(转)
./configure CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld --host=arm-linux --prefix=/usr/loca ...
- 入门视频采集与处理(学会分析YUV数据)
做视频采集与处理,自然少不了要学会分析YUV数据.因为从采集的角度来说,一般的视频采集芯片输出的码流一般都是YUV数据流的形式,而从视频处理(例如H.264.MPEG视频编解码)的角度来说,也是在原始 ...
随机推荐
- [LeetCode] 731. My Calendar II 我的日历之二
Implement a MyCalendarTwo class to store your events. A new event can be added if adding the event w ...
- [LeetCode] 660. Remove 9 移除9
Start from integer 1, remove any integer that contains 9 such as 9, 19, 29... So now, you will have ...
- [LeetCode] 213. House Robber II 打家劫舍之二
You are a professional robber planning to rob houses along a street. Each house has a certain amount ...
- 热情组——项目冲刺 Day7
项目相关 作业相关 具体描述 班级 班级链接 作业要求 链接地址 团队名称 热情组 作业目标 实现软件制作,以及在福大的传播 Github链接 链接地址 SCRUM部分: 成员昵称 昨日目标 开始时间 ...
- [比赛题解]CWOI2019-1
[比赛题解]CWOI2019-1 比赛日期:2019.10.12 T1 一道神仙DP题. 我们考虑\(dp[i][j][k]\)表示最后\(i\)位数,\(i-1\)位都是9,最后一位为\(j\),最 ...
- [转载]3.4 UiPath键盘操作的介绍和使用
一.键盘操作的介绍 模拟用户使用键盘操作的一种行为: 例如使用发送热键(Sendhotkey),输入信息 (Typeinto)的操作 二.键盘操作在UiPath中的使用 1.打开设计器,在设计库中新建 ...
- Visual Studio 2019(VS2019)正式版注册码秘钥
Visual Studio 2019 EnterpriseBF8Y8-GN2QH-T84XB-QVY3B-RC4DF Visual Studio 2019 ProfessionalNYWVH-HT4X ...
- 在onclick事件中传递对象参数
1.传json对象 var obj = {id: 1, name: 'jimc', age: 20}; var jsonObj = '<a onclick="show(' + JSON ...
- MySQL:实用 SQL 语句集合
写在前面的话 本文主要用于记录工作中不经常使用但是偶尔用到又非常有用的 SQL 语句,持续不断不定期更新. 数据库大小统计 1. 查看 MySQL 某个库的所有表大小,记录数,占用空间等. ,), ' ...
- kafka在zookeeper创建使用了哪些znode节点?
我们都知道kafka利用zookeeper做分布式管理,具体创建使用了哪些znode节点呢? 答案均在源码的ZkData.scala文件中,具体路径如下: https://github.com/apa ...