3、CameraProvider 实现:如何注册、枚举相机设备,以及 Treble 架构下的服务化
好,咱们今天聊聊 CameraProvider。这东西说白了,就是 Android Treble 架构下,相机 HAL 和 Framework 之间的那座桥。没有它,上层根本不知道你手机上有几个摄像头,每个摄像头能干啥。
我个人习惯把 CameraProvider 理解成一个「服务中介」。它负责两件事:一是把自己注册到系统中,让 Framework 能找到它;二是把底层的相机设备信息,翻译成上层能懂的语言,枚举出来。
Treble 架构下的服务化:为什么需要 Provider?
在 Treble 之前,HAL 是以共享库的形式直接加载到 cameraserver 进程里的。那时候改个 HAL 就得重新刷整个系统镜像,想想就头疼。
Treble 之后,Google 把 HAL 和 Framework 彻底解耦了。HAL 跑在独立的进程里,通过 HIDL(后来是 AIDL)接口通信。这个独立的进程,就是 CameraProvider。
嗯,这里要注意:Provider 本身是一个服务,它继承自 ICameraProvider 接口。系统启动时,它会把自己注册到 hwservicemanager(HIDL 版)或 servicemanager(AIDL 版)里。
核心思路: Provider 作为一个独立进程,生命周期由 init 脚本管理。Framework 通过服务名找到它,然后调用它的接口来获取相机设备信息。
注册过程:从代码层面看 Provider 如何「上线」
咱们直接看代码。以 AIDL 版本的 CameraProvider 为例,它的注册流程大致是这样的:
// 典型的 CameraProvider 实现入口
int main() {
// 1. 创建 Provider 实例
std::shared_ptr<CameraProvider> provider = ndk::SharedRefBase::make<CameraProvider>();
// 2. 获取服务管理器实例
auto manager = AServiceManager_getInstance();
// 3. 将 Provider 注册为系统服务
// 服务名通常是 "provider@0.1" 或 "provider@aidl"
AServiceManager_addService(manager, provider->asBinder().get(), "provider@aidl");
// 4. 进入事件循环,等待客户端请求
ABinderProcess_joinThreadPool();
return 0;
}
这段代码看着简单,但有几个坑。我曾经在项目里遇到过一个问题:注册时服务名写错了,导致 cameraserver 一直连不上 Provider。排查了半天,最后发现是名字里少了个「@」符号。
避坑指南: 服务名必须和 Framework 端期望的名字完全一致。HIDL 版本的服务名格式是 vendor::hardware::camera::provider::V1_0::ICameraProvider,AIDL 版本则简单得多,直接是字符串。
枚举相机设备:getCameraIdList 的秘密
注册完了,Framework 会调用 getCameraIdList() 来获取所有可用的相机 ID。这个接口的实现,直接决定了你的手机能识别出几个摄像头。
我见过一些厂商的实现,直接把硬件探测逻辑写在这里。比如:
// CameraProvider 中的枚举实现
ScopedAStatus CameraProvider::getCameraIdList(
std::vector<std::string>* _aidl_return) {
// 1. 清空返回列表
_aidl_return->clear();
// 2. 遍历底层硬件,获取所有相机设备
// 这里通常调用 vendor 提供的底层接口
std::vector<CameraDeviceStatus> devices;
auto status = getCameraDevicesFromHardware(&devices);
if (!status.isOk()) {
// 如果失败,返回空列表
return ScopedAStatus::fromServiceSpecificError(
ERROR_CAMERA_DEVICE_NOT_FOUND);
}
// 3. 将设备 ID 填入返回列表
for (const auto& device : devices) {
_aidl_return->push_back(device.id);
}
return ScopedAStatus::ok();
}
这里有个细节:getCameraDevicesFromHardware() 这个函数,不同厂商实现差异很大。有的直接读设备树,有的通过 V4L2 接口枚举,还有的写死在配置文件里。
个人经验: 我建议在枚举时加入日志,把每个设备的物理路径打印出来。这样调试时能快速定位「为什么少了一个摄像头」的问题。我曾经靠这个日志,发现某个设备因为 GPIO 配置错误,导致硬件初始化失败。
设备状态变化:热插拔与通知机制
你以为枚举一次就完事了?不不不,有些设备支持热插拔,比如外接 USB 摄像头。这时候 Provider 需要主动通知 Framework:
// 设备状态变化的回调
void CameraProvider::onDeviceStatusChanged(
const std::string& cameraId,
CameraDeviceStatus newStatus) {
// 构造回调参数
std::vector<CameraDeviceStatusChange> changes;
changes.push_back({cameraId, newStatus});
// 通知所有注册的 callback
for (const auto& callback : mCallbacks) {
auto status = callback->onCameraDeviceStatusChanges(changes);
if (!status.isOk()) {
ALOGE("Failed to notify status change for camera %s",
cameraId.c_str());
}
}
}
这个机制在车载系统里特别常见。我做过一个项目,车上有 6 个摄像头,但只有 4 个是固定的,另外 2 个是外接的。每次插拔都要触发这个回调,否则上层界面不会刷新。
服务化带来的好处与挑战
说白了,Treble 架构下的服务化,最大的好处就是解耦。HAL 开发者可以独立更新 Provider,不用等系统镜像。但挑战也很明显:
| 方面 | 好处 | 挑战 |
|---|---|---|
| 开发效率 | HAL 和 Framework 可以并行开发 | 接口定义需要提前冻结,后期改起来麻烦 |
| 稳定性 | Provider 崩溃不会拖垮整个系统 | 进程间通信增加了延迟和复杂度 |
| 兼容性 | 同一套 Framework 可以适配不同厂商的 HAL | 需要严格遵循接口规范,否则兼容性测试过不了 |
| 调试难度 | 可以单独重启 Provider 进程 | 需要同时看 cameraserver 和 Provider 的日志 |
我记得有一次,Provider 进程因为内存泄漏被系统杀掉了,但 cameraserver 不知道,还在那傻等。结果就是相机应用打开黑屏。后来我们在 Provider 里加了心跳检测,每隔 5 秒向 cameraserver 报告一次状态。
总结一下
CameraProvider 的核心职责就三个:注册、枚举、通知。注册让系统能找到你,枚举让系统知道你有什么,通知让系统能应对变化。
你想想看,如果没有 Provider,Framework 就得直接操作硬件,那不同厂商的差异就会暴露在上层,维护成本会高得吓人。Treble 架构的精髓,就是通过服务化把这层差异封装起来。
嗯,下一章我们会深入 ICameraDevice 的实现,看看单个相机设备是如何被打开和控制的。到时候你会看到,Provider 其实只是个「前台」,真正的硬功夫都在设备层。