11、Camera 管道与 Buffer 管理:Gralloc 图形缓冲区、Stream 配置、Request 处理

Camera 管道,说白了就是一条从镜头到屏幕的数据流水线。你按下快门那一刻,背后有几十个组件在疯狂协作。我当年第一次接触 Camera HAL 时,就被这套 Buffer 流转机制搞得晕头转向。今天咱们就把这块硬骨头啃下来。

11.1 整体架构:谁在管谁?

先看一张总览图,搞清楚 Camera 管道里各个角色是怎么串起来的。

Camera 管道 Buffer 流转总览 App / Camera2 API 发送 Capture Request Camera Framework 管理 Stream / Request Camera HAL 处理 Request / 填充 Buffer Gralloc 图形缓冲区分配器 分配 / 映射 / 管理 Buffer 内存 Preview Buffer SurfaceFlinger 消费 Capture Buffer App 直接读取 Raw / Depth Buffer 专用处理管线 Buffer 生命周期:分配 → 入队 → 填充 → 出队 → 消费 → 回收

这张图里,从上到下分了三层:App 发请求,Framework 做调度,HAL 真正干活。而 Gralloc 是底层的 Buffer 大管家,所有图形内存都归它管。

核心要点:Camera 管道的本质是 Buffer 的「生产者-消费者」模型。HAL 是生产者,SurfaceFlinger 或 App 是消费者。Buffer 在两者之间循环流转。

11.2 Gralloc:图形缓冲区的基石

Gralloc 是什么?它是 Android 图形系统的内存分配器。我习惯把它理解成「图形界的 malloc」。但它比普通内存分配复杂得多——它要处理连续物理内存、硬件压缩格式、跨进程共享等问题。

11.2.1 Gralloc 的核心职责

  • 分配 Buffer:根据宽、高、格式、用法标志,分配合适的图形内存。
  • 映射 Buffer:把物理内存映射到进程的虚拟地址空间,让 CPU 能读写。
  • 管理 Buffer 状态:跟踪 Buffer 是在 GPU、显示控制器还是 CPU 手里。
  • 跨进程共享:通过 Binder 或 dma-buf 把 Buffer 句柄传给其他进程。

我记得有一次调试一个预览花屏的问题,查了两天才发现是 Gralloc 分配的 Buffer 格式不匹配——HAL 输出的是 NV12,但 SurfaceFlinger 预期的是 YV12。嗯,这种坑踩过一次就长记性了。

11.2.2 Buffer 的用法标志

分配 Buffer 时,Gralloc 需要知道这个 Buffer 会被怎么使用。不同的用法标志决定了内存的分配策略:

标志 含义 典型场景
GRALLOC_USAGE_SW_READ_OFTEN CPU 经常读取 App 直接读取帧数据
GRALLOC_USAGE_SW_WRITE_OFTEN CPU 经常写入 软件编码器输入
GRALLOC_USAGE_HW_TEXTURE GPU 纹理采样 预览显示、滤镜处理
GRALLOC_USAGE_HW_CAMERA_WRITE Camera ISP 写入 HAL 填充预览/拍照数据
GRALLOC_USAGE_HW_VIDEO_ENCODER 视频编码器读取 录像场景

避坑指南:我曾经在某个平台上,给预览流分配了 SW_READ_OFTEN 标志,结果预览帧率直接掉到 15fps。原因是 CPU 读取操作触发了 cache 同步,拖慢了整个管线。后来改成 HW_TEXTURE 就正常了。所以,能用硬件就别用软件

11.3 Stream 配置:管道的蓝图

在 Camera 开始干活之前,App 必须先配置 Stream。Stream 就是一条数据通道,它定义了图像的尺寸、格式、用途。

11.3.1 Stream 配置的结构

一个典型的 Stream 配置包含以下信息:

// Camera3 中的 Stream 结构体
typedef struct camera3_stream {
    uint32_t      stream_type;   // CAMERA3_STREAM_OUTPUT / INPUT / BIDIRECTIONAL
    uint32_t      width;         // 图像宽度
    uint32_t      height;        // 图像高度
    int32_t       format;        // 像素格式,如 HAL_PIXEL_FORMAT_YCbCr_420_888
    uint32_t      usage;         // Gralloc 用法标志
    uint32_t      max_buffers;   // 最大 Buffer 数量
    void         *priv;          // HAL 私有数据
    android_dataspace_t data_space; // 色彩空间
    int32_t       rotation;      // 旋转角度
} camera3_stream_t;

你想想看,App 要同时预览和拍照,那就得配两个 Output Stream。一个给预览用(小尺寸、高帧率),一个给拍照用(全尺寸、高质量)。

11.3.2 配置流程

  1. App 调用 createCaptureSession,传入 Stream 列表。
  2. Framework 把 Stream 配置下发给 HAL 的 configure_streams 方法。
  3. HAL 检查配置是否支持,必要时调整参数。
  4. HAL 返回确认,Framework 开始分配 Buffer。
  5. Gralloc 根据 Stream 的 usage 标志分配 Buffer 池。

