[v4l2] V4L2框架的buffer轮转机制
v4l2的buffer轮转机制是Linux视频设备驱动中用于管理视频数据缓冲区的核心机制,主要用于摄像头采集、视频流处理等场景。核心思想是通过缓冲区队列实现高效的数据流转,减少数据拷贝和延迟。
1.V4L2的Buffer管理模型
V4L2使用"生产者-消费者"模型:
- 生产者(摄像头硬件)填充buffer缓冲区,并交给驱动
- 消费者(用户态应用程序)从驱动获取已填充的buffer,处理后再返还给驱动。
缓冲区通常以队列(Queue)结构来管理,分为3种模式:
1.MMAP(内存映射):用户态直接访问内核分配的缓冲区(共享内存,零拷贝)
2.UserPTR:用户自行分配缓冲区,不过需要驱动的支持
3.DMABUF:基于DMA的缓冲区共享(适合跨设备/GPU交互)
2.Buffer轮转流程(以MMAP为例)
2.1 初始化缓冲区队列
struct v4l2_requestbuffers req = {
.count = 4, // 申请4个缓冲区
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP
};
ioctl(fd, VIDIOC_REQBUFS, &req); // 向驱动申请缓冲区
驱动分配count个buffer,每个buffer可通过VIDIOC_QUERYBUF查询详细信息(物理地址、buffer大小)
2.2 内存映射(MMAP)
void *buffers[req.count];
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP,
.index = i
};
ioctl(fd, VIDIOC_QUERYBUF, &buf); // 查询Buffer信息
buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); // 映射到用户空间
ioctl(fd, VIDIOC_QBUF, &buf); // 将Buffer加入驱动输入队列
}
2.3 启动视频流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type); // 开始采集
2.4 Buffer在用户态到驱动轮转流程
轮转操作:VIDIOC_QBUF->硬件填充->VIDIOC_DQBUF->处理->VIDIOC_QBUF
1.驱动填充Buffer
Camera硬件将数据写入驱动队列中的空闲Buffer
2.用户获取Buffer
通过VIDIOC_DQBUF从驱动输出队列获取已填充的Buffer
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP
};
ioctl(fd, VIDIOC_DQBUF, &buf); // 取出已填充的Buffer(阻塞/非阻塞)
process_data(buffers[buf.index]); // 处理数据
3.返还buffer
处理完成后,通过VIDIOC_QBUF将Buffer重新加入驱动输入队列
ioctl(fd, VIDIOC_QBUF, &buf); // 返还Buffer,驱动可复用
4.buffer轮转的触发
Camera硬件采集完一帧后会触发中断,驱动将Buffer移至输出队列,用户态应用再通过VIDIOC_QBUF/VIDIOC_DQBUF主动轮转
3.小结
V4L2的Buffer队列是双队列设计
- 输入队列:通过VIDIOC_QBUF提交到空Buffer
- 输出队列:通过VIDIOC_DQBUF获取到已填充的buffer
默认情况下,VIDIOC_DQBUF会阻塞直到有数据到达。设置O_NONBLOCK标志后可以改为非阻塞模式
4.代码demo
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
int main() {
int fd = open("/dev/video0", O_RDWR);
// 初始化Buffer队列(MMAP模式)
struct v4l2_requestbuffers req = { .count = 4, ... };
ioctl(fd, VIDIOC_REQBUFS, &req);
// 映射Buffer到用户空间
void *buffers[4];
for (int i = 0; i < 4; i++) {
struct v4l2_buffer buf = { .index = i, ... };
ioctl(fd, VIDIOC_QUERYBUF, &buf);
buffers[i] = mmap(..., fd, buf.m.offset);
ioctl(fd, VIDIOC_QBUF, &buf);
}
// 启动流
ioctl(fd, VIDIOC_STREAMON, &type);
// 轮转处理
while (1) {
struct v4l2_buffer buf = { ... };
ioctl(fd, VIDIOC_DQBUF, &buf); // 获取数据
process(buffers[buf.index]);
ioctl(fd, VIDIOC_QBUF, &buf); // 返还Buffer
}
// 退出时记得接触映射,防止内存泄漏
_exit:
ioctl(fd, VIDIOC_STREAMOFF, &type); // 停止流
for (int i = 0; i < req.count; i++)
{
munmap(buffers[i], buf.length); // 解除映射
}
}

浙公网安备 33010602011771号