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 其实只是个「前台」,真正的硬功夫都在设备层。