Camera HAL3架构精讲:接口定义与核心关系

大家好,我是你们的嵌入式架构师老K。今天咱们来啃一块硬骨头——HAL3架构。说实话,我刚接触HAL3的时候也被它绕晕过,什么CameraDevice、CameraStream、CameraSession,一堆新概念砸过来。但搞懂了之后你会发现,这套设计其实挺优雅的。

咱们先看一张整体架构图,后面再逐个拆解。

Android Camera 应用层 (Camera2 API) Camera Framework (CameraService) Camera HAL3 层 CameraDevice CameraStream CameraSession Pipeline 模型:Request → Capture → Result 每个Stream对应一个独立的Pipeline处理链路 Camera 驱动层 (V4L2 / ISP)

一、HAL3接口定义——从HAL1到HAL3的进化

先说说为什么要有HAL3。HAL1时代,Camera操作是同步的,你拍一张照片,整个系统都得等着。这在单摄时代还行,但到了多摄时代就彻底崩了。我当年在项目里调试双摄同步,HAL1的代码改得我头皮发麻——每个流都要自己管理buffer,同步全靠spinlock,死锁问题排查了整整两周。

HAL3的核心接口定义在 hardware/libhardware/include/hardware/camera3.h 里。说白了,它定义了一套异步回调机制。关键接口就这几个:

// HAL3 设备接口
typedef struct camera3_device {
    hw_device_t common;
    
    // 初始化设备
    int (*initialize)(const struct camera3_device *,
            const camera3_callback_ops_t *callback_ops);
    
    // 配置Stream
    int (*configure_streams)(const struct camera3_device *,
            camera3_stream_configuration_t *stream_list);
    
    // 注册Buffer
    int (*register_stream_buffers)(const struct camera3_device *,
            const camera3_stream_buffer_set_t *buffer_set);
    
    // 提交Capture请求
    int (*process_capture_request)(const struct camera3_device *,
            camera3_capture_request_t *request);
    
    // 刷新
    int (*flush)(const struct camera3_device *);
} camera3_device_t;

嗯,这里要注意 configure_streams 和 process_capture_request 这两个函数。前者决定了你能开多少路流,后者决定了每一帧怎么处理。我在项目中见过有人把这两个搞混,结果stream配置好了但request一直提交失败,debug了一整天才发现是stream配置和request里的stream ID对不上。

二、CameraDevice、CameraStream、CameraSession的关系

这三个概念是HAL3的基石。我习惯用一个比喻来理解:

  • CameraDevice —— 就是相机硬件本身。一个物理摄像头对应一个CameraDevice。多摄系统里,你有几个摄像头就有几个Device。
  • CameraStream —— 是数据流的抽象。比如你要预览,这是一个Stream;你要拍照,这是另一个Stream;你要做视频录制,可能又是第三个Stream。每个Stream有自己的分辨率、格式、帧率。
  • CameraSession —— 是Stream的集合。它把多个Stream绑定到一个逻辑会话里,统一管理它们的生命周期。

它们的关系可以用一张表说清楚:

概念 对应实体 生命周期 典型数量
CameraDevice 物理摄像头 从open到close 1个/摄像头
CameraStream 数据流通道 从configure到delete 1~N个/Device
CameraSession Stream集合 从创建到销毁 1个/应用
重要: 在多路Camera场景下,每个CameraDevice可以创建多个Stream,但这些Stream必须属于同一个Session。我曾经在一个项目里踩过坑——给每个Stream单独创建Session,结果底层ISP资源分配冲突,预览和拍照互相抢buffer,画面直接卡死。

三、Pipeline模型详解——数据流的核心

Pipeline模型是HAL3最精妙的设计。你想想看,Camera的数据处理链路其实是一条流水线:应用层提交一个Request,HAL层处理,然后返回Result。但HAL3把这个过程拆成了更细的粒度。

每个Stream对应一个独立的Pipeline。Pipeline内部包含三个阶段:

  1. Request阶段:应用层通过 process_capture_request 提交请求。请求里包含了要处理的Stream ID、需要的3A参数、输出buffer等。
  2. Capture阶段:HAL层拿到请求后,驱动ISP开始干活。这个阶段是异步的,HAL层可以同时处理多个请求。
  3. Result阶段:处理完成后,通过回调函数 notify 和 process_capture_result 把结果返回给Framework。

我画了个Pipeline的流程图,方便你理解:

Request Stream ID + 3A参数 + Buffer Capture ISP处理 + 3A计算 Result Metadata + Image Buffer 异步Pipeline队列:最多可同时处理 N 个未完成的Request N 由 HAL 层通过 get_metadata 中的 android.request.pipelineMaxDepth 指定

这里有个关键点——Pipeline的深度。HAL3允许同时有多个Request在Pipeline里排队。比如你设置了pipelineMaxDepth为4,那么你可以连续提交4个Request,HAL层会并行处理。这在多路Camera场景下特别重要,因为每个Stream的Pipeline是独立的,你可以让预览Stream的Pipeline深度大一些,拍照Stream的深度小一些。

实战技巧: 我在调试多路Camera时,习惯把预览Stream的pipelineMaxDepth设为3,拍照Stream设为1。为什么?预览需要连续帧,深度大可以防止丢帧;拍照只需要单帧,深度1就够了,还能减少延迟。这个配置在 camera3_stream_configuration 的 streams 数组里设置。

四、多路Camera场景下的Pipeline调度

好了,前面都是基础。现在咱们聊聊多路Camera时Pipeline怎么调度。这是HAL3最难的地方,也是面试常考的点。

假设你有两个摄像头:主摄和广角。每个摄像头都有自己的CameraDevice,每个Device又有自己的Stream。但注意,这些Stream的Pipeline是独立运行的。Framework层通过 process_capture_request 分别向两个Device提交Request,HAL层各自处理,然后分别回调Result。

这里有个坑——同步问题。两个摄像头的帧率可能不一样,主摄30fps,广角可能只有15fps。如果你要做双摄融合,就必须等两个Pipeline都完成才能处理。我当年在项目里实现双摄同步,用了两种方案:

  • 方案一:Frame Sync —— 在HAL层维护一个同步计数器,等两个Stream都到达同一帧号再一起返回。优点是精度高,缺点是增加了延迟。
  • 方案二:Timestamp对齐 —— 每个Result都带上硬件时间戳,Framework层根据时间戳做对齐。优点是延迟低,缺点是对时间戳精度要求高。
避坑指南: 我曾经在方案一上栽过跟头。当时没考虑两个摄像头的启动延迟差异,结果主摄都跑了10帧了,广角才刚启动。同步计数器一直等不到广角的帧,导致整个Pipeline卡死。后来加了个超时机制,超过3帧没同步就强制返回,问题才解决。

最后说一句,HAL3的Pipeline模型虽然复杂,但它给了我们极大的灵活性。你可以为每个Stream独立配置分辨率、格式、帧率,甚至可以为不同的Stream分配不同的ISP资源。这在多路Camera场景下是刚需。

嗯,今天就先聊到这儿。HAL3的接口定义和核心关系搞清楚了,后面咱们才能继续深入多路Camera的实战配置。


可可精品资料库