4. Stream与Buffer关系:Stream配置参数、Buffer请求与返回流程、Stream与Buffer的生命周期
在Camera HAL框架中,Stream(流)与Buffer(缓冲区)是数据管道的核心抽象。Stream定义了图像的生产规格(分辨率、格式、帧率、用途),而Buffer承载了实际的像素数据。理解两者之间的绑定关系、配置参数、请求流转以及生命周期管理,是掌握Buffer队列调度与帧同步优化的基础。
4.1 Stream配置参数详解
Stream的配置参数在Camera3StreamConfiguration结构体中定义,是HAL设备configure_streams()调用的核心输入。这些参数决定了Buffer队列的容量、格式、方向和使用场景。
| 参数名称 | 类型 | 说明 | 典型值/范围 |
|---|---|---|---|
stream_type |
uint32_t | 流方向:输入(给ISP)、输出(给应用)、双向 | CAMERA3_STREAM_OUTPUT (0)CAMERA3_STREAM_INPUT (1)CAMERA3_STREAM_BIDIRECTIONAL (2) |
width |
uint32_t | 图像宽度(像素) | 640, 1280, 1920, 3840, 4096 |
height |
uint32_t | 图像高度(像素) | 480, 720, 1080, 2160, 3072 |
format |
int32_t | 像素格式(HAL定义) | HAL_PIXEL_FORMAT_YCbCr_420_888HAL_PIXEL_FORMAT_RAW10HAL_PIXEL_FORMAT_BLOB (JPEG) |
data_space |
android_dataspace_t | 色彩空间与动态范围 | HAL_DATASPACE_SRGBHAL_DATASPACE_BT2020_HLG |
rotation |
int32_t | 输出旋转角度(仅输出流) | 0, 90, 180, 270 |
usage |
uint64_t | Buffer使用标志(Gralloc) | GRALLOC_USAGE_SW_READ_OFTENGRALLOC_USAGE_HW_TEXTURE |
max_buffers |
uint32_t | HAL请求的最大Buffer数(由HAL填充) | 通常 3~8,取决于流水线深度 |
stream_type决定了Buffer的所有权流向:输出流由HAL填充,输入流由框架填充。format与data_space必须匹配,否则HAL可能拒绝配置。max_buffers由HAL在configure_streams()返回时设置,框架据此分配Buffer池。- 多个Stream可以共享同一个物理Camera,但分辨率/格式组合受ISP带宽限制。
4.2 Buffer请求与返回流程
Buffer的请求与返回构成了Camera HAL的帧循环。框架通过process_capture_request()向HAL提交请求,HAL通过process_capture_result()回调返回填充好的Buffer。整个流程遵循生产者-消费者模型。
- 请求阶段: 框架将Buffer的
handle(句柄)传递给HAL,但所有权仍属框架。 - 处理阶段: HAL拥有Buffer的临时独占访问权,可读写像素数据。
- 返回阶段: HAL通过result回调将Buffer归还框架,框架恢复完全控制权。
- 关键规则: HAL不得在返回后继续访问Buffer,否则会导致数据竞争或崩溃。
4.3 Stream与Buffer的生命周期
Stream和Buffer的生命周期由配置、激活、流转、销毁四个阶段构成。理解生命周期有助于避免内存泄漏、死锁和帧率抖动。
4.3.1 Stream生命周期
- 重复配置: 在Stream活跃时再次调用
configure_streams(),HAL必须等待所有未完成的Buffer返回,否则可能导致Buffer泄漏。 - Stream删除: 框架在关闭Camera时删除Stream,HAL必须释放所有关联的硬件资源(如ISP通道、DMA通道)。
- Buffer残留: 如果HAL在Stream销毁后仍持有Buffer引用,Gralloc会报错,严重时导致系统服务重启。
4.3.2 Buffer生命周期
Buffer的生命周期比Stream更短,且高度依赖引用计数。每个Buffer在Stream的Buffer队列中经历以下状态:
| 状态 | 所有者 | 描述 | 触发动作 |
|---|---|---|---|
| 空闲 | 框架 | Buffer在队列中等待被请求 | Buffer分配完成 / 返回后释放 |
| 已请求 | HAL(临时) | 框架通过request将Buffer交给HAL | process_capture_request() |
| 已填充 | HAL | HAL完成硬件处理,等待返回 | ISP中断 / 硬件完成 |
| 已返回 | 框架 | HAL通过result回调归还Buffer | process_capture_result() |
| 已释放 | Gralloc | Buffer从Stream队列中移除,内存回收 | Stream销毁 / 显式释放 |
4.3.3 Stream与Buffer的绑定关系
一个Stream在配置时确定其Buffer队列的容量(max_buffers),但Buffer本身是按需分配的。框架在首次请求时通过allocate_buffers()(或隐式分配)为Stream创建Buffer池。绑定关系如下:
- 一对一: 每个Buffer在任意时刻只属于一个Stream。Buffer的
stream_id字段标识其归属。 - 多对一: 一个Stream可以拥有多个Buffer(队列深度),但所有Buffer共享相同的配置参数(分辨率、格式)。
- 动态绑定: 框架可以在Stream活跃期间动态增加Buffer(通过
signal_stream_flush()),但HAL必须支持这种动态调整。
- Buffer复用: 优先复用Buffer而非频繁分配/释放,减少Gralloc开销。
- 队列深度: 根据ISP流水线深度设置
max_buffers,通常为“流水线阶段数 + 2”。 - 生命周期对齐: 确保Buffer的释放发生在Stream销毁之前,避免悬空指针。
- 调试手段: 在HAL中记录每个Buffer的
frame_number和stream_id,便于追踪生命周期异常。
4.4 典型场景:多Stream下的Buffer调度
在实际应用中(如同时预览+拍照+录像),多个Stream共享同一个Sensor输出。HAL需要将Sensor的原始数据分发给不同的Stream,每个Stream拥有独立的Buffer队列。
- 同步分发: 同一帧数据可以同时填充多个Stream的Buffer(如预览和录像共享YUV数据)。
- 异步分发: 拍照Stream可能使用独立的Sensor输出(如全分辨率RAW),与预览流异步。
- Buffer饥饿: 如果某个Stream的Buffer队列为空(所有Buffer都在HAL中),框架会阻塞对该Stream的请求,直到有Buffer返回。
- 优先级: 预览流通常要求低延迟,因此HAL应优先返回预览Buffer,拍照流可以容忍稍长延迟。
4.5 总结:Stream与Buffer关系要点
| 维度 | Stream | Buffer |
|---|---|---|
| 定义 | 图像数据的管道规格 | 承载像素数据的内存块 |
| 配置时机 | configure_streams() 时确定 |
首次请求时分配,可动态调整 |
| 生命周期 | 从配置到销毁,贯穿Camera会话 | 从分配到释放,随帧循环流转 |
| 所有权 | 框架拥有Stream对象,HAL拥有硬件资源 | 框架拥有Buffer句柄,HAL临时借用 |
| 数量关系 | 一个Camera可以有多个Stream | 一个Stream可以有多个Buffer(队列) |
| 错误处理 | 配置失败则整个Camera无法启动 | Buffer丢失只影响单帧,可重试 |
掌握Stream与Buffer的关系,是理解后续Buffer队列管理(第5章)和帧同步优化(第6章)的前提。在实际HAL开发中,配置参数的准确性和生命周期的一致性是避免帧率抖动、内存泄漏和系统崩溃的关键。