第三章 Camera HAL模块加载:HIDL服务注册与发现、CameraProvider实现、模块初始化流程
好,咱们进入第三章。这一章讲的是Camera HAL启动的“第一公里”——模块加载。说白了,就是系统怎么找到你的HAL库,怎么把它拉起来,然后怎么让上层知道“嘿,我准备好了”。
这部分我当年刚接手时也踩过不少坑。你想想看,如果HAL都加载不起来,后面所有的工作都是白搭。所以这一章,咱们把每个环节掰开揉碎了讲清楚。
3.1 HIDL服务注册与发现
Android从8.0开始全面推行Treble架构,HAL和framework彻底解耦。怎么解耦的?靠的就是HIDL。
HIDL的全称是HAL Interface Definition Language。它定义了一套标准的通信接口。CameraProvider作为一个HIDL服务,需要向系统注册自己,然后framework才能发现它、调用它。
注册的过程,我习惯把它比作“开店”。你开了一家店(CameraProvider),你得在工商局注册(hwservicemanager),然后顾客(framework)才能找到你的店址,进来买东西。
3.1.1 服务注册流程
CameraProvider的注册,核心代码在 camera/provider/2.4/default/CameraProvider.cpp 里。关键函数是 registerAsService()。
// 典型的注册代码
sp<ICameraProvider> provider = new CameraProvider();
status_t status = provider->registerAsService("default");
if (status != OK) {
ALOGE("Failed to register CameraProvider service: %d", status);
return -1;
}
ALOGI("CameraProvider service registered successfully");
这里有个细节要注意。registerAsService的参数是实例名。如果你只做单摄像头方案,用"default"就够了。但如果你做的是多摄像头或者外挂摄像头,我建议你给每个provider起个有意义的名称,比如"external/0"、"internal/0"。
注意: 服务名称一旦确定,不要轻易更改。因为framework端是通过这个名称来查找服务的。改名称意味着framework也要同步修改,否则就找不到你的HAL了。
3.1.2 服务发现机制
服务注册好了,framework怎么找到它?通过 ICameraProvider::getService()。
// framework端查找服务
sp<ICameraProvider> provider = ICameraProvider::getService("default");
if (provider == nullptr) {
ALOGE("Failed to get CameraProvider service");
return;
}
嗯,这里有个坑。getService默认是阻塞的,它会一直等,直到服务出现或者超时。默认超时时间是5秒。如果你的HAL启动比较慢,比如需要初始化传感器或者加载固件,5秒可能不够。
我曾经在一个项目上遇到过,外挂USB摄像头初始化需要8秒,结果framework那边等了5秒就超时报错了。后来怎么解决的?在HAL端先快速注册一个空服务,等初始化完成后再更新真正的服务接口。或者,你也可以在framework端把超时时间调长一点。
小技巧: 调试阶段可以用 lshal 命令查看服务是否注册成功。输入 adb shell lshal | grep camera,如果看到你的provider,说明注册成功了。
3.2 CameraProvider实现
CameraProvider是HAL的入口点。它负责管理所有Camera设备。一个Provider可以管理多个Camera设备,比如前置、后置、广角、微距等等。
3.2.1 Provider接口定义
ICameraProvider.hal定义了Provider需要实现的接口。核心方法就几个:
| 方法名 | 功能 | 说明 |
|---|---|---|
| getCameraIdList | 获取所有Camera ID列表 | 返回当前Provider管理的所有摄像头ID |
| getCameraDeviceInterface | 获取Camera设备接口 | 返回指定ID的ICameraDevice对象 |
| setCallback | 设置回调 | 用于通知framework状态变化 |
我个人习惯,在实现Provider时,先把getCameraIdList写好。因为framework第一个调用的就是它。如果这个接口返回空列表,framework会认为没有摄像头,直接跳过。
3.2.2 多Camera管理
实际项目中,一个Provider管理多个Camera是常态。我建议用map来管理:
// 内部管理结构
std::map<std::string, std::shared_ptr<CameraDevice>> mCameraDevices;
// 初始化时添加设备
mCameraDevices["0"] = std::make_shared<CameraDevice>(0, "back");
mCameraDevices["1"] = std::make_shared<CameraDevice>(1, "front");
mCameraDevices["2"] = std::make_shared<CameraDevice>(2, "external");
这里要注意Camera ID的命名规范。Android推荐用数字字符串,比如"0"、"1"、"2"。但如果你做的是外挂摄像头,也可以用"external/0"这种格式。不过,我建议你统一用数字,兼容性最好。
3.3 模块初始化流程
好了,前面铺垫了那么多,现在咱们看看完整的初始化流程。从系统启动到HAL就绪,大致分这么几步:
- hwservicemanager启动:这是HIDL服务的“工商局”,必须先启动。
- CameraProvider进程启动:init.rc中配置的service被拉起。
- HAL库加载:通过dlopen加载camera.
.so。 - HAL模块初始化:调用hal_module_struct.common.open()。
- Provider注册:调用registerAsService()。
- framework发现服务:通过getService()获取Provider。
这里面,第三步和第四步最容易出问题。我详细说说。
3.3.1 HAL库加载细节
HAL库的加载路径是固定的。Android会去 /vendor/lib/hw/ 或者 /vendor/lib64/hw/ 下找 camera.<variant>.so。variant通常对应你的硬件平台,比如 camera.sdm845.so。
加载的核心代码在 CameraProvider.cpp 的 initialize() 方法里:
// 加载HAL库
void* handle = dlopen("camera.sdm845.so", RTLD_NOW);
if (handle == nullptr) {
ALOGE("Failed to dlopen HAL library: %s", dlerror());
return;
}
// 获取HAL模块结构体
hal_module_struct_t* module = (hal_module_struct_t*)dlsym(handle, "HAL_MODULE_INFO_SYM");
if (module == nullptr) {
ALOGE("Failed to get HAL module info: %s", dlerror());
dlclose(handle);
return;
}
这里有个关键点:RTLD_NOW 表示立即解析所有符号。如果库里有未定义的符号,dlopen会直接失败。我建议调试阶段先用 RTLD_LAZY,等稳定了再改成 RTLD_NOW。
重点: HAL_MODULE_INFO_SYM 这个符号名是固定的,不能改。它是HAL模块的入口点。如果编译出来的so里没有这个符号,dlopen能成功,但dlsym会返回null。
3.3.2 模块初始化顺序
拿到HAL模块结构体后,接下来调用open方法。open方法会返回一个camera_device_t结构体,里面包含了所有操作函数指针。
// 打开Camera设备
camera_device_t* device = nullptr;
status_t status = module->common.open(&module->common, "0", (hw_device_t**)&device);
if (status != OK) {
ALOGE("Failed to open camera device: %d", status);
return;
}
open方法的第二个参数是Camera ID。这里要注意,open方法内部会做很多初始化工作,比如:
- 加载传感器驱动
- 初始化ISP
- 分配内存池
- 启动预览线程
这些操作如果放在open里做,会导致open耗时很长。我建议把耗时操作放到第一次调用startPreview或者startCapture时再做。open只做必要的初始化,比如分配资源、注册回调等。
我曾经在一个项目上,open方法里做了传感器校准,耗时3秒。结果framework那边以为HAL卡死了,直接kill了进程。后来我把校准挪到了第一次拍照时做,问题就解决了。
3.3.3 初始化失败处理
初始化过程中,任何一步失败都要妥善处理。我的经验是:
- dlopen失败:检查so文件是否存在,权限是否正确。用
adb shell ls -l /vendor/lib/hw/camera.*.so查看。 - dlsym失败:检查HAL_MODULE_INFO_SYM是否导出。用
adb shell readelf -s camera.*.so | grep HAL_MODULE查看。 - open失败:检查硬件是否正常,驱动是否加载。用
adb shell dmesg | grep camera查看内核日志。
注意: 初始化失败后,一定要释放已分配的资源。比如dlopen成功但dlsym失败,要调用dlclose。否则会造成资源泄漏。
3.4 实战经验总结
说了这么多,我总结几条实战经验:
- 服务注册要尽早:Provider进程启动后,尽快注册服务。哪怕设备还没准备好,先注册一个空服务,等准备好了再更新。
- 日志要打全:每个关键步骤都打日志。我习惯用ALOGI打正常流程,ALOGE打错误流程。调试时用
adb logcat -s CameraProvider过滤。 - 超时处理要谨慎:framework端的getService默认5秒超时。如果你的初始化超过5秒,要么优化初始化流程,要么修改超时时间。
- 多Camera要并行初始化:如果Provider管理多个Camera,建议用线程池并行初始化,不要串行。串行的话,3个摄像头每个初始化2秒,总共6秒,用户早就不耐烦了。
嗯,这一章的内容就到这里。模块加载是整个HAL的基石,基础打好了,后面的开发才会顺利。下一章咱们聊聊Camera设备的具体操作,比如打开、关闭、配置流等。
记住,HAL开发没有捷径,但可以少走弯路。多打日志,多测试,多思考。遇到问题不要慌,先看日志,再查代码,最后问同事。实在不行,翻翻我的手册,说不定能找到答案。
调试小工具: 推荐用 adb shell dumpsys media.camera 查看Camera服务的状态。如果能看到你的Provider和Camera设备,说明初始化成功了。
公众号:蓝海资料掘金营,微信deep3321