4、Camera HAL3核心概念:Pipeline、Stream、Request与Result、同步与超时机制
好,咱们今天来啃一块硬骨头——HAL3的核心概念。说实话,我刚接触HAL3的时候,也被Pipeline、Stream、Request这些词搞得晕头转向。但后来我发现,只要理解了这几个概念之间的关系,整个Camera HAL3的架构就清晰了。
我个人习惯把HAL3想象成一条「流水线」。你想想看,流水线上有原料进来,经过一道道工序,最后产出成品。Camera HAL3也是这个道理。
4.1 Pipeline:整条流水线
Pipeline,说白了就是一条完整的图像处理链路。从Sensor采集原始数据,经过ISP处理,再到输出给上层应用,整条链路就是一个Pipeline。
在HAL3中,一个Camera设备可以同时运行多个Pipeline吗?嗯,这里要注意——一个Camera设备同一时刻只能有一个Pipeline在运行。但一个Pipeline可以包含多个Stream。
核心要点:Pipeline是逻辑上的概念,它定义了「从Sensor到输出」的完整路径。每个Pipeline对应一个Camera设备实例。
我在项目中遇到过一个问题:某个平台宣称支持三路同时预览,但实际调试时发现,第三路预览的帧率掉到了10fps以下。后来排查发现,是Pipeline的带宽预算没算对,ISP的处理能力成了瓶颈。
4.2 Stream:流水线上的不同出口
Stream是什么?你可以把它理解成Pipeline上的「出口」。同一个Pipeline,可以分出多个Stream,每个Stream输出不同格式、不同分辨率的图像数据。
举个例子:
- Stream 0:预览,1920x1080,NV12格式
- Stream 1:拍照,4096x3072,JPEG格式
- Stream 2:视频编码,1920x1080,NV12格式
这三个Stream共享同一个Pipeline,但输出不同。这就是HAL3最强大的地方——多路输出。
| Stream类型 | 典型用途 | 常见格式 | 分辨率 |
|---|---|---|---|
| PREVIEW | 取景器显示 | NV12, YUYV | 屏幕分辨率 |
| STILL_CAPTURE | 拍照 | JPEG, RAW | Sensor最大分辨率 |
| VIDEO_RECORD | 视频编码 | NV12 | 视频分辨率 |
| ZSL | 零延时拍照 | NV12 | 与预览相同 |
| RAW | 专业摄影/算法 | RAW10, RAW16 | Sensor原生分辨率 |
我的经验:配置Stream时,一定要确认硬件是否支持同时输出这些格式。我曾经遇到过,Sensor支持4K拍照,但ISP不支持4K预览+4K拍照同时输出。这种坑,提前看datasheet能避免。
4.3 Request与Result:请-应答模式
HAL3的工作模式是「请求-响应」式的。上层(Framework)下发一个Request,底层(HAL)处理完后返回一个Result。
一个Request包含:
- 要使用的Stream列表
- 要设置的参数(AE、AWB、AF等)
- 要输出的Buffer
一个Result包含:
- 处理完成的Buffer数据
- 最终的参数状态(实际AE值、AWB值等)
- 时间戳、Metadata等信息
为什么会设计成这种模式?说白了,就是为了解耦。Framework不需要知道HAL内部怎么处理,它只管下发Request、收Result。HAL也不需要关心Framework什么时候要数据,它只管处理完就返回。
关键点:Request和Result是一一对应的。你下发一个Request,就一定会收到一个Result。但Result的顺序不一定和Request的顺序一致——HAL可以乱序返回。
我记得有一次调试,发现预览画面卡顿。打log一看,Framework下发了10个Request,但HAL只返回了5个Result。后来发现是某个Stream的Buffer分配不够,HAL在等Buffer释放,导致后续Request被阻塞了。
4.4 同步机制:别让数据打架
多路Stream同时跑,数据怎么同步?这是个好问题。
HAL3的同步机制主要靠两个东西:
- Frame Number:每个Request都有一个递增的Frame Number。同一个Frame Number下的所有Stream,数据是同步的。
- Timestamp:每个Result都带一个时间戳。不同Stream的Result,可以通过时间戳对齐。
举个例子:
// Framework下发Request
Request req1 = new Request();
req1.frameNumber = 100;
req1.addStream(streamPreview);
req1.addStream(streamCapture);
// HAL处理完后返回Result
Result resultPreview = new Result();
resultPreview.frameNumber = 100;
resultPreview.stream = streamPreview;
resultPreview.timestamp = 1234567890;
Result resultCapture = new Result();
resultCapture.frameNumber = 100;
resultCapture.stream = streamCapture;
resultCapture.timestamp = 1234567890;
你看,同一个Frame Number 100,预览和拍照的Result都有。上层拿到这两个Result后,就知道它们是同一帧的数据。
注意:千万不要假设不同Stream的Result会同时返回。HAL可能先返回预览的Result,再返回拍照的Result。上层必须根据Frame Number来匹配。
4.5 超时机制:别让系统卡死
如果HAL处理Request超时了怎么办?系统不能一直等下去。HAL3定义了超时机制:
| 场景 | 超时时间 | 后果 |
|---|---|---|
| processCaptureRequest | 通常4-5帧时间 | HAL可能被重启 |
| flush | 1000ms | 强制关闭Camera |
| Buffer dequeue | 由HAL实现定义 | 丢帧或报错 |
我曾经在项目中遇到一个诡异的bug:手机连续拍照10张后,第11张就卡住了。查了三天,发现是HAL内部的一个Buffer队列满了,但Framework还在不停下发Request。HAL没有及时返回错误,导致整个Pipeline死锁。
解决方案?在HAL里加了一个超时检测:如果某个Request处理超过500ms,就直接返回错误Result,并清空队列。这样虽然会丢一帧,但至少系统不会卡死。
我的建议:实现HAL3时,一定要给每个Request设置一个最大处理时间。不要依赖Framework的超时,HAL自己要有保护机制。我曾经见过一个方案,HAL内部用了一个watchdog线程,专门监控Request的处理时间。
4.6 整体流程串起来
好了,我们把上面几个概念串起来,看看完整的流程:
- Framework创建CameraDevice,配置Pipeline和Stream
- Framework下发Request,包含Stream列表和参数
- HAL收到Request,开始处理
- HAL从Sensor读取数据,经过ISP处理
- HAL把处理后的数据填入各个Stream的Buffer
- HAL返回Result,包含Buffer和时间戳
- Framework收到Result,把数据送给Display、Encoder等模块
- 如果超时,Framework会触发恢复机制
下面这张图展示了Pipeline、Stream、Request/Result之间的关系:
嗯,这张图基本把核心逻辑都画出来了。你仔细看看,应该能理解Pipeline、Stream、Request/Result之间的关系。
4.7 避坑指南
最后,我总结几个实际开发中容易踩的坑:
- Stream配置冲突:不同Stream的格式、分辨率、usage flag不能冲突。比如,你不能同时要求一个Stream输出NV12和JPEG。
- Buffer数量不足:每个Stream至少需要2-3个Buffer,否则容易卡住。我曾经因为只分配了1个Buffer,导致dequeue一直失败。
- 乱序Result处理:上层一定要按Frame Number匹配Result,不要假设顺序。我见过一个Framework实现,直接按收到顺序处理Result,结果画面花屏了。
- 超时处理不完善:HAL内部要有超时保护,不能依赖Framework来兜底。Framework的超时是最后一道防线,不是常规手段。
重要提醒:HAL3的同步和超时机制,是保证系统稳定性的关键。很多车载项目出问题,都是因为这两个地方没处理好。车载场景下,Camera不能出任何问题,否则可能影响行车安全。
好了,这一章的内容就到这里。HAL3的核心概念其实不复杂,但需要你在实际项目中反复体会。下一章我们会深入HAL3的Buffer管理机制,那是另一个容易踩坑的地方。
公众号:蓝海资料掘金营,微信deep3321