4、Camera HAL3进阶:Request模板、同步与超时机制、Buffer管理、Metadata处理、HAL3与HAL1对比
各位同学,欢迎来到HAL3的进阶篇。说实话,HAL3刚出来那会儿,我也觉得它太复杂了——一个拍照动作要拆成Request、Result、Stream、Buffer这么多概念。但等你真正啃下来,你会发现它其实比HAL1要优雅得多。今天我们就来把这些硬骨头一块块啃掉。
4.1 Request模板:你的拍照“配方”
HAL3里,每一次拍照或预览,本质上都是向HAL提交一个“Request”。这个Request里包含了所有控制参数——曝光、白平衡、对焦、输出格式等等。说白了,它就是一张“配方单”。
我个人习惯把Request模板分为三类:
- 预览模板:持续提交,追求低延迟,通常只输出YUV或HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED格式。
- 拍照模板:单次触发,追求高画质,输出JPEG或RAW。
- 录像模板:持续提交,但帧率稳定,输出编码器友好的格式。
在代码层面,每个Request对应一个camera3_capture_request_t结构体。我见过不少新手在构造Request时,把settings填得乱七八糟。嗯,这里要注意:settings是一个const camera_metadata_t *,它必须包含所有必要的控制项,否则HAL会直接拒绝。
核心要点:Request模板的设计决定了相机行为的“基调”。你可以在运行时动态修改模板中的参数,但模板本身的框架(如输出流数量、格式)最好在初始化阶段就定好。
4.2 同步与超时机制:别让相机“卡死”
HAL3引入了同步框架,这是它和HAL1最大的区别之一。HAL1里,你调用一个函数,它必须等硬件干完活才返回。HAL3不一样——它把“提交请求”和“获取结果”分开了。
为什么会这样?因为现代ISP(图像信号处理器)是高度并行的。你提交一个Request后,硬件可能同时在处理前一个Request的RAW数据、当前Request的YUV输出、以及下一个Request的3A统计信息。如果每个操作都同步等待,那性能就全浪费了。
HAL3的同步机制通过Fence(栅栏)实现。每个输出Buffer都带有一个Fence文件描述符。HAL在Buffer填满数据后,会通过Fence通知框架:“嘿,这帧数据可以用了。”框架拿到信号后,才会把Buffer交给上层应用。
说到超时,我曾经踩过一个坑:某个平台上,HAL在处理高分辨率拍照时,耗时超过了框架默认的5秒超时阈值。结果框架直接杀掉了相机服务进程。后来我查了文档才发现,camera3_callback_ops_t里的process_capture_result回调必须在合理时间内返回,否则框架会认为HAL“死锁”了。
避坑指南:我曾经在调试一个HAL3实现时,发现预览偶尔会卡住。排查了半天,原来是某个Buffer的Fence没有正确signal。记住:Fence必须成对出现——create和signal缺一不可。否则框架会一直等待,最终触发超时。
4.3 Buffer管理:内存的“接力赛”
Buffer管理是HAL3里最容易被忽视、但最容易出问题的环节。HAL3使用Gralloc Buffer作为数据载体。框架会预先分配好一组Buffer(称为BufferPool),然后通过camera3_stream_t传递给HAL。
HAL拿到Buffer后,需要做三件事:
- 填充数据:把ISP输出的图像数据写入Buffer。
- 管理Fence:在Buffer准备好后,通过Fence通知框架。
- 释放Buffer:框架消费完数据后,会把Buffer归还给HAL,供下一帧使用。
这里有个关键点:Buffer的“所有权”是不断转移的。框架把Buffer交给HAL时,HAL拥有写入权;HAL把Buffer返回给框架时,框架拥有读取权。如果HAL在写入过程中崩溃了,Buffer可能永远无法归还,导致内存泄漏。
个人经验:我建议在HAL层实现一个Buffer跟踪器,记录每个Buffer的当前状态(空闲、填充中、已返回)。这样一旦出现异常,你可以快速定位是哪个Buffer卡住了。我在一个项目里就用这个办法,把一次内存泄漏的排查时间从两天缩短到了两小时。
4.4 Metadata处理:相机的“神经系统”
Metadata是HAL3里最灵活、也最复杂的一部分。它本质上是一个键值对集合,用camera_metadata_t结构体表示。所有控制参数和结果信息都通过Metadata传递。
我把它分为两类:
- 控制Metadata:从框架流向HAL,包含曝光时间、ISO、对焦模式等。你可以在Request里设置这些值。
- 结果Metadata:从HAL流向框架,包含实际使用的参数、3A状态、统计信息等。HAL在
process_capture_result回调中返回。
处理Metadata时,我最常用的API是find_camera_metadata_entry和update_camera_metadata_entry。但要注意:Metadata的存储是紧凑的,你不能直接修改它的长度。如果需要添加新的tag,必须用allocate_camera_metadata重新分配空间。
举个例子:假设你想在拍照时强制锁定AE(自动曝光)。你需要在Request的settings里设置ANDROID_CONTROL_AE_LOCK为ANDROID_CONTROL_AE_LOCK_ON。HAL收到后,会在结果Metadata里返回实际的AE锁定状态。如果HAL不支持锁定,它会返回ANDROID_CONTROL_AE_LOCK_OFF,并附带一个错误原因。
4.5 HAL3与HAL1对比:从“单线程”到“流水线”
最后,我们来做个对比。HAL1和HAL3的差异,说白了就是“单线程”和“流水线”的区别。
| 对比维度 | HAL1 | HAL3 |
|---|---|---|
| 调用模型 | 同步阻塞,一个操作完成才能进行下一个 | 异步非阻塞,多个Request可以并行处理 |
| Buffer管理 | 框架分配Buffer,HAL直接使用 | 框架和HAL通过Fence协作,所有权明确 |
| 控制粒度 | 粗粒度,只能设置有限的参数 | 细粒度,每个Request可以独立控制所有参数 |
| 性能 | 低延迟但吞吐量有限 | 高吞吐量,适合多帧处理和HDR |
| 复杂度 | 简单,适合入门 | 复杂,但灵活性和扩展性更强 |
我记得第一次从HAL1迁移到HAL3时,最不适应的是“没有回调函数了”——HAL1里你直接调用takePicture,然后等回调。HAL3里你得自己管理Request队列、处理Fence、解析Metadata。但一旦你习惯了这种“流水线”思维,你会发现它能做的事情比HAL1多得多。
举个例子:在HAL1里,如果你想实现“先拍照后对焦”(即拍完一张照片后,用户可以点击画面重新对焦),几乎不可能。因为HAL1的拍照流程是线性的,你没法在拍照完成后还保留RAW数据。但在HAL3里,你只需要在Request里同时请求JPEG和RAW输出,然后让用户选择对焦点后,从RAW里重新生成JPEG就行了。
一句话总结:HAL1适合功能简单的设备,HAL3适合需要高性能和灵活性的设备。如果你在开发旗舰机,别犹豫,直接上HAL3。
好了,这一章的内容就到这里。HAL3的进阶知识其实还有很多,比如多帧合成、HDR、RAW域处理等等,但今天我们先把这几个核心概念理清楚。下一章我们会深入Stream配置,看看如何为不同的使用场景(预览、拍照、录像)配置最优的Stream组合。
公众号:蓝海资料掘金营,微信deep3321