注意:configure_streams 是一个同步调用,HAL 必须在这个调用里完成所有资源准备。我见过一些 HAL 实现在这个函数里做耗时的 ISP 初始化,导致 App 启动预览卡顿。正确的做法是提前初始化,configure_streams 只做轻量级的配置检查。

11.4 Request 处理:管道的驱动引擎

Stream 配置好了,Buffer 也分配了,接下来就是 Request 驱动整个管道运转。每个 Request 对应一次拍照或一帧预览。

11.4.1 Request 的生命周期

我习惯把 Request 的处理分成四个阶段:

  • 提交阶段:App 通过 capturesetRepeatingRequest 提交 Request。
  • 排队阶段:Framework 把 Request 放入 HAL 的请求队列。
  • 处理阶段:HAL 从队列取出 Request,配置 ISP,填充 Buffer。
  • 返回阶段:HAL 把填充好的 Buffer 和 metadata 返回给 Framework。

这里有个关键点:Request 和 Buffer 是一一对应的。每个 Request 都指定了要填充哪些 Stream 的 Buffer。

11.4.2 同步与超时处理

Camera 管道是异步的。HAL 处理 Request 需要时间,App 不能干等。Framework 通过回调机制通知 App 结果。

但异步也带来了问题——如果 HAL 处理太慢怎么办?我曾经在一个低端平台上遇到预览超时,原因是 ISP 的 3A 算法收敛太慢,导致第一帧花了 500ms 才出来。解决方案是让 HAL 先返回一帧「空数据」给预览流,等 3A 稳定后再输出正常帧。

// 伪代码:Request 处理流程
void process_capture_request(camera3_capture_request_t *request) {
    // 1. 解析 Request 中的 settings(3A 参数、AE/AWB/AF 等)
    // 2. 配置 ISP 寄存器
    // 3. 等待帧同步信号(VSYNC)
    // 4. ISP 开始填充 Buffer
    // 5. 填充完成后,调用 process_capture_result 回调
    // 6. 通知 Framework 结果可用
}

经验之谈:处理 Request 时,一定要关注 Buffer 的 timestamp。这个时间戳是后续音视频同步的基础。我见过一个 bug,因为 HAL 返回的时间戳是单调时钟,而音频用的是实时时钟,结果录出来的视频音画不同步。后来统一用 CLOCK_MONOTONIC 才解决。

11.5 Buffer 管理实战:从分配到回收

Buffer 管理是 Camera 管道里最容易出问题的地方。咱们把整个生命周期走一遍。

11.5.1 Buffer 分配

Framework 在 configure_streams 之后,会调用 Gralloc 为每个 Stream 分配 Buffer 池。池的大小由 max_buffers 决定,通常预览流是 3-5 个,拍照流是 1-2 个。

11.5.2 Buffer 流转

Buffer 在以下角色之间流转:

  1. Framework 持有空闲 Buffer:等待被 Request 使用。
  2. HAL 持有填充中的 Buffer:ISP 正在往里面写数据。
  3. Framework 持有已填充 Buffer:等待 App 或 SurfaceFlinger 消费。
  4. 消费者持有 Buffer:App 读取像素数据,或 SurfaceFlinger 合成显示。
  5. Buffer 回到空闲池:消费者释放后,Buffer 回到 Framework 手中。

11.5.3 常见问题与排查

问题现象 可能原因 排查方法
预览卡顿、掉帧 Buffer 不足,HAL 等待空闲 Buffer 检查 max_buffers 是否过小
拍照黑帧 Buffer 格式不匹配,HAL 填充失败 检查 format 和 usage 标志
内存泄漏 Buffer 没有被正确释放回池 使用 dumpsys 查看 Buffer 计数
跨进程共享失败 dma-buf 句柄无效 检查 Binder 传输的 fd 是否可访问

调试技巧:adb shell dumpsys media.camera 可以查看当前 Camera 的 Stream 配置和 Buffer 状态。我每次遇到 Buffer 相关的问题,第一件事就是跑这个命令,看看 Buffer 是不是卡在某个环节了。

11.6 总结

Camera 管道与 Buffer 管理,说白了就是三件事:Gralloc 分配内存,Stream 定义通道,Request 驱动流转。这三者环环相扣,任何一个环节出问题,都会导致预览黑屏、拍照失败或者性能下降。

我个人觉得,理解这套机制的关键在于「Buffer 的视角」——你要时刻清楚当前 Buffer 在谁手里,处于什么状态,下一步要去哪里。只要把这个流转逻辑理清了,Camera 开发中的大部分问题都能迎刃而解。

嗯,今天就先聊到这里。下一章咱们深入 Camera HAL 的实现细节,看看怎么在 HAL 层高效地处理 Request 和 Buffer。


公众号:蓝海资料掘金营,微信deep3321