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 解析时直接崩溃。后来查了半天,发现是设备名称里多了个空格。嗯,这种坑真的让人头大。
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_REQUEST、CAMERA3_RESULT_BUFFER_ERROR。这样调试时一眼就能看出问题在哪。
adb shell setprop persist.vendor.camera.logs 1。这样每次接口调用都会打印详细参数,省去你加 log 的功夫。
2.5 实战:从 Framework 到 HAL 的调用链路
咱们走一遍完整的调用流程,这样你就能把接口串起来了:
- App 调用
CameraManager.openCamera() - Framework 的 CameraService 通过 HIDL/AIDL 调用
ICameraProvider.openCamera() - HAL 层返回
ICameraDevice接口 - Framework 调用
ICameraDevice.configureStreams()配置预览流 - App 发起预览请求,Framework 调用
ICameraDevice.processCaptureRequest() - HAL 层处理请求,通过回调返回结果
你看,每一步都离不开这些接口。我记得有一次调试一个预览黑屏的问题,从 App 一路追到 HAL,最后发现是 configureStreams() 里流格式配错了。嗯,这种问题最折磨人,因为 Framework 层完全看不出异常。
好了,这一章就到这里。接口定义是 HAL 开发的基石,你把它吃透了,后面讲 pipeline、buffer 管理都会轻松很多。下一章咱们聊聊 Camera 的 pipeline 架构,那可是真正的「硬核」内容。