4. Camera HAL Buffer 管理核心:分配、注册、入队、出队、取消流程

Camera HAL 层的 Buffer 管理是连接上层 Framework 与底层 Kernel 驱动的关键桥梁。本章深入剖析 Buffer 从分配到取消的完整生命周期,涵盖分配、注册、入队、出队、取消五个核心环节,并揭示每个环节在 HAL 与驱动之间的交互细节。

核心认知:Buffer 管理本质是“生产者-消费者”模型在 Camera 系统中的具体实现。HAL 作为中间层,既要满足 Framework 对 Buffer 的灵活控制需求,又要遵循 Kernel 驱动对 DMA-BUF 和 Videobuf2 的严格约束。

4.1 Buffer 生命周期全景图

下图展示了 Buffer 从分配开始到最终取消的完整状态流转,以及各阶段涉及的关键模块。

Buffer 生命周期状态机与关键操作 分配 (Allocate) ION/DMA-BUF 注册 (Register) Videobuf2 导入 入队 (Queue) QBUF 出队 (Dequeue) DQBUF 取消 (Cancel) STREAMOFF 释放 (Free) ION 释放 关键说明: • 分配阶段:HAL 通过 ION/DMA-BUF 分配物理连续或非连续内存,获取 fd。 • 注册阶段:将 fd 导入 Videobuf2 框架,建立驱动侧 Buffer 对象。 • 取消流程:通过 STREAMOFF 将已入队但未处理的 Buffer 全部返回 HAL。 • 释放阶段:关闭 fd,归还内存给 ION 堆。

4.2 Buffer 分配:从 ION 到 DMA-BUF

HAL 层 Buffer 分配的核心目标是获取一块可供 Camera 硬件直接访问的内存。现代 Android 系统已从 ION 逐步迁移至 DMA-BUF Heaps,但底层原理一致。

4.2.1 分配接口与参数

参数 说明 典型值
Heap 类型 指定内存来源,决定物理连续性 DMA-BUF: SYSTEM / CARVEOUT
Buffer 大小 单帧数据所需字节数 width × height × bpp × planes
对齐要求 硬件对起始地址的对齐约束 4KB / 2MB
Flag 标志 读写权限、缓存一致性 O_RDWR | O_CLOEXEC

4.2.2 分配代码示例

// HAL 层 Buffer 分配示例(基于 DMA-BUF Heaps)
int allocateCameraBuffer(uint32_t size, uint32_t heap_flags) {
    struct dma_heap_allocation_data data = {0};
    int fd, ret;

    // 打开 DMA-BUF heap 设备
    fd = open("/dev/dma_heap/system", O_RDWR);
    if (fd < 0) {
        ALOGE("Failed to open DMA heap: %s", strerror(errno));
        return -1;
    }

    // 填充分配参数
    data.len = size;
    data.fd_flags = O_RDWR | O_CLOEXEC;
    data.heap_flags = heap_flags;
    data.fd = 0;

    // 执行分配 ioctl
    ret = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
    if (ret < 0) {
        ALOGE("DMA heap alloc failed: %s", strerror(errno));
        close(fd);
        return -1;
    }

    close(fd);
    return data.fd;  // 返回 dma-buf fd
}
实战提示:分配成功后返回的 fd 是跨进程传递的关键句柄。HAL 需要将此 fd 通过 V4L2_QBUF 的 m.fd 字段传递给驱动,驱动通过 dma_buf_get() 获取内核态引用。

4.3 Buffer 注册:Videobuf2 导入机制

注册阶段的核心任务是将 HAL 分配的 DMA-BUF fd 导入到 Kernel 的 Videobuf2 框架中,建立驱动可识别的 Buffer 对象。

4.3.1 注册流程

  1. HAL 调用 REQBUFS: 设置 buffer count 和 memory type 为 V4L2_MEMORY_DMABUF。
  2. 驱动创建 vb2_queue: 初始化 videobuf2 队列,注册 buf_ops 回调。
  3. HAL 调用 QBUF 传入 fd: 驱动通过 vb2_dma_contig_memops 或自定义 mem_ops 导入 dma-buf。
  4. 驱动建立 sg_table: 将 dma-buf 映射为硬件可访问的 scatterlist。
  5. 返回 index: 驱动分配 buffer index,HAL 后续通过 index 操作该 buffer。

4.3.2 注册阶段关键数据结构

// Kernel 侧 Videobuf2 Buffer 结构
struct vb2_v4l2_buffer {
    struct v4l2_buffer vb2_buf;      // V4L2 标准 buffer 结构
    struct vb2_buffer *vb2_plane;    // 每个 plane 对应一个 dma-buf
    unsigned int index;              // buffer 索引
    struct list_head queued_entry;   // 入队链表节点
};

// HAL 侧 BufferRecord
struct CameraBufferRecord {
    int fd;                          // dma-buf fd
    int index;                       // 驱动返回的 buffer index
    uint32_t width, height, stride;
    uint64_t timestamp;
    buffer_status_t status;          // DEQUEUED / QUEUED / ERROR
};

