目录
V4L2 视频采集流程
代码例子
核心命令字和结构体
VIDIOC_ENUM_FMT
VIDIOC_G_FMT / VIDIOC_S_FMT / VIDIOC_TRY_FM
VIDIOC_REQBUFS
VIDIOC_QUERYBUF
VIDIOC_QBUF /VIDIOC_DQBUF
VIDIOC_STREAMON / VIDIOC_STREAMOFF
V4L2 是 Linux 处理视频的最新标准代码模块,这其中包括对视频输入设备的处理,比 如高频头(即电视机信号输入端子)或者摄像头,还包括对视频输出设备的处理。一般而言, 最常见的是使用 V4L2 来处理摄像头数据采集的问题
我们平常所使用的摄像头,实际上就是一个图像传感器,将光线捕捉到之后经过视频芯 片的处理,编码成 JPG/MJPG 或者 YUV 格式输出。而通过 V4L2 我们可以很方便地跟摄像 头等视频设备“沟通”,比如设置或者获取它们的工作参数
V4L2 视频采集流程
在内核中,摄像头所捕获的视频数据,我们可以通过一个队列来存储,我们所做的工作 大致是这样的:首先配置好摄像头的相关参数,使之能正常工作,然后申请若干个内核视频 缓存,并且将它们一一送到队列中,就好比三个空盘子被一一放到传送带上一样。然后我们 还需要将这三个内核的缓存区通过 mmap 函数映射到用户空间,这样我们在用户层就可以 操作摄像头数据了,紧接着我们就可以启动摄像头了开始数据捕获,每捕获一帧数据我们就 可以做一个出队操作,读取数据,然后将读过数据的内核缓存再次入队,依次循环
代码例子
// 1,打开摄像头设备文件
int cam_fd = open("/dev/video3", O_RDWR);
// 2,获取摄像头当前的采集格式
struct v4l2_format *fmt = calloc(1, sizeof(*fmt));
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam_fd, VIDIOC_G_FMT, fmt);
show_camfmt(fmt); // 显示具体参数(详见 v4l2_jpeg_videostream.c)
// 3,配置摄像头的采集格式为 JPEG
bzero(fmt, sizeof(*fmt));
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt->fmt.pix.width = lcdinfo.xres;
fmt->fmt.pix.height = lcdinfo.yres;
fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
fmt->fmt.pix.field = V4L2_FIELD_INTERLACED;
ioctl(cam_fd, VIDIOC_S_FMT, fmt);
// 4,设置即将要申请的摄像头缓存的参数
int nbuf = 3;
struct v4l2_requestbuffers reqbuf;
bzero(&reqbuf, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = nbuf;
// 5,使用该参数 reqbuf 来申请缓存
ioctl(cam_fd, VIDIOC_REQBUFS, &reqbuf);
// 6,根据刚设置的 reqbuf.count 的值,来定义相应数量的 struct v4l2_buffer
// 每一个 struct v4l2_buffer 对应内核摄像头驱动中的一个缓存
struct v4l2_buffer buffer[nbuf];
int length[nbuf];
unsigned char *start[nbuf];
for (i = 0; i < nbuf; i++) {
bzero(&buffer[i], sizeof(buffer[i]));
buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer[i].memory = V4L2_MEMORY_MMAP;
buffer[i].index = i;
ioctl(cam_fd, VIDIOC_QUERYBUF, &buffer[i]);
length[i] = buffer[i].length;
start[i] = mmap(NULL, buffer[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, cam_fd, buffer[i].m.offset);
ioctl(cam_fd, VIDIOC_QBUF, &buffer[i]);
}
// 7,启动摄像头数据采集
enum v4l2_buf_type vtype = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam_fd, VIDIOC_STREAMON, &vtype);
struct v4l2_buffer v4lbuf;
bzero(&v4lbuf, sizeof(v4lbuf));
v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4lbuf.memory = V4L2_MEMORY_MMAP;
// 8,循环读取摄像头数据
i = 0;
while (1) {
// 从队列中取出填满数据的缓存
v4lbuf.index = i % nbuf;
// VIDIOC_DQBUF 在摄像头没数据的时候会阻塞
ioctl(cam_fd, VIDIOC_DQBUF, &v4lbuf);
shooting(start[i % nbuf], length[i % nbuf], fb_mem); // 显示到 LCD
// 将已经读取过数据的缓存块重新置入队列中
v4lbuf.index = i % nbuf;
ioctl(cam_fd, VIDIOC_QBUF, &v4lbuf);
i++;
}
核心命令字和结构体
VIDIOC_ENUM_FMT
含义:枚举出当前摄像头(驱动)所支持的所有数据格式
使用方法:ioctl(fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *argp);
通过迭代结构体 struct v4l2_fmtdesc 中的 index 成员,来枚举罗列支持的所有格式, 该结构体的详细信息如下
struct v4l2_fmtdesc
{
__u32 index; // 数据格式的索引
__u32 type; // 一般设置为 V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 flags;
__u8 description[32];
__u32 pixelformat;//表示像素格式的值,是一个32位的标识符,用于指定视频数据的像素编码格式,如 YUV420、RGB24 等
__u32 reserved[4];
};
- 其中 type 跟 v4l2_format 中的 type 设置要一致。
- 在成功调用ioctl 之后,description 将保存对当前获取的数据格式的描述。
VIDIOC_G_FMT / VIDIOC_S_FMT / VIDIOC_TRY_FM
含义: 1,获取当前摄像头驱动数据格式 2,设置摄像头驱动数据格式 3,尝试设置格式
具体用法:
- ioctl(fd, VIDIOC_G_FMT, struct v4l2_format *argp);
- ioctl(fd, VIDIOC_S_FMT, struct v4l2_format *argp);
- ioctl(fd, VIDIOC_TRY_FMT, struct v4l2_format *argp);
涉及数据结构
struct v4l2_format
{
__u32 type;
union
{
struct v4l2_pix_format pix;
struct v4l2_pix_format_mplane pix_mp;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced; __u8 raw_data[200];
} fmt;
};
- V4l2_format 中的 fmt 是一个 union,其中哪个成员有效取决于 type 的取值,
- 一般较常用的是取类型 type 为 V4L2_BUF_TYPE_VIDEO_CAPTURE,此时 pix 生效。
struct v4l2_pix_format
{
__u32 width;
__u32 height;
__u32 pixelformat;
__u32 field;
__u32 bytesperline;
__u32 sizeimage;
__u32 colorspace;
__u32 priv;
};
- 该结构体中的成员 pixelformat 代表视频输入驱动所使用的像素格式,常见的有 V4L2_PIX_FMT_JPEG、V4L2_PIX_FMT_YUV、V4L2_PIX_FMT_MJPG等。
- 而成员field 代表视频帧传输的方式,选择 V4L2_FIELD_INTERLACED 为交错式
VIDIOC_REQBUFS
含义:向内核申请视频缓存(内核中处理视频数据的队列缓存)
用法: ioctl(fd, VIDIOC_REQBUFS, v4l2_requestbuffers *argp);
struct v4l2_requestbuffers
{
__u32 count; // 申请缓存总个数
__u32 type; // 与 struct v4l2_format 中的 type 一致
__u32 memory;
__u32 reserved[2];
};
- 其中 memory 的取值为 V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR, 取决于,当该字段被设置为 V4L2_MEMORY_MMAP 时,count 字段才有效。
VIDIOC_QUERYBUF
含义:内核成功分配了缓存后,取得这些缓存的具体参数
用法: ioctl(fd, VIDIOC_QUERYBUF, v4l2_buffer *argp);
取得这些缓存的具体参数的目的是:这些缓存都是处在内核空间的,我们并不能直接操作他们,因此需要将他们通过 mmap 映射到用户空间,这就要求必须知道他们的大小、偏移等信息。这些信息统一被储存到如下结构体中
struct v4l2_buffer
{
__u32 index; // 内核缓存索引号,由用户指定,范围是[0 ~ count-1]
__u32 type; // 与 v4l2_format 中的 type 一致
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
__u32 memory; // 与 v4l2_requestbuffers 中的 memory 一致
union
{
__u32 offset; // 缓存相对于设备内存的偏移
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length; // 缓存大小
__u32 reserved2;
__u32 reserved;
};
VIDIOC_QBUF /VIDIOC_DQBUF
含义:
- 使一个空的(视频输入时)或者一个满的(视频输出时)缓存入队
- 使一个满的(视频输入时)或者一个空的(视频输出时)缓存出队
用法:
- ioctl(fd, VIDIOC_QBUF, v4l2_buffer *argp);
- ioctl(fd, VIDIOC_DQBUF, v4l2_buffer *argp);
- 在尚未开启摄像头取像之前,需要将空的缓存一一入队。
- 针对视频输入,出队的时候如果缓存没有数据,那么出队将阻塞。
- 虽然内核对这些内存的定义是“队列”,但实际上不按顺序“加塞(插队)”也是 可以的。但一般不那么做
VIDIOC_STREAMON / VIDIOC_STREAMOFF
含义:
- 开启 I/O 流
- 关闭 I/O 流
用法:
- ioctl(fd, VIDIOC_STREAMON, const int *argp);
- ioctl(fd, VIDIOC_STREAMOFF, const int *argp);
不管 I/O 方式被设定为内存映射(MMAP)方式还是用户指针(USERPTR)方式,都可以使用 VIDIOC_STREAMON 和 VIDIOC_STREAMOFF 来启停 I/O 流。事实上,在使用 ioctl 调用 VIDIOC_STREAMON 之前,物理硬件将暂时被禁用且没有缓存被填充数据。
VIDIOC_STREAMOFF 除了终止进程的 DMA 操作(如果有的话)之外,还将解锁用户指针指向的物理内存,队列中的所有缓存都将被移除,这意味着如果是视频输入,那么那些没来得及读取的视频帧将被丢弃,如果是视频输出,那么那写没来及传输的视频帧也同样会被丢弃