2、HAL 接口定义(HIDL / AIDL):深入理解 ICameraProvider、ICameraDevice 等核心接口

好,咱们今天来聊聊 HAL 层的接口定义。说实话,这是整个 Camera HAL 的骨架。你想想看,Framework 和 HAL 之间怎么通信?就是靠这些接口。我个人习惯把 HIDL 和 AIDL 比作「协议语言」,Framework 说一句,HAL 得听懂并回应。

Android 从 8.0 开始引入 Treble 架构,HIDL 就成了标配。到了 Android 14,Google 又大力推 AIDL HAL。嗯,这里要注意,虽然 AIDL 是趋势,但 HIDL 在现有项目中依然大量存在。我去年还接手过一个 Android 12 的项目,里面全是 HIDL 接口。

2.1 ICameraProvider:HAL 的「入口总管」

ICameraProvider 是啥?说白了,它就是 HAL 层的门面。Framework 要跟 Camera 打交道,第一步就是拿到这个接口。它负责两件事:枚举摄像头设备,以及提供设备服务。

来看一个典型的 HIDL 定义片段:

interface ICameraProvider {
    /**
     * 返回当前可用的摄像头设备列表
     * @return status 操作状态
     * @return cameraDeviceNames 设备名称列表,如 "device@3.4/internal/0"
     */
    getCameraDeviceList() generates (Status status, vec<string> cameraDeviceNames);

    /**
     * 根据设备名称打开对应的 CameraDevice
     * @param cameraDeviceName 设备名称
     * @return status 操作状态
     * @return device 返回 ICameraDevice 接口
     */
    openCamera(string cameraDeviceName) generates (Status status, ICameraDevice device);
};

我在项目中遇到过一个问题:某个厂商的 HAL 实现里,getCameraDeviceList() 返回的设备名称格式不标准。Framework 解析时直接崩溃。后来查了半天,发现是设备名称里多了个空格。嗯,这种坑真的让人头大。

避坑指南: 设备名称的格式必须严格遵循 "device@{HAL版本}/{逻辑摄像头ID}"。我曾经见过厂商把版本号写错,导致 CameraService 无法正确匹配 HAL 实现。

2.2 ICameraDevice:真正的「干活主力」

拿到 ICameraProvider 之后,下一步就是通过 openCamera() 获取 ICameraDevice。这个接口才是真正干活的。它负责配置流、捕获请求、处理结果回调。

核心方法就这几个:

  • getCameraCharacteristics():获取摄像头静态信息,比如支持的分辨率、AE/AWB 能力等。
  • configureStreams():配置数据流。你想想看,拍照、预览、录像,每种场景需要的流配置都不一样。
  • processCaptureRequest():提交捕获请求。这是最核心的方法,每次拍照或预览都会调用它。
  • flush():清空所有待处理的请求。我一般调试时遇到卡顿,会先调一下 flush 看看是不是请求堆积了。

来看 AIDL 版本的接口定义(Android 14 风格):

interface ICameraDevice {
    /**
     * 获取摄像头特性
     */
    CameraMetadata getCameraCharacteristics();

    /**
     * 配置输出流
     * @param streams 流配置列表
     * @return 配置结果
     */
    Status configureStreams(in StreamConfiguration[] streams);

    /**
     * 提交捕获请求
     * @param request 捕获请求
     * @param callback 结果回调
     * @return 请求ID
     */
    int processCaptureRequest(in CaptureRequest request, in ICameraDeviceCallback callback);

    /**
     * 清空请求队列
     */
    Status flush();
};

关键点: processCaptureRequest() 是异步的。你提交请求后,结果通过回调返回。我刚开始做 HAL 时,总以为它是同步的,结果等半天没反应。后来才意识到,Camera 管道是流水线作业,请求和结果是分离的。

2.3 HIDL vs AIDL:到底选哪个?

这个问题我经常被问到。直接说结论:

对比项 HIDL AIDL
引入版本 Android 8.0 Android 10(稳定版)
性能 中等,有序列化开销 更好,支持 Binder 直传
稳定性 非常稳定,经过多年验证 较新,但 Google 主推
代码生成 生成 C++/Java 代码 生成 C++/Java/NDK 代码
未来趋势 逐步淘汰 主流方向

我个人建议:新项目直接用 AIDL。但如果你维护的是老项目,别急着迁移。我见过一个团队花了两周迁移 HIDL 到 AIDL,结果因为一个回调接口没处理好,导致预览帧率掉了一半。得不偿失。

2.4 接口设计的「潜规则」

做 HAL 开发久了,你会发现有些东西文档里没写,但实际必须遵守。我总结了几条:

  • 接口版本号必须递增:每次修改接口,版本号 +1。Framework 会根据版本号判断兼容性。我见过有人改了接口忘了改版本号,结果老版本 Framework 直接 crash。
  • 回调接口不能阻塞ICameraDeviceCallback 里的 processCaptureResult() 必须快速返回。如果你在里面做耗时操作,整个 Camera 管道都会卡住。
  • 错误码要明确:别返回一个泛泛的 UNKNOWN_ERROR。我习惯把错误码细化到具体模块,比如 CAMERA3_RESULT_INVALID_REQUESTCAMERA3_RESULT_BUFFER_ERROR。这样调试时一眼就能看出问题在哪。
小技巧: 调试接口问题时,可以开启 CameraService 的 verbose 日志。命令是 adb shell setprop persist.vendor.camera.logs 1。这样每次接口调用都会打印详细参数,省去你加 log 的功夫。

2.5 实战:从 Framework 到 HAL 的调用链路

咱们走一遍完整的调用流程,这样你就能把接口串起来了:

  1. App 调用 CameraManager.openCamera()
  2. Framework 的 CameraService 通过 HIDL/AIDL 调用 ICameraProvider.openCamera()
  3. HAL 层返回 ICameraDevice 接口
  4. Framework 调用 ICameraDevice.configureStreams() 配置预览流
  5. App 发起预览请求,Framework 调用 ICameraDevice.processCaptureRequest()
  6. HAL 层处理请求,通过回调返回结果

你看,每一步都离不开这些接口。我记得有一次调试一个预览黑屏的问题,从 App 一路追到 HAL,最后发现是 configureStreams() 里流格式配错了。嗯,这种问题最折磨人,因为 Framework 层完全看不出异常。

好了,这一章就到这里。接口定义是 HAL 开发的基石,你把它吃透了,后面讲 pipeline、buffer 管理都会轻松很多。下一章咱们聊聊 Camera 的 pipeline 架构,那可是真正的「硬核」内容。