4. V4L2 Buffer管理机制:V4L2_BUF_TYPE_VIDEO_CAPTURE、V4L2_MEMORY_MMAP/USERPTR/DMABUF、requestbuffers与querybuf流程

V4L2 Buffer管理是Camera HAL层数据流的核心。从Sensor捕获的原始图像数据,必须通过Buffer在驱动、内核空间与用户空间之间高效流转。本章深入剖析Buffer的类型、内存模型以及申请与查询的完整流程。

4.1 Buffer类型:V4L2_BUF_TYPE_VIDEO_CAPTURE

V4L2定义了多种Buffer类型,用于区分数据流的方向与格式。在Camera HAL层中,最核心的类型是V4L2_BUF_TYPE_VIDEO_CAPTURE,代表从摄像头设备捕获视频帧。

Buffer类型 宏定义 典型用途
视频捕获 V4L2_BUF_TYPE_VIDEO_CAPTURE 从Sensor/ISP获取原始或处理后的帧
视频输出 V4L2_BUF_TYPE_VIDEO_OUTPUT 向显示设备或编码器发送帧
视频捕获多平面 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE YUV/RAW等分离平面的捕获
视频输出多平面 V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE 多平面格式的输出
Overlay V4L2_BUF_TYPE_VIDEO_OVERLAY 视频叠加层(较少使用)
实践提示:在HAL层实现中,绝大多数Camera设备使用V4L2_BUF_TYPE_VIDEO_CAPTURE(单平面)或V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE(多平面)。对于MIPI RAW10/RAW12等格式,通常使用多平面类型以分离像素数据与元数据。

4.2 内存模型:MMAP / USERPTR / DMABUF

V4L2支持三种内存模型,决定了Buffer在内核与用户空间之间的映射方式。选择哪种模型直接影响性能、内存占用以及与其他框架(如ION/DMA-BUF)的集成能力。

内存模型 宏定义 数据流向 典型场景
MMAP V4L2_MEMORY_MMAP 内核分配 → 用户mmap映射 简单预览、快照,无需跨进程共享
USERPTR V4L2_MEMORY_USERPTR 用户分配 → 内核通过指针访问 需要用户控制内存分配的场景
DMABUF V4L2_MEMORY_DMABUF 用户导出DMA-BUF → 内核导入 跨硬件(ISP/GPU/Display)零拷贝共享

4.2.1 MMAP(内存映射)

内核驱动负责分配物理连续或非连续的内存,用户空间通过mmap()系统调用将内核空间地址映射到用户空间。这是最传统、兼容性最好的方式,但无法实现跨进程零拷贝。

4.2.2 USERPTR(用户指针)

用户空间预先分配内存(如通过malloc或posix_memalign),将用户空间虚拟地址传递给驱动。驱动通过get_user_pages锁定物理页面并进行DMA操作。这种方式灵活性高,但存在页面锁定开销和内存碎片风险。

4.2.3 DMABUF(DMA缓冲区)

基于Linux内核的DMA-BUF框架,用户空间从ION、DMA_HEAP或GPU驱动导出DMA-BUF文件描述符,再传递给V4L2驱动。驱动直接操作DMA-BUF对应的物理内存,实现零拷贝。这是Android Camera HAL层的主流方案。

性能对比:在4K/8K高分辨率场景下,DMABUF相比MMAP可减少约30%~50%的内存拷贝开销,同时降低CPU占用率。
V4L2 三种内存模型对比 用户空间 (mmap) 内核驱动 (分配) MMAP:内核分配,用户映射 用户空间 (分配) 内核驱动 (锁定+访问) USERPTR:用户分配,内核锁定 用户空间 (导出FD) 内核驱动 (导入FD) DMABUF:用户导出FD,内核导入 关键选择依据 • MMAP:最简单,适合单进程预览,无需跨硬件共享 • USERPTR:灵活但性能一般,已逐渐被DMABUF取代 • DMABUF:Android推荐方案,支持零拷贝跨硬件(ISP→GPU→Display)

4.3 Request Buffers:申请Buffer

在开始数据流之前,HAL层必须通过VIDIOC_REQBUFS ioctl向驱动申请Buffer。这个步骤决定了Buffer的数量、类型和内存模型。

4.3.1 数据结构

struct v4l2_requestbuffers {
    __u32 count;        // 请求的Buffer数量(驱动可能调整)
    __u32 type;         // Buffer类型,如 V4L2_BUF_TYPE_VIDEO_CAPTURE
    __u32 memory;       // 内存模型,如 V4L2_MEMORY_MMAP
    __u32 capabilities; // 驱动能力标志(只读)
    __u32 reserved[1];
};

