第四章: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 实战中的那些坑
说了这么多理论,来点实际的。我在项目中遇到过几个典型问题:
- ID不连续:有些平台支持热插拔摄像头,ID可能不是0、1、2这样连续的。这时候getCameraIdList要返回实际存在的ID,不能硬编码。
- 元数据版本不匹配:Android版本升级时,元数据tag可能会变。我遇到过从Android 9升到10,某个tag被废弃了,但代码里还在用,编译直接报错。
- 多摄像头同步:现在手机动辄三摄四摄,每个摄像头的元数据要独立维护。我曾经因为共用了一份元数据,导致广角和长焦的特性完全一样,拍照时切换镜头毫无区别。
最后说一句。设备枚举是整个HAL的起点,这里要是出了问题,后面所有流程都是白搭。我每次做新平台,都会花至少两天时间,把getCameraIdList和静态元数据反复验证。你想想看,如果连摄像头都枚举不出来,还谈什么拍照呢?
好,这一章就到这儿。下一章咱们聊聊预览流,那才是真正考验功底的地方。