第四章:Camera设备枚举:getCameraIdList实现、静态元数据填充、Camera特性上报

好,咱们今天聊点实在的。设备枚举这件事,说白了就是让系统知道「你到底接了几颗摄像头」。我刚开始做HAL时,觉得这步就是走个过场。后来发现,这里埋的坑,比后面所有流程加起来都多。

4.1 getCameraIdList:你的第一道关卡

这个接口是HAL层的入口。Framework会先调用它,问一句:「老兄,你手上有几个摄像头?」

实现起来其实不复杂。我习惯在HAL初始化时,先扫描硬件设备树或者读取配置文件。比如在QCOM平台上,我会去读/sys/devices/platform/下的传感器节点。嗯,这里要注意,不同平台的做法差异很大。

// 伪代码示例
int getCameraIdList(struct camera_module *module,
                    int *num_cameras,
                    struct camera_info **cameras) {
    // 我习惯先清空列表
    *num_cameras = 0;
    *cameras = NULL;

    // 扫描硬件,获取实际连接的摄像头数量
    int count = scan_hardware_cameras();
    if (count <= 0) {
        // 我曾经遇到过传感器电源没起来,返回0的情况
        // 排查了半天,结果是GPIO配置错了
        return -ENODEV;
    }

    // 分配内存,填充信息
    *cameras = malloc(count * sizeof(struct camera_info));
    for (int i = 0; i < count; i++) {
        (*cameras)[i].id = i;
        (*cameras)[i].facing = get_facing(i);  // 前摄还是后摄
        (*cameras)[i].orientation = get_orientation(i);
    }

    *num_cameras = count;
    return 0;
}
⚠️ 避坑指南
我曾经在某个项目里,因为设备树里摄像头节点顺序写反了,导致前摄后摄ID互换。用户自拍时画面是倒的,被测试妹子追着骂了一周。所以,ID的分配一定要和硬件实际布局对应。

4.2 静态元数据填充:别小看这些「死数据」

静态元数据,说白了就是摄像头出厂时就定死的参数。比如传感器尺寸、像素大小、支持的AE模式等等。这些数据不会变,但Framework会拿它们做各种判断。

我个人习惯把静态元数据分成几类来维护:

类别 典型字段 注意事项
传感器信息 SENSOR_INFO_PIXEL_ARRAY_SIZE
SENSOR_INFO_SENSITIVITY_RANGE
像素阵列大小一定要和实际sensor输出一致。我见过有人把1080p的sensor填成4K,结果预览卡成PPT
能力声明 REQUEST_AVAILABLE_CAPABILITIES
SCALER_AVAILABLE_STREAM_CONFIGURATIONS
你声明了支持BURST_CAPTURE,就一定要实现。否则上层会直接crash
控制参数 CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
CONTROL_AWB_AVAILABLE_MODES
FPS范围别瞎填。我曾经填了个[5, 120],结果sensor实际只能跑30fps,预览帧率忽高忽低

填充时,我建议用结构体数组来管理。每个摄像头对应一份元数据。你想想看,如果后期要加一颗新sensor,只需要在数组里加一条记录,代码改动量很小。

// 静态元数据填充示例
static camera_metadata_t *construct_static_metadata(int camera_id) {
    // 我习惯先分配一个足够大的buffer
    camera_metadata_t *metadata = allocate_camera_metadata(1024, 200);

    // 填充基本信息
    int32_t facing = (camera_id == 0) ? CAMERA_FACING_BACK : CAMERA_FACING_FRONT;
    add_camera_metadata_entry(metadata,
        ANDROID_LENS_FACING, &facing, 1);

    // 填充支持的输出尺寸
    int32_t stream_configs[] = {
        1920, 1080, HAL_PIXEL_FORMAT_YCbCr_420_SP,
        1280, 720,  HAL_PIXEL_FORMAT_YCbCr_420_SP,
        640,  480,  HAL_PIXEL_FORMAT_YCbCr_420_SP
    };
    add_camera_metadata_entry(metadata,
        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
        stream_configs, sizeof(stream_configs)/sizeof(int32_t));

    return metadata;
}
💡 小技巧
静态元数据里有个字段叫ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL。如果你不确定自己的HAL实现到什么程度,先填LIMITED。等所有功能都验证通过了,再改成FULL。我曾经一上来就填FULL,结果CTS测试挂了30多项,改得想哭。

4.3 Camera特性上报:告诉上层「我能做什么」

特性上报,其实就是把静态元数据打包好,通过get_camera_info接口返回给Framework。这里有个关键点:你上报的特性,必须和实际实现一致。

为什么会这样?因为Framework会根据你上报的特性,来决定调用哪些API。比如你上报了CONTROL_AE_MODE_ON_AUTO_FLASH,但实际HAL里没处理这个模式,那用户一开闪光灯,直接就黑屏了。

我整理了一份常见的特性上报清单:

  • 基础能力:是否支持HDR、是否支持夜景模式、是否支持视频防抖
  • 流配置:支持哪些分辨率和帧率组合、支持哪些像素格式
  • 3A能力:AE/AWB/AF支持哪些模式、是否支持手动调节
  • 后处理:是否支持人脸检测、是否支持美颜、是否支持滤镜

嗯,这里要特别说一下。有些厂商喜欢把特性上报得特别全,觉得「有总比没有好」。但我的经验是:宁缺毋滥。你上报了某个特性,就要保证它在所有场景下都能正常工作。否则CTS测试会教你做人。

🔑 核心原则
特性上报 = 能力承诺。你报了什么,就必须实现什么。少报一个特性,最多是功能少一点。多报一个没实现的特性,那就是bug。

4.4 实战中的那些坑

说了这么多理论,来点实际的。我在项目中遇到过几个典型问题:

  1. ID不连续:有些平台支持热插拔摄像头,ID可能不是0、1、2这样连续的。这时候getCameraIdList要返回实际存在的ID,不能硬编码。
  2. 元数据版本不匹配:Android版本升级时,元数据tag可能会变。我遇到过从Android 9升到10,某个tag被废弃了,但代码里还在用,编译直接报错。
  3. 多摄像头同步:现在手机动辄三摄四摄,每个摄像头的元数据要独立维护。我曾经因为共用了一份元数据,导致广角和长焦的特性完全一样,拍照时切换镜头毫无区别。

最后说一句。设备枚举是整个HAL的起点,这里要是出了问题,后面所有流程都是白搭。我每次做新平台,都会花至少两天时间,把getCameraIdList和静态元数据反复验证。你想想看,如果连摄像头都枚举不出来,还谈什么拍照呢?

好,这一章就到这儿。下一章咱们聊聊预览流,那才是真正考验功底的地方。