第4章:HAL 接口定义(HIDL/AIDL)
好,咱们今天聊聊HAL接口定义。说实话,这一章是很多初学者最头疼的部分。我当年刚接触时也懵,什么HIDL、AIDL,一堆缩写绕得人头晕。但等你真正搞懂了,会发现这东西其实挺有章法的。
4.1 HIDL 与 AIDL 的区别
先说说这两个东西到底啥关系。HIDL是Android 8.0引入的,目的是把HAL和Framework彻底解耦。AIDL呢,老牌IPC工具,Android 1.0就有了。Google后来在Android 11里又把AIDL捡回来,用来定义HAL接口。
你可能会问:为什么折腾来折腾去?
嗯,这里有个历史原因。HIDL刚出来时,Google想搞一套全新的东西,专门给HAL用。但后来发现,开发者们对AIDL更熟悉,而且AIDL经过这么多年打磨,生态更成熟。所以Android 11开始,Google又推AIDL作为首选。
| 对比项 | HIDL | AIDL |
|---|---|---|
| 引入版本 | Android 8.0 | Android 1.0(HAL用从11开始) |
| 稳定性 | 稳定,但学习曲线陡 | 成熟,开发者熟悉 |
| 性能 | 原生支持,性能好 | 经过优化,差距不大 |
| 代码生成 | hidl-gen | aidl工具 |
| Google推荐 | 旧项目维护 | 新项目首选 |
核心区别:HIDL是Google为HAL量身定做的,AIDL是通用IPC方案。我个人建议新项目直接用AIDL,除非你要维护老代码。
4.2 ICameraProvider 接口详解
ICameraProvider是Camera HAL的入口。说白了,它就是Camera服务的"大管家"。Framework通过它来发现和管理所有Camera设备。
我记得在项目中第一次实现这个接口时,踩了个坑——getCameraIdList()返回的ID列表顺序,直接影响上层应用的摄像头切换逻辑。如果你把前后摄顺序搞反了,用户自拍时打开的是后摄,那可就闹笑话了。
核心方法就这几个:
- getCameraIdList():返回所有可用的Camera ID列表
- isSetTorchModeSupported():是否支持闪光灯模式
- setTorchMode():设置闪光灯模式
- getCameraDeviceInterface():获取具体的Camera设备接口
小技巧:实现getCameraIdList()时,建议把物理摄像头和逻辑摄像头分开返回。逻辑摄像头ID通常以"L"开头,方便上层区分。
4.3 ICameraDevice 接口详解
ICameraDevice代表一个具体的Camera设备。你可以把它理解成"摄像头对象"。Framework通过它来配置摄像头、创建会话、开始预览等。
这个接口里,最重要的就是open()和createDefaultRequest()。open()方法会返回一个ICameraDeviceSession,而createDefaultRequest()则生成默认的拍照参数。
我曾经遇到过一个诡异的问题:某款手机在低光环境下预览特别暗。查了半天,发现是createDefaultRequest()返回的默认参数里,曝光时间设得太短。后来我们在初始化时根据环境光照动态调整了默认参数,问题就解决了。
关键方法一览:
- open():打开摄像头,返回ICameraDeviceSession
- createDefaultRequest():创建默认的CaptureRequest
- getCameraCharacteristics():获取摄像头特性
- close():关闭摄像头
4.4 ICameraDeviceSession 接口详解
ICameraDeviceSession是真正干活的地方。拍照、录像、预览,所有数据流都经过它。这个接口的方法比较多,但核心就三个:processCaptureRequest()、flush()和close()。
processCaptureRequest()是重中之重。它接收一个CaptureRequest,然后返回一个CaptureResult。你想想看,每一帧图像的处理,都要经过这个方法。性能好不好,就看它实现得怎么样。
我记得有一次,某平台在4K录像时帧率上不去。排查后发现,是processCaptureRequest()里做了太多同步操作,导致处理线程被阻塞。后来改成异步处理,帧率就稳了。
注意:flush()方法必须能快速中断正在处理的请求。如果实现不好,会导致应用卡死。我曾经见过一个厂商的HAL,flush()里居然sleep了500ms,结果相机应用经常ANR。
4.5 如何编写 .hal 文件
好,到了动手环节。怎么写.hal文件?其实没那么玄乎。我拿一个简化版的CameraProvider来举例。
// ICameraProvider.hal
package android.hardware.camera.provider@3.5;
interface ICameraProvider {
getCameraIdList() generates (vec<string> cameraIds);
isSetTorchModeSupported() generates (bool supported);
setTorchMode(string cameraId, bool enabled)
generates (Status status);
getCameraDeviceInterface(
string cameraId
) generates (Status status, ICameraDevice device);
};
写.hal文件时,有几个要点:
- 包名要规范:
android.hardware.xxx开头,后面跟版本号 - 接口命名:I开头,驼峰命名
- 方法签名:输入参数在前,输出用
generates关键字 - 类型使用:HIDL有自己的类型系统,比如
vec<string>表示字符串数组
个人经验:写.hal文件时,建议先画个接口关系图。ICameraProvider、ICameraDevice、ICameraDeviceSession这三者的调用链,理清楚了再动手写代码。我曾经因为没画图,结果接口定义了好几版才定下来。
写完.hal文件后,用hidl-gen工具生成C++或Java代码。命令大概是这样的:
hidl-gen -o output -L c++ -r android.hardware:hardware/interfaces \
android.hardware.camera.provider@3.5::ICameraProvider
生成的代码里,你会看到一堆Bn、Bp开头的类。别慌,Bn是服务端基类,Bp是客户端代理。你只需要继承Bn类,实现具体方法就行。
嗯,这一章内容不少。HIDL和AIDL的区别、三个核心接口、怎么写.hal文件,都讲到了。下一章咱们聊聊Camera HAL的初始化流程,看看Framework是怎么一步步找到并打开摄像头的。