4.4 Buffer 入队与出队:核心数据流

入队(QBUF)和出队(DQBUF)是 Buffer 流转最频繁的操作,直接影响帧率与延迟。

4.4.1 入队流程

步骤 HAL 操作 驱动响应
1 填充 v4l2_buffer,设置 index、timestamp、bytesused 验证 buffer index 有效性
2 调用 QBUF ioctl 调用 buf_ops->buf_queue() 回调
3 等待驱动处理完成 硬件开始填充数据
4 调用 DQBUF 获取已填充 buffer 返回填充完成的 buffer,更新 timestamp/bytesused

4.4.2 入队/出队代码示例

// HAL 层 Buffer 入队
int queueBuffer(int fd, int index, uint64_t timestamp) {
    struct v4l2_buffer buf = {0};
    struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{0}};

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_DMABUF;
    buf.index = index;
    buf.timestamp.tv_sec = timestamp / 1000000000;
    buf.timestamp.tv_usec = (timestamp % 1000000000) / 1000;
    buf.length = 1;
    buf.m.planes = planes;

    // 设置 dma-buf fd
    planes[0].m.fd = fd;
    planes[0].length = buffer_size;
    planes[0].bytesused = 0;

    if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
        ALOGE("QBUF failed: %s", strerror(errno));
        return -1;
    }
    return 0;
}

// HAL 层 Buffer 出队
int dequeueBuffer(int fd, uint64_t *timestamp) {
    struct v4l2_buffer buf = {0};
    struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{0}};

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_DMABUF;
    buf.length = 1;
    buf.m.planes = planes;

    if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
        ALOGE("DQBUF failed: %s", strerror(errno));
        return -1;
    }

    *timestamp = (uint64_t)buf.timestamp.tv_sec * 1000000000 +
                 (uint64_t)buf.timestamp.tv_usec * 1000;
    return buf.index;
}

4.5 Buffer 取消:STREAMOFF 与 Buffer 回收

取消操作通常发生在流停止(STREAMOFF)或异常恢复场景。核心目标是:将驱动中所有已入队但未处理的 Buffer 安全返回给 HAL。

4.5.1 取消触发条件

  • 正常停止: 应用层调用 stopPreview() 或 close()。
  • 异常恢复: 驱动检测到硬件超时,主动触发 cancel。
  • Buffer 泄漏检测: HAL 监控到长时间未返回的 buffer,主动发起 cancel。

4.5.2 取消流程详解

  1. HAL 调用 STREAMOFF: 发送 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型。
  2. 驱动停止硬件: 立即停止 DMA 传输,防止新数据写入。
  3. 驱动遍历 queued_list: 将所有处于 QUEUED 状态的 buffer 标记为 DEQUEUED。
  4. 驱动调用 buf_ops->buf_finish(): 清理硬件相关的缓存同步。
  5. 驱动通过 DQBUF 返回所有 buffer: HAL 循环调用 DQBUF 直到返回 -EAGAIN。
  6. HAL 更新内部状态: 将所有 buffer 状态重置为 DEQUEUED,准备重新入队。

4.5.3 取消流程时序图

Buffer 取消流程时序 HAL Videobuf2 Camera 驱动 STREAMOFF 停止硬件 DMA 遍历 queued_list buf_finish() 回调 DQBUF 返回 -EAGAIN 重置 buffer 状态 注意:取消后所有 buffer 必须通过 DQBUF 返回,否则会导致 buffer 泄漏,后续 REQBUFS 可能失败。

4.6 常见问题与调试技巧

问题现象 根因分析 调试方法
QBUF 返回 -EINVAL fd 无效或 buffer index 越界 检查 fd 是否已关闭,index 是否在 [0, count) 范围内
DQBUF 一直阻塞 驱动未完成处理或 buffer 丢失 检查驱动中断是否正常,使用 trace-cmd 跟踪 vb2 事件
STREAMOFF 后 DQBUF 仍返回数据 驱动未正确停止 DMA 检查驱动 stop_streaming 回调是否完整
Buffer 泄漏(count 持续减少) HAL 未及时 DQBUF 或驱动未返回 在 HAL 侧打印 buffer 状态统计,监控 queued/dequeued 数量
调试红线:切勿在中断上下文中直接调用 DQBUF 或 QBUF。Videobuf2 的队列操作必须在进程上下文进行,否则会导致内核死锁。建议使用 workqueue 将 buffer 操作推迟到进程上下文执行。

4.7 本章小结

Buffer 管理的五个核心环节构成了 Camera HAL 与驱动交互的主干:

  • 分配: 通过 DMA-BUF Heaps 获取物理内存,返回跨进程 fd。
  • 注册: 将 fd 导入 Videobuf2,建立驱动侧 buffer 对象。
  • 入队: 将空闲 buffer 交给驱动,等待硬件填充数据。
  • 出队: 从驱动取回已填充数据的 buffer,交给上层处理。
  • 取消: 安全回收所有 buffer,确保状态一致性。