一、简介
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==-1) { cout<<"Cannot open the device."??endl; exit(1); } else { cout<<"Open the device."??endl; } }
</pre>
3.2查看摄像头支持的模式已经初始化
需要用到的结构体:
<pre>
struct v4l2_capability { __u8 driver[16]; // 驱动名字 __u8 card[32]; // 设备名字 __u8 bus_info[32]; // 设备在系统中的位置 __u32 version; // 驱动版本号 __u32 capabilities; // 设备支持的操作 __u32 reserved[4]; // 保留字段 }; </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[200]; }; </pre> 实现函数: <pre> void init_camera(Camera* cam){ struct v4l2_capability cap; if (-1 == ioctl(cam->fd, VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device ", 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 ", cam->device_name); exit(EXIT_FAILURE); } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o ",cam->device_name); exit(EXIT_FAILURE); } //#ifdef DEBUG_CAM printf(" VIDOOC_QUERYCAP "); printf("the camera driver is %s ", cap.driver); printf("the camera card is %s ", cap.card); printf("the camera bus info is %s ", cap.bus_info); printf("the version is %d ", cap.version); struct v4l2_format fmt; memset(&fmt, 0, 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) < 0) { 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[2]; }; 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, 0, sizeof(req)); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) { fprintf(stderr, "Request buffers failure. "); exit(EXIT_FAILURE); } if (req.count < 2) { fprintf(stderr, "Insufficient buffer memory on %s ", cam->device_name); return; } cam->buffers = (Buffer *)calloc(req.count, sizeof(*cam->buffers)); struct v4l2_buffer buf; for (unsigned int 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; if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) == -1) { 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 = 0; i < 4; i++) { memset(&buf, 0, 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) < 0) { } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE;; if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) { } cout<<"STREAMON"<<endl; }
</pre>
3.5读取一帧并交给用户程序处理
<pre>
int read_and_encode_frame(Camera* cam) { 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(cam->fd, VIDIOC_DQBUF, &capture_buf) < 0) { cout<<"cannot get buf"<<endl; } cout<<"read_and_encode_frame"<<endl; encode_frame(cam,capture_buf.index,capture_buf.length); if (-1 == ioctl(cam->fd, VIDIOC_QBUF, &capture_buf)) return -1; return 0; }
</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]=='