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里提供了CameraMetadataNativeCameraCharacteristics.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。具体做法是:

  1. metadata_definitions.xml里添加<vendor>节,定义新的Tag和类型
  2. 重新生成camera_metadata_tags.h和对应的Java/Kotlin类
  3. 在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核心脉络,帮你把今天讲的内容串起来:

CameraMetadata 知识体系 Static Metadata Dynamic Metadata Control Metadata CameraCharacteristics CaptureResult CaptureRequest Tag 查询:Key / ANDROID_* 宏 / metadata_definitions.xml 自定义 Metadata:vendor 命名空间 (0x8000+)

这张图从左到右展示了三种Metadata的源头、对应的API接口,以及Tag查询和自定义扩展的路径。你可以把它当作一个快速索引,遇到问题时先定位是哪个环节出了岔子。

好了,关于CameraMetadata的核心内容就讲到这里。这东西说复杂也复杂,说简单也简单——你只要记住「Static是能力、Dynamic是状态、Control是指令」这个口诀,再配合Tag查询和自定义扩展的知识,基本就能应付大部分开发场景了。


公众号:蓝海资料掘金营,微信deep3321