11、Camera 管道与 Buffer 管理:Gralloc 图形缓冲区、Stream 配置、Request 处理
Camera 管道,说白了就是一条从镜头到屏幕的数据流水线。你按下快门那一刻,背后有几十个组件在疯狂协作。我当年第一次接触 Camera HAL 时,就被这套 Buffer 流转机制搞得晕头转向。今天咱们就把这块硬骨头啃下来。
11.1 整体架构:谁在管谁?
先看一张总览图,搞清楚 Camera 管道里各个角色是怎么串起来的。
这张图里,从上到下分了三层: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 配置流程
- App 调用
createCaptureSession,传入 Stream 列表。 - Framework 把 Stream 配置下发给 HAL 的
configure_streams方法。 - HAL 检查配置是否支持,必要时调整参数。
- HAL 返回确认,Framework 开始分配 Buffer。
- 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 通过
capture或setRepeatingRequest提交 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 在以下角色之间流转:
- Framework 持有空闲 Buffer:等待被 Request 使用。
- HAL 持有填充中的 Buffer:ISP 正在往里面写数据。
- Framework 持有已填充 Buffer:等待 App 或 SurfaceFlinger 消费。
- 消费者持有 Buffer:App 读取像素数据,或 SurfaceFlinger 合成显示。
- 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