4.3.2 典型调用流程

  1. 设置参数:先通过VIDIOC_S_FMT设置图像格式(分辨率、像素格式)。
  2. 申请Buffer:调用VIDIOC_REQBUFS,传入count、type、memory。
  3. 检查实际数量:驱动可能返回小于请求的count,HAL层必须使用返回的实际值。
  4. 分配内存:对于MMAP模式,驱动在内核中分配内存;对于DMABUF,用户空间准备DMA-BUF。
常见陷阱:如果驱动返回的count为0,表示不支持该内存模型或类型。HAL层应回退尝试其他内存模型(如MMAP降级)。

4.4 Query Buffers:查询Buffer信息

申请Buffer后,HAL层需要查询每个Buffer的详细信息,包括偏移量、长度、文件描述符等,以便进行内存映射或导入。

4.4.1 数据结构

struct v4l2_buffer {
    __u32 index;        // Buffer索引,从0到count-1
    __u32 type;         // Buffer类型
    __u32 bytesused;    // 实际数据长度(驱动填充)
    __u32 flags;        // 状态标志
    __u32 field;        // 场序
    struct timeval timestamp; // 时间戳
    struct v4l2_timecode timecode;
    __u32 sequence;     // 帧序列号
    __u32 memory;       // 内存模型
    union {
        __u32 offset;   // MMAP模式:偏移量
        unsigned long userptr; // USERPTR模式:用户地址
        int fd;         // DMABUF模式:文件描述符
    } m;
    __u32 length;       // Buffer长度
    __u32 reserved2;
    __u32 reserved;
};

4.4.2 查询流程

  1. 循环查询:对每个索引(0到count-1)调用VIDIOC_QUERYBUF。
  2. 获取内存信息:根据memory字段,读取m.offset(MMAP)、m.userptr(USERPTR)或m.fd(DMABUF)。
  3. 建立映射:对于MMAP,使用mmap()将offset映射到用户空间;对于DMABUF,直接使用fd进行跨硬件共享。
Request Buffers & Query Buffers 完整流程 1. S_FMT 设置格式 2. REQBUFS 申请Buffer 3. 驱动分配内存 4. QUERYBUF 查询每个Buffer 5. 获取 offset / fd / userptr 6. mmap / 导入FD 关键代码片段:REQBUFS + QUERYBUF struct v4l2_requestbuffers req = {0}; req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_DMABUF; ioctl(fd, VIDIOC_REQBUFS, &req); // 申请4个DMABUF Buffer for (int i = 0; i < req.count; i++) { struct v4l2_buffer buf = {0}; buf.index = i; buf.type = req.type; buf.memory = req.memory; ioctl(fd, VIDIOC_QUERYBUF, &buf); // 获取buf.m.fd (DMABUF文件描述符) }

4.5 完整Buffer生命周期

理解Buffer从申请到回收的完整生命周期,有助于调试数据流异常:

阶段 ioctl / 操作 状态 说明
1. 申请 VIDIOC_REQBUFS 已分配 驱动分配内存,Buffer处于空闲池
2. 查询 VIDIOC_QUERYBUF 已映射 用户获取地址/FD,完成mmap或导入
3. 入队 VIDIOC_QBUF 已入队 Buffer放入驱动待处理队列,等待硬件填充
4. 出队 VIDIOC_DQBUF 已填充 驱动完成填充,用户获取数据
5. 再入队 VIDIOC_QBUF 循环 用户处理完数据后重新入队,形成流水线
6. 释放 VIDIOC_REQBUFS(count=0) 已释放 传入count=0,驱动释放所有Buffer
调试技巧:当出现“Buffer不足”或“DQBUF超时”时,检查VIDIOC_REQBUFS返回的实际count是否满足流水线深度需求。通常预览需要至少3~4个Buffer,高帧率场景需要5~8个。

4.6 常见问题与避坑指南

  • MMAP映射失败:检查mmap的length参数是否与v4l2_buffer.length一致,以及offset是否按页对齐。
  • DMABUF导入失败:确保DMA-BUF文件描述符来自同一硬件IOMMU域,或已通过ION/DMA_HEAP正确分配。
  • USERPTR页面锁定失败:用户内存必须使用mmap或posix_memalign分配,且不能是栈内存或malloc小内存。
  • REQBUFS返回0:驱动可能不支持请求的内存模型,尝试切换为V4L2_MEMORY_MMAP作为降级方案。
  • Buffer泄漏:确保在停止流(VIDIOC_STREAMOFF)后,所有Buffer都已通过DQBUF回收,再调用REQBUFS(count=0)释放。