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 |
视频叠加层(较少使用) |
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层的主流方案。
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 典型调用流程
- 设置参数:先通过
VIDIOC_S_FMT设置图像格式(分辨率、像素格式)。 - 申请Buffer:调用
VIDIOC_REQBUFS,传入count、type、memory。 - 检查实际数量:驱动可能返回小于请求的
count,HAL层必须使用返回的实际值。 - 分配内存:对于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 查询流程
- 循环查询:对每个索引(0到count-1)调用
VIDIOC_QUERYBUF。 - 获取内存信息:根据
memory字段,读取m.offset(MMAP)、m.userptr(USERPTR)或m.fd(DMABUF)。 - 建立映射:对于MMAP,使用
mmap()将offset映射到用户空间;对于DMABUF,直接使用fd进行跨硬件共享。
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 |
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)释放。