Camera HAL层优化:HAL3架构详解、Stream配置优化、Buffer管理策略、Request Queue深度调优
各位同学,今天我们来聊聊Camera HAL层的优化。说实话,HAL层是Android摄像头性能优化的核心战场。很多上层搞应用的同学觉得卡顿是App的问题,其实不然——我见过太多案例,App写得再漂亮,HAL层没调好,帧率就是上不去。
我个人习惯把HAL层优化分成四个维度:架构理解、Stream配置、Buffer管理、Request调度。咱们一个一个来拆解。
1. HAL3架构详解:你必须要懂的Pipeline
HAL3和HAL2最大的区别是什么?说白了,HAL3把摄像头抽象成了一个多路复用的Pipeline。你想想看,以前HAL2只能一路一路地拍,现在HAL3可以同时处理多个Request,每个Request还能指定不同的输出流。
我记得第一次接触HAL3时,被它的process_capture_request回调搞晕了。后来才明白,这个回调就是整个Pipeline的入口。每次上层下发一个Capture Request,HAL层就要完成三件事:
- 解析Request:提取出Stream、Buffer、Metadata等信息
- 调度硬件:把Request塞给ISP、Sensor、Flash等模块
- 返回Result:把填好数据的Buffer和Metadata送回Framework
这里有个关键点——HAL3是异步的。什么意思?就是上层下发Request后不会阻塞等待,HAL层处理完再通过回调通知。这个设计很巧妙,但也带来了调优的复杂性。
核心要点:HAL3的Pipeline深度决定了你能同时处理多少个Request。深度越大,吞吐量越高,但延迟也会增加。这是一个典型的trade-off。
下面这张图是我自己画的HAL3核心架构图,帮你快速建立整体认知:
2. Stream配置优化:别让格式和尺寸拖后腿
Stream配置是HAL层优化的第一道坎。我见过太多项目,Stream配置随便写,结果性能惨不忍睹。
先说说Stream的组成。每个Stream包含三个要素:格式(Format)、尺寸(Size)、使用场景(Usage)。这三者组合不当,就会导致额外的格式转换或内存拷贝。
我的经验:尽量让所有Stream使用相同的格式和尺寸。如果做不到,至少保证Preview和Video使用同一路Stream,避免重复采集。
举个例子,假设你的App需要Preview(1080p)和Video(4K)两个流。最蠢的做法是让Sensor同时输出1080p和4K两路数据。聪明的做法是:让Sensor只输出4K,Preview从4K downscale到1080p。这样Sensor只需要工作在一组配置下,省去了切换开销。
Stream配置时还要注意Usage Flag。我踩过一个坑:把Preview Stream的Usage设成了GRALLOC_USAGE_HW_VIDEO_ENCODER,结果Gralloc分配了不合适的Buffer,导致Preview掉帧。后来改成GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_SW_READ_OFTEN才解决问题。
| Stream类型 | 推荐Usage Flag | 说明 |
|---|---|---|
| Preview | GRALLOC_USAGE_HW_COMPOSER | 用于SurfaceFlinger合成 |
| Video | GRALLOC_USAGE_HW_VIDEO_ENCODER | 用于硬件编码器 |
| Snapshot | GRALLOC_USAGE_HW_CAMERA_WRITE | 用于JPEG编码 |
| ZSL | GRALLOC_USAGE_HW_CAMERA_ZSL | 零延迟快门专用 |
3. Buffer管理策略:Gralloc和ION的那些事
Buffer管理是HAL层优化的重头戏。说白了,摄像头就是不停地在Sensor、ISP、Memory之间搬运数据。搬运效率直接决定了帧率。
Android的Buffer管理主要靠两个东西:Gralloc和ION。Gralloc是上层接口,ION是底层内存分配器。我建议你直接使用ION分配连续物理内存,因为Camera硬件(Sensor、ISP)通常需要物理连续的内存。
注意:不要频繁分配和释放Buffer!我曾经在一个项目里发现,每次Request都重新分配Buffer,导致帧率从30fps掉到15fps。后来改成Buffer Pool预分配,问题立刻解决。
Buffer Pool的大小怎么定?我一般遵循这个原则:Pool大小 = Request Queue深度 × 2 + 1。比如Queue深度是4,Pool就分配9个Buffer。这样既能保证流水线不空转,又不会浪费内存。
还有一个容易被忽略的点——Cache一致性。Camera硬件写Buffer时,CPU Cache里的数据就失效了。如果你在HAL层用CPU读Buffer(比如做格式转换),一定要记得调用ion_sync_fd或dma_buf_sync。否则你会读到脏数据。
避坑指南:我曾经调试过一个Bug,预览画面偶尔出现条纹。查了两天才发现是Buffer没有做Cache同步。加上ion_sync_fd后,问题消失。嗯,这个坑我印象太深了。
4. Request Queue深度调优:找到那个甜蜜点
Request Queue深度,说白了就是HAL层能同时处理多少个Request。深度越大,Pipeline越满,吞吐量越高。但深度太大,延迟也会变大。
怎么调?我建议从2开始,逐步增加到8,观察帧率和延迟的变化。一般来说:
- 深度=2:延迟最低,适合拍照场景
- 深度=4:平衡点,适合预览+录像
- 深度=6~8:吞吐量最大,适合高速连拍
你想想看,如果Queue深度是1,那HAL层处理完一个Request才能接下一个,中间全是空闲时间。深度调到4后,Sensor在曝光第N帧时,ISP已经在处理第N-1帧,CPU在准备第N-2帧的Metadata。这就是流水线的威力。
不过要注意,Queue深度不是越大越好。我见过一个项目把深度调到16,结果内存爆了——每个Request都要分配Buffer,16个Request就是16份Buffer,内存压力巨大。
我的建议:先根据内存预算算出最大Buffer数,再反推Queue深度。比如内存预算50MB,每个Buffer 5MB,那最多10个Buffer。Queue深度就设到4~5,留一些Buffer给Pool周转。
最后说一个实战技巧:动态调整Queue深度。在低光环境下,Sensor曝光时间变长,Queue可以浅一些;在强光下,曝光时间短,Queue可以深一些。我在一个项目中实现了这个逻辑,帧率稳定性提升了20%。
好了,HAL层优化的核心内容就这些。记住:架构是基础,Stream配置是细节,Buffer管理是效率,Queue深度是平衡。把这四点吃透,你的Camera性能不会差。
公众号:蓝海资料掘金营,微信deep3321