4. Stream复用原理:BufferQueue与Surface组合、多个Stream共享同一个BufferQueue的机制
在Camera HAL3框架中,Stream复用是降低内存占用、减少CPU/GPU拷贝、提升整体吞吐量的核心技术。其核心思想是:多个Stream(如预览、录像、ZSL)在满足分辨率、格式、使用场景约束的前提下,共享同一个BufferQueue,从而避免为每个Stream独立分配和搬运Buffer。
本章深入剖析BufferQueue与Surface的组合关系,以及多个Stream共享同一个BufferQueue的底层机制、配置约束和性能收益。
4.1 BufferQueue与Surface:生产者-消费者模型
Android图形系统中,BufferQueue是核心的Buffer管理组件,Surface是BufferQueue的生产者端接口。在Camera HAL3中,每个Stream对应一个或多个Surface,这些Surface最终绑定到同一个BufferQueue上。
| 组件 | 角色 | 说明 |
|---|---|---|
| BufferQueue | Buffer池与调度器 | 管理Buffer的分配、排队、回收;支持多个生产者和消费者 |
| Surface | 生产者接口 | Camera HAL通过Surface向BufferQueue提交填充好的Buffer |
| Consumer | 消费者接口 | 如SurfaceFlinger、MediaCodec、GPU,从BufferQueue取出Buffer消费 |
| BufferSlot | Buffer槽位 | BufferQueue内部维护的固定数量Buffer槽,每个槽可容纳一个GraphicBuffer |
关键概念: 一个BufferQueue可以绑定多个Surface(生产者),但所有Surface共享同一组BufferSlot。这意味着Camera HAL通过不同Surface提交的Buffer,实际上来自同一个Buffer池。
性能提示: BufferQueue的深度(maxBufferCount)直接影响延迟和内存。Camera场景通常设置为4~8个Buffer,过少会导致生产者阻塞,过多则增加内存压力。
4.2 多个Stream共享同一个BufferQueue的机制
当多个Stream(如预览Stream和录像Stream)满足复用条件时,Camera Framework会在底层将它们映射到同一个BufferQueue上。其核心流程如下:
- Stream配置阶段: Camera HAL收到configure_streams调用,Framework根据Stream参数(分辨率、格式、usage flag)判断是否可复用。
- BufferQueue分配: 若判定可复用,Framework只为这组Stream分配一个BufferQueue,并创建多个Surface指向该BufferQueue。
- Buffer提交: HAL通过不同Surface提交Buffer时,BufferQueue内部记录每个Buffer对应的Surface ID。
- 消费者分发: 消费者(如预览SurfaceFlinger、录像MediaCodec)从BufferQueue取出Buffer时,根据Surface ID或Buffer的metadata决定是否消费。
4.3 Stream复用配置约束与判定规则
并非所有Stream都能复用。Framework在configure_streams阶段执行严格的判定。以下表格总结了复用条件:
| 判定维度 | 复用条件 | 不可复用示例 |
|---|---|---|
| 分辨率 | 宽×高完全一致 | 预览1920×1080 vs 录像1280×720 → 不可复用 |
| 像素格式 | 格式兼容(如均为NV12或均为YUV420_888) | NV12 vs JPEG → 不可复用 |
| usage flag | 无冲突(如CPU_READ与HAL_PRODUCER可共存) | CPU_READ_OFTEN vs GPU_RENDER → 可能冲突 |
| 数据空间 | 相同的数据空间(如均为V0或均为V1) | V0 vs V1 → 不可复用 |
| Stream类型 | 均为输出Stream(INPUT类型不可与OUTPUT复用) | OUTPUT vs INPUT → 不可复用 |
常见陷阱: 即使分辨率相同,若一个Stream要求CPU_READ_OFTEN(频繁CPU读取),另一个要求HAL_PRODUCER(仅HAL写入),虽然usage flag可共存,但CPU读取会触发Cache刷新,影响HAL写入性能。此时复用虽然技术上可行,但性能可能反而下降。
4.4 共享BufferQueue的Buffer生命周期
当多个Stream共享同一个BufferQueue时,Buffer的生命周期管理变得复杂。以下是一个典型的Buffer流转过程:
- dequeueBuffer: HAL从BufferQueue申请一个空闲Buffer,获得BufferSlot索引。
- queueBuffer: HAL填充数据后,将Buffer入队,并标记对应的Surface ID(如预览Surface)。
- acquireBuffer: 消费者(如SurfaceFlinger)根据Surface ID或Buffer的acquire fence,取出Buffer进行显示。
- releaseBuffer: 消费者消费完成后,将Buffer释放回BufferQueue,等待下一次dequeue。
4.5 实际场景:预览+录像Stream复用
以最常见的“预览+录像”场景为例,分析Stream复用的配置和收益:
| Stream | 分辨率 | 格式 | usage flag | 是否复用 |
|---|---|---|---|---|
| 预览Stream | 1920×1080 | NV12 | HAL_PRODUCER | CPU_READ | 是(共享BufferQueue) |
| 录像Stream | 1920×1080 | NV12 | HAL_PRODUCER | VIDEO_ENCODER | 是(共享BufferQueue) |
性能对比:
- 不复用: 预览和录像各分配4个Buffer,每个Buffer约6MB(1920×1080×3/2),总内存=2×4×6=48MB;HAL需要填充两次Buffer,CPU拷贝开销约2ms/帧。
- 复用: 共享4个Buffer,总内存=4×6=24MB(节省50%);HAL只填充一次Buffer,CPU拷贝开销约1ms/帧(节省50%)。
4.6 性能优化建议
- 优先复用同分辨率Stream: 在Stream配置阶段,尽量将预览、录像、ZSL等Stream的分辨率对齐,以触发BufferQueue共享。
- 避免usage flag冲突: CPU_READ_OFTEN与GPU_RENDER同时存在时,建议拆分Stream或使用Buffer sharing扩展(如Gralloc4的Buffer Sharing API)。
- 控制BufferQueue深度: 根据帧率和延迟要求,合理设置maxBufferCount。30fps场景建议4个Buffer,60fps场景建议6~8个Buffer。
- 使用acquire fence同步: 多个消费者共享Buffer时,务必使用fence机制确保数据完整性,避免出现画面撕裂或数据损坏。
进阶技巧: 在HAL3中,可以通过camera3_stream_t的max_buffers字段向Framework建议BufferQueue深度。但Framework可能根据整体系统负载调整该值,HAL应做好动态适配。