CameraMetadata系统:Metadata结构、Static/Dynamic/Control区分、Tag查询、自定义Metadata
各位同学,今天我们来聊聊CameraMetadata。说实话,这个模块在Android相机系统里属于那种「平时不太显眼,但一旦出问题就让人抓狂」的存在。我最早接触它是在做Camera HAL层适配的时候,当时被各种Tag搞到头大,后来才慢慢摸清它的门道。
CameraMetadata,说白了就是相机硬件和上层应用之间传递信息的一张「大表格」。这张表格里什么都有:传感器能拍多大分辨率、当前帧率是多少、用户想设定什么曝光值……所有信息都通过Key-Value的形式存着。
核心要点:CameraMetadata是Android相机框架中用于描述相机能力、当前状态以及控制参数的标准化数据结构。它本质上是一个可序列化的键值对容器。
Metadata的结构:一张分层的「大表格」
我们先看看它的内部结构。CameraMetadata在C++层对应的是CameraMetadata类,底层用的是一个叫做camera_metadata_t的结构体。这个结构体设计得挺巧妙——它把所有数据紧凑地放在一块连续内存里。
// camera_metadata_t 的核心布局(简化版)
typedef struct camera_metadata {
size_t size; // 总大小
uint32_t version; // 版本号
uint32_t flags; // 标志位
size_t entry_capacity; // 最大能存多少条entry
size_t entry_count; // 当前有多少条entry
camera_metadata_entry_t *entries; // 指向entry数组
// ... 后面跟着数据区
} camera_metadata_t;
每个entry包含什么呢?一个Tag(就是那个int32的ID)、数据类型(比如BYTE、INT32、FLOAT、INT64、DOUBLE、RATIONAL)、以及实际数据的指针和个数。
我个人习惯把Metadata想象成一个「自描述的二进制协议」。你不需要事先约定好字段顺序,只要知道Tag,就能把数据读出来。这种设计在HAL层和框架层之间传递数据时特别方便。
Static / Dynamic / Control:三种角色的区分
为什么要把Metadata分成三类?我刚开始学的时候也觉得多此一举。后来在项目中踩过坑才明白——这是为了明确「谁该对什么负责」。
| 类型 | 含义 | 谁生成 | 什么时候变 |
|---|---|---|---|
| Static | 相机静态能力 | HAL层 | 相机打开后不变 |
| Dynamic | 当前状态/结果 | HAL层 | 每帧都可能变 |
| Control | 应用层的控制指令 | 应用层 | 每次拍照/预览前设置 |
Static Metadata:相机出厂就定死的能力。比如传感器最大分辨率、支持的AE模式列表、镜头焦距范围。这些信息在CameraCharacteristics里获取。我记得有一次做兼容性测试,发现某款设备的ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE和实际输出尺寸对不上,查了半天发现是HAL层写死了静态信息——嗯,这属于典型的HAL层bug。
Dynamic Metadata:每一帧的实时状态。比如当前帧的曝光时间、传感器温度、人脸检测结果。这些信息通过CameraCaptureResult返回给上层。我在做多帧合成算法时,经常需要从Dynamic Metadata里拿每帧的曝光参数来做对齐。
Control Metadata:应用层下发的「指令」。比如设定对焦区域、选择HDR模式、调整白平衡。这些通过CaptureRequest传给HAL层。这里有个坑:Control Metadata不是所有Tag都能随便设的,得先查Static Metadata里ANDROID_REQUEST_AVAILABLE_CAPABILITIES看看设备支不支持。
我的经验:调试时如果发现某个控制参数没生效,先别急着怀疑HAL。去查一下Static Metadata里对应的available_*列表,很可能设备压根不支持这个功能。
Tag查询:从数字到含义的「翻译」
Tag在代码里是一个int32的数字,比如ANDROID_SENSOR_EXPOSURE_TIME对应的值是0x00000003。但你不能直接拿数字去读代码,那太反人类了。
Android SDK里提供了CameraMetadataNative和CameraCharacteristics.Key这类封装。在Java/Kotlin层,我们直接用Key对象来读写:
// Java层示例:读取静态信息
CameraCharacteristics chars = cameraManager.getCameraCharacteristics(cameraId);
Integer[] availableModes = chars.get(
CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES
);
// 设置控制参数
CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
在Native层(C++),我们直接用Tag数字或者ANDROID_*宏来操作:
// C++层示例
CameraMetadata metadata;
metadata.update(ANDROID_SENSOR_EXPOSURE_TIME, &exposureTime, 1);
// 查询某个Tag是否存在
camera_metadata_entry_t entry;
int ret = metadata.find(ANDROID_SENSOR_EXPOSURE_TIME, &entry);
if (ret == OK) {
// 读取entry.data.i64[0]
}
我个人调试时常用的方法是:在system/media/camera/docs/目录下找到metadata_definitions.xml,这里面定义了所有Tag的完整信息。或者直接用adb shell dumpsys media.camera来dump当前设备的全部Metadata——这招在排查问题时特别管用。
自定义Metadata:扩展相机能力的「后门」
有时候厂商需要添加自己的私有功能,比如特定的夜景算法、特殊的HDR模式。这时候就需要自定义Metadata。
Android框架允许厂商在vendor命名空间下定义自己的Tag。具体做法是:
- 在
metadata_definitions.xml里添加<vendor>节,定义新的Tag和类型 - 重新生成
camera_metadata_tags.h和对应的Java/Kotlin类 - 在HAL层实现对应的读写逻辑
举个例子,假设我们要加一个「超级夜景模式」的控制开关:
<!-- metadata_definitions.xml 中的自定义部分 -->
<vendor id="0x8000">
<section name="com.example">
<controls>
<entry name="superNightMode" type="byte">
<description>Enable/disable super night mode</description>
<enum>
<value>0</value> <!-- OFF -->
<value>1</value> <!-- ON -->
</enum>
</entry>
</controls>
</section>
</vendor>
注意:自定义Tag的ID必须使用0x8000以上的vendor区间,不要占用系统预留的0x0000-0x7FFF范围。我曾经见过某厂商直接用了系统Tag的ID,结果升级Android版本后跟新系统冲突,导致相机闪退——这个坑希望大家别踩。
自定义Metadata的好处是灵活,坏处是——它只在你自己的设备上有效。如果你做的是通用App,千万别依赖vendor Tag,否则换台设备就崩了。
知识体系总览
下面这张图是我自己整理的CameraMetadata核心脉络,帮你把今天讲的内容串起来:
这张图从左到右展示了三种Metadata的源头、对应的API接口,以及Tag查询和自定义扩展的路径。你可以把它当作一个快速索引,遇到问题时先定位是哪个环节出了岔子。
好了,关于CameraMetadata的核心内容就讲到这里。这东西说复杂也复杂,说简单也简单——你只要记住「Static是能力、Dynamic是状态、Control是指令」这个口诀,再配合Tag查询和自定义扩展的知识,基本就能应付大部分开发场景了。
公众号:蓝海资料掘金营,微信deep3321