第三章:HAL架构分析:Android Camera HAL3 架构、Pipeline模型、Stream配置
好,咱们进入正题。这一章我打算聊聊HAL3的架构。说实话,很多做Camera开发的兄弟,一开始都被HAL3的复杂结构搞晕过。我当年刚接触时也一样,对着代码看了三天,才理清楚它到底想干什么。
HAL3的设计理念,说白了就是「解耦」。把上层框架和底层硬件彻底分开。你想想看,手机厂商那么多,Sensor型号五花八门,如果每个厂商都自己搞一套接口,Google那边维护起来得疯掉。所以HAL3应运而生,它定义了一套标准接口,让上层不用关心你用的是OV还是Sony的Sensor。
3.1 HAL3整体架构
先看一张我脑子里的架构图(嗯,没有图,我口述给你听)。
最上层是Android Framework的CameraService,它通过HIDL或AIDL接口跟HAL层通信。中间这一层,就是我们今天的主角——HAL3实现。最下层是硬件,包括Sensor、ISP、Flash等等。
我个人习惯把HAL3分成三个核心模块:
- CameraModule:负责管理所有Camera设备。比如你的手机有三个后摄一个前摄,它就要管理这四个设备。
- CameraDevice:代表单个Camera设备。每个设备都有自己的Pipeline和Stream配置。
- CameraStream:负责数据流的处理。包括预览流、拍照流、视频流等等。
这里有个坑,我提醒一下。很多新手会把CameraModule和CameraDevice搞混。其实很简单:Module是管「有几个摄像头」的,Device是管「单个摄像头怎么工作」的。
核心要点:HAL3架构的核心思想是「一次配置,多次使用」。你只需要在打开Camera时配置好Stream,后续的每一帧都会按照这个配置来处理。
3.2 Pipeline模型详解
Pipeline模型,是HAL3最精髓的部分。我刚开始理解它时,总觉得它跟工厂流水线很像。你想想看,一条流水线上有多个工位,每个工位负责一道工序。Camera的Pipeline也是这个道理。
一个典型的Pipeline包含以下几个阶段:
- Sensor阶段:原始数据采集。这个阶段输出的是RAW图。
- ISP阶段:图像信号处理。包括去噪、白平衡、色彩校正等。
- Post-processing阶段:后处理。比如人脸检测、HDR合成等。
- Output阶段:输出最终图像。可以是YUV、JPEG或者HEIC格式。
我记得在项目中遇到过一个问题:多路同步时,Pipeline的延迟不一致导致画面不同步。后来发现是ISP阶段的处理时间差异造成的。解决办法是给每个Pipeline加上时间戳对齐机制。
避坑指南:我曾经在调试多摄同步时,忽略了Pipeline的buffer管理。结果导致内存泄漏,系统直接卡死。后来我养成了一个习惯:每次配置Pipeline时,先检查buffer pool的大小是否合理。
Pipeline还有一个重要概念叫「复用」。什么意思呢?就是同一个Pipeline可以同时输出多个Stream。比如你预览用的是YUV流,拍照用的是JPEG流,这两个流可以来自同一个Pipeline。这样做的好处是节省资源,减少延迟。
3.3 Stream配置实战
Stream配置,是HAL3开发中最容易出错的地方。我见过太多人在这里栽跟头了。咱们直接看代码:
// 创建一个Stream配置
camera3_stream_configuration_t stream_config;
stream_config.num_streams = 3; // 三个流:预览、拍照、视频
// 配置预览流
camera3_stream_t preview_stream;
preview_stream.stream_type = CAMERA3_STREAM_OUTPUT;
preview_stream.width = 1920;
preview_stream.height = 1080;
preview_stream.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
preview_stream.usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_TEXTURE;
preview_stream.max_buffers = 4; // 我习惯用4个buffer,太少容易丢帧
// 配置拍照流
camera3_stream_t capture_stream;
capture_stream.stream_type = CAMERA3_STREAM_OUTPUT;
capture_stream.width = 4032;
capture_stream.height = 3024;
capture_stream.format = HAL_PIXEL_FORMAT_BLOB; // JPEG格式
capture_stream.usage = GRALLOC_USAGE_SW_READ_OFTEN;
capture_stream.max_buffers = 2;
// 配置视频流
camera3_stream_t video_stream;
video_stream.stream_type = CAMERA3_STREAM_OUTPUT;
video_stream.width = 1920;
video_stream.height = 1080;
video_stream.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
video_stream.usage = GRALLOC_USAGE_HW_VIDEO_ENCODER;
video_stream.max_buffers = 6; // 视频流需要更多buffer
看到没?每个Stream都有自己的配置参数。这里我重点说一下usage字段。它决定了buffer给谁用。比如预览流要同时给CPU和GPU用,所以用了SW_READ_OFTEN和HW_TEXTURE。视频流只给编码器用,所以只用HW_VIDEO_ENCODER。
警告:千万不要随意设置usage标志。我曾经为了省事,把所有流都设成SW_READ_OFTEN,结果导致GPU无法直接访问buffer,性能直接掉了一半。每个usage标志都有它的用途,别乱用。
Stream配置还有一个关键点:分辨率对齐。在多Camera场景下,不同Camera的Stream分辨率可能不一样。比如主摄支持4K,广角只支持1080P。这时候就需要做分辨率对齐,否则同步时会出问题。
我一般这样处理:
- 先确定所有Camera都支持的最小分辨率
- 用这个分辨率作为基准,配置所有Stream
- 如果某个Camera不支持这个分辨率,就做缩放处理
嗯,这里要注意:缩放处理会增加延迟。所以能不做尽量不做。最好的办法是在硬件设计阶段就统一分辨率。
3.4 多路同步中的Stream管理
多路同步时,Stream管理就变得复杂了。我总结了一个「三同步」原则:
| 同步类型 | 说明 | 常见问题 |
|---|---|---|
| 时间同步 | 所有Stream的帧时间戳一致 | Pipeline延迟不同导致时间戳偏移 |
| 空间同步 | 所有Stream的画面内容对齐 | 视差导致画面错位 |
| 数据同步 | 所有Stream的buffer状态一致 | buffer分配不均导致丢帧 |
时间同步是最容易出问题的。我遇到过一种情况:主摄和广角的Pipeline延迟差了2帧,结果合成出来的画面像「鬼影」一样。后来我加了一个帧队列,让快的Pipeline等慢的Pipeline,问题就解决了。
空间同步呢,说白了就是校准。每个Camera的视场角不一样,画面内容肯定有差异。你需要做图像配准,把不同Camera的画面映射到同一个坐标系下。这个工作一般在ISP阶段完成。
数据同步就简单了,保证每个Stream的buffer数量一致就行。我习惯用max_buffers = 4作为默认值,既不会浪费内存,也不会丢帧。
总结一下:HAL3架构的核心是Pipeline模型,Stream配置是它的灵魂。多路同步时,一定要做好时间、空间、数据三个维度的同步。别嫌麻烦,这一步做不好,后面全是坑。
好了,这一章就聊到这里。下一章我会讲具体的同步实现方案,包括硬件同步和软件同步两种方式。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。