[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);  // 解除映射
    }
}

posted @ 2025-06-12 11:22  Emma1111  阅读(323)  评论(0)    收藏  举报