一、V4L2介绍
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。(Video4Linux2)是 Linux 内核中用于捕获视频数据的框架和 API。
它提供了一套标准化的接口,方便开发者与各种视频硬件设备进行交互,并支持丰富的视频格式和功能。通过使用 V4L2,开发者可以构建高效的视频捕获和处理应用程序。
V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少。
V4L2 的主要功能和特性
-
设备抽象:V4L2 提供了一种统一的接口来抽象不同的硬件设备,使得应用程序无需关心具体的硬件实现细节。
-
视频捕获:支持从视频捕获设备(如摄像头)中获取视频数据。V4L2 提供了多种视频格式和帧率的支持。
-
视频输出:支持将视频数据发送到视频输出设备(如显示器或其他显示设备)。
-
控制接口:允许应用程序控制视频设备的各种参数,如亮度、对比度、饱和度等。
-
内存映射和用户指针:提供了高效的数据传输机制,包括内存映射(mmap)和用户指针(user pointer)方式,使得视频数据的捕获和处理更加高效。
二、 buffer的管理
使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。
摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。
驱动程序周而复始地做如下事情:
-
从硬件采集到数据
-
把"空闲链表"取出buffer,把数据存入buffer
-
把含有数据的buffer放入"完成链表"
APP也会周而复始地做如下事情:
-
监测"完成链表",等待它含有buffer
-
从"完成链表"中取出buffer
-
处理数据
-
把buffer放入"空闲链表"
链表操作示意图如下:
三、 完整的使用流程
参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:
-
open:打开设备节点/dev/videoX
-
ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如
-
确认它是否是"捕获设备",因为有些节点是输出设备
-
确认它是否支持mmap操作,还是仅支持read/write操作
-
-
ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
-
ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
-
ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
-
ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射
-
如果申请到了N个buffer,这个ioctl就应该执行N次
-
执行mmap后,APP就可以直接读写这些buffer
-
-
ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
-
如果申请到了N个buffer,这个ioctl就应该执行N次
-
-
ioctl VIDIOC_STREAMON:启动摄像头
-
这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
-
poll/select
-
ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer
-
处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer
-
ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
-
-
ioctl VIDIOC_STREAMOFF:停止摄像头
四、帧数据遇到的问题:
问题一、linux/videodev.h:没有那个文件或目录
解决:
sudo apt-get install libv4l-dev
cd /usr/include/linux
sudo ln -s ../libv4l1-videodev.h videodev.h
五、帧数据采集代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
/* ./video_test </dev/video0> */
int main(int argc, char **argv)
{
int fd;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_frmsizeenum fsenum;
int fmt_index = 0;
int frame_index = 0;
if (argc != 2)
{
printf("Usage: %s </dev/videoX>, print format detail for video device\n", argv[0]);
return -1;
}
/* open */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
while (1)
{
/* 枚举格式 */
fmtdesc.index = fmt_index; // 比如从0开始
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"
if (0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
break;
frame_index = 0;
while (1)
{
/* 枚举这种格式所支持的帧大小 */
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
{
printf("format %s,%d, framesize %d: %d x %d\n", fmtdesc.description, fmtdesc.pixelformat, frame_index, fsenum.discrete.width, fsenum.discrete.height);
}
else
{
break;
}
frame_index++;
}
fmt_index++;
}
return 0;
}
调用ioctl VIDIOC_ENUM_FMT可以枚举摄像头支持的格式,但是无法获得更多细节(比如支持哪些分辨率),
调用ioctl VIDIOC_G_FMT可以获得"当前的格式",包括分辨率等细节,但是无法获得其他格式的细节。
需要结合VIDIOC_ENUM_FMT、VIDIOC_ENUM_FRAMESIZES这2个ioctl来获得这些细节:
-
VIDIOC_ENUM_FMT:枚举格式
-
VIDIOC_ENUM_FRAMESIZES:枚举指定格式的帧大小(即分辨率)
输出结果:
参考资料:百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发