4. Camera HAL Buffer 管理核心:分配、注册、入队、出队、取消流程
Camera HAL 层的 Buffer 管理是连接上层 Framework 与底层 Kernel 驱动的关键桥梁。本章深入剖析 Buffer 从分配到取消的完整生命周期,涵盖分配、注册、入队、出队、取消五个核心环节,并揭示每个环节在 HAL 与驱动之间的交互细节。
核心认知:Buffer 管理本质是“生产者-消费者”模型在 Camera 系统中的具体实现。HAL 作为中间层,既要满足 Framework 对 Buffer 的灵活控制需求,又要遵循 Kernel 驱动对 DMA-BUF 和 Videobuf2 的严格约束。
4.1 Buffer 生命周期全景图
下图展示了 Buffer 从分配开始到最终取消的完整状态流转,以及各阶段涉及的关键模块。
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 注册流程
- HAL 调用 REQBUFS: 设置 buffer count 和 memory type 为 V4L2_MEMORY_DMABUF。
- 驱动创建 vb2_queue: 初始化 videobuf2 队列,注册 buf_ops 回调。
- HAL 调用 QBUF 传入 fd: 驱动通过
vb2_dma_contig_memops或自定义 mem_ops 导入 dma-buf。 - 驱动建立 sg_table: 将 dma-buf 映射为硬件可访问的 scatterlist。
- 返回 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 取消流程详解
- HAL 调用 STREAMOFF: 发送 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型。
- 驱动停止硬件: 立即停止 DMA 传输,防止新数据写入。
- 驱动遍历 queued_list: 将所有处于 QUEUED 状态的 buffer 标记为 DEQUEUED。
- 驱动调用 buf_ops->buf_finish(): 清理硬件相关的缓存同步。
- 驱动通过 DQBUF 返回所有 buffer: HAL 循环调用 DQBUF 直到返回 -EAGAIN。
- HAL 更新内部状态: 将所有 buffer 状态重置为 DEQUEUED,准备重新入队。
4.5.3 取消流程时序图
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,确保状态一致性。