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后,需要做三件事:

  1. 填充数据:把ISP输出的图像数据写入Buffer。
  2. 管理Fence:在Buffer准备好后,通过Fence通知框架。
  3. 释放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_entryupdate_camera_metadata_entry。但要注意:Metadata的存储是紧凑的,你不能直接修改它的长度。如果需要添加新的tag,必须用allocate_camera_metadata重新分配空间。

举个例子:假设你想在拍照时强制锁定AE(自动曝光)。你需要在Request的settings里设置ANDROID_CONTROL_AE_LOCKANDROID_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 核心流程与组件关系图 Framework Request模板 Metadata处理 Buffer管理 同步与超时 HAL3实现 结果返回 HAL1 vs HAL3 核心差异 HAL1:同步阻塞 · 粗粒度控制 · 低吞吐量 HAL3:异步流水线 · 细粒度控制 · 高吞吐量

好了,这一章的内容就到这里。HAL3的进阶知识其实还有很多,比如多帧合成、HDR、RAW域处理等等,但今天我们先把这几个核心概念理清楚。下一章我们会深入Stream配置,看看如何为不同的使用场景(预览、拍照、录像)配置最优的Stream组合。


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