← 返回课程

Metadata系统

Camera全栈开发(Qcom Camx) 第 10 / 30 节

第 8 章:Metadata 系统


本章导读

你在 App 层写了一句 builder.set(CONTROL_AE_MODE, AE_MODE_ON)。这一行代码在 HAL 层最终变成了什么?它经过了哪些环节才到达 ISP 的 AE 算法?

反过来,ISP 处理完一帧,算出了新的曝光值——这个曝光值怎么穿过层层边界,最终让你在 App 的 onCaptureCompleted() 里读到?

本章就是回答这两个问题。


8.1 从一个参数开始 — AE_MODE 的旅程

对照这个图看下面的流程

让我们追踪一个具体参数 CONTROL_AE_MODE = ON 从 App 到 HAL 再到 App 的完整旅程。

Request 方向:App → HAL

① App 层:
   builder.set(CaptureRequest.CONTROL_AE_MODE,
               CaptureRequest.CONTROL_AE_MODE_ON);
   ↓
   CaptureRequest 内部是一个 CameraMetadata 容器
   tag = CONTROL_AE_MODE (0x10090001), value = 1 (ON)

② Binder 传输:
   CameraMetadata::flatten()
   → 把所有 tag-value 对序列化成连续字节流
   → 跨 Binder 传给 system_server 中的 CameraService

③ CameraService:
   字节流 → unflatten() → 恢复成 CameraMetadata 对象
   → 通过 HIDL/AIDL 传给 CameraProvider 进程
   又是一次 flatten() → unflatten()

④ HAL 层 (CamX):
   camera3_device_t.process_capture_request() 收到
   → camera3_capture_request_t.settings 就是 metadata
   → Session → Pipeline → Node

⑤ IFE Node:
   从 metadata 中读取 CONTROL_AE_MODE
   发现它设为了 ON → 启动 AE 自动算法
   → IFE 处理时采集 AE Stats,为下一帧算曝光参数

Result 方向:HAL → App

⑤ ISP 处理完一帧:
   IFE Node 写回 result metadata:
   → SENSOR_EXPOSURE_TIME = 33ms(AE 算出来的)
   → SENSOR_SENSITIVITY = 200
   → SENSOR_TIMESTAMP = 265793404062(硬件时间戳)

④ Pipeline/Node 汇聚 result:
   Node::ProcessResult() → Pipeline::ProcessPipelineResult()
   → Session 收集所有 Pipeline 的 result → 合并成一个 metadata

③ HIDL/Binder 传输:
   又一次 flatten() → unflatten() 的往返
   camera3_capture_result_t.result 包含合并后的 metadata

② CameraService → App:
   CaptureResult 中包含了所有 result metadata

① App:
   TotalCaptureResult result = callback.onCaptureCompleted(...);
   Long exposureNs = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
   // → 33000000 ns (33ms)

每拍一帧照片,metadata 就在这套"来回跑"的流程中走了两遍。理解了这个,你就理解了 HAL3 的双向通信模型。


8.2 CameraMetadata 的数据结构

📊 参考:AOSP 源码 system/media/camera/include/system/camera_metadata.h

CameraMetadata 是一个为跨进程传输优化的 key-value 容器

为什么不用 HashMap?

Java HashMap:
  - 每条 entry 独立分配 → 内存碎片
  - 序列化时要遍历所有 key → 慢
  - 不保证内存连续性

CameraMetadata:
  - 所有 entry 在连续内存块中
  - 可以直接 flatten 成字节流 → 跨 Binder 传输
  - O(1) 随机访问
  - 预分配固定大小 → 避免运行时 realloc

数据结构

// system/media/camera/include/system/camera_metadata.h
typedef struct camera_metadata {
    uint32_t    version;
    uint32_t    entry_count;      // 当前条目数
    uint32_t    entry_capacity;   // 最大容量(预分配)
    uint32_t    data_count;       // 数据区已用大小
    uint32_t    data_capacity;    // 数据区容量(预分配)
    camera_metadata_entry_t entries[];  // 条目数组
    uint8_t     data[];                // 数据区(所有 value 实际存这里)
} camera_metadata_t;

内存布局

┌───────────────────────────────┐
│          metadata 头部         │
│  version / entry_count / ...  │
├───────────────────────────────┤
│        条目索引区              │
│  entry[0]: tag=CONTROL_AE_MODE │ → 指向 data 区偏移量
│  entry[1]: tag=SENSOR_EXP_TIME│
│  ...                          │
├───────────────────────────────┤
│          数据区                │
│  [偏移 0]   CONTROL_AE_MODE=1  │ ← 实际数据存在这里
│  [偏移 4]   EXPOSURE_TIME=33ms │
│  ...                          │
└───────────────────────────────┘

为什么这样设计?
  → 序列化时,直接把整块内存拷出去就是字节流
  → 反序列化时,把字节流拷进来,修正指针偏移就行
  → 不需要逐个 entry 解析

8.3 Standard Tag vs Vendor Tag

Standard Tag(0x00000000 ~ 0x7FFFFFFF)

由 Android 定义,所有设备必须支持。定义在 AOSP 自动生成的 camera_metadata_tags.h 中:

enum {
    // 控制类 (0x1009xxxx 范围)
    CONTROL_AE_MODE            = 0x10090001,
    CONTROL_AF_MODE            = 0x10090002,
    CONTROL_AWB_MODE           = 0x10090003,

    // 传感器类 (0x100Bxxxx 范围)
    SENSOR_EXPOSURE_TIME       = 0x100B0001,
    SENSOR_SENSITIVITY         = 0x100B0002,

    // JPEG 类(section = 7,即 0x0007xxxx 范围)
    JPEG_QUALITY              = 0x00070001,
    JPEG_ORIENTATION          = 0x00070002,

    // ... 总共约 400 个 tag
};

Tag 编号的编码规则

0x   0002     0001
  │    │      └── Index(在 Section 内的序号,低 16 位)
  │    └───────── Section(高 16 位:2 = CONTROL, 0B = SENSOR, ...)
  └────────────── 整个 32 位即一个 tag(不含 Type)

💡 Type 并不编进 tag 值:byte / int32 / float 等数据类型,存在 camera_metadata_buffer_entry 结构体的独立 type 字段里(与 tag 分开),通过 tag 查表得到。所以不要试图从 tag 数值本身反推 Type。

Vendor Tag(0x80000000 ~ 0xFFFFFFFF)

厂商自定义的 tag。高通定义的 vendor tag 示例:

// chi-cdk 中生成的 vendor tag 定义
#define VENDOR_EIS3_ENABLE             0x80000001  // EIS 开关
#define VENDOR_RECORDING_END_OF_STREAM  0x80000002  // 录像结束标记

Vendor Tag 的注册流程

① 厂商在代码中定义 tag 名称和 ID
② 实现 get_vendor_tag_ops() 函数(在 camera_module_t 中注册)
③ CameraService 在 Provider 启动时查询 vendor tag 列表
④ CameraService 将 vendor tag 信息传给 CameraCharacteristics
⑤ App 可以通过 CameraCharacteristics 发现并使用 vendor tag

在 App 层使用 vendor tag:

// App 需要自己定义 Key,因为 vendor tag 不在标准 API 中
public static final CaptureRequest.Key<Byte> EIS_MODE =
    new CaptureRequest.Key<>(
        "org.quic.camera.eis3enable.EISV3Enable", Byte.class);

// 使用和标准 tag 一样的 set 方法
builder.set(EIS_MODE, (byte) 0x01);

如何查看设备上的所有 vendor tag

adb shell dumpsys media.camera | grep -A 80 "Vendor tags"
# 列出所有已注册的 vendor tag 及其类型

8.4 Metadata 在 CamX 内部的传递

App 发来的 metadata 到了 CamX 内部后,并不是所有 Node 都能看到全部内容。

Session 级别的过滤

Session 收到完整的 Request Metadata
  │
  ├── Pipeline 0 (Preview):
  │     只取自己需要的 tag——比如 AE_MODE、EXPOSURE_TIME
  │     不需要 JPEG_QUALITY(那是 Pipeline 1 的事)
  │
  └── Pipeline 1 (JPEG):
        只取自己需要的 tag——比如 JPEG_QUALITY、JPEG_ORIENTATION
        不需要 AE_MODE

Node 级别的依赖声明

每个 Node 通过 DependencyUnithasPropertyDependency 来声明自己需要哪些 metadata(第 14 章有详细解释):

// 伪代码:IFE Node 声明需要的 property
DependencyUnit ifeDependency;
ifeDependency.hasPropertyDependency = TRUE;
ifeDependency.properties = {
    { CONTROL_AE_MODE },
    { SENSOR_EXPOSURE_TIME },
    { SENSOR_SENSITIVITY },
};
// IFE 只等这三个 property,其他的不需要

Metadata 池——每帧一个 slot

CamX 用一个 MetadataPool 来管理每帧的 metadata:

// Session 为每个 Pipeline 分配 metadata pool
MetadataPool* pMainPool = CreateMetadataPool(numSlots);

// 每个 slot 可以存一帧的 metadata
// Slot N 对应 request N
// 处理完 Slot N 后,可以复用 Slot N 给新的 request

8.5 实际调试:从 dumpsys 看 Metadata

查看设备的 Static Metadata

adb shell dumpsys media.camera | grep -A 100 "Camera 0"

你会看到类似这样的输出:

Camera 0 (BACK):
  android.lens.facing: BACK
  android.sensor.orientation: 90
  android.sensor.info.activeArraySize: (0,0,4032,3024)
  android.sensor.info.exposureTimeRange: (100000, 1000000000)
  android.jpeg.availableThumbnailSizes: (176,144),(320,240),...
  ...

这些就是 ChiContext::InitializeStaticMetadataPool() 构建的内容。

抓一次 request 中的 metadata

# 开启 metadata dump
adb shell setprop persist.vendor.camera.dumpMetadata 1
# 注意:不是所有平台都支持这个 prop,具体看 camxoverridesettings.txt

8.6 常见问题

Q1: 设置了参数但不生效

三个可能:
1. AE/AF 处于自动模式 → 手动值被覆盖(先关掉自动模式)
2. 该参数不属于当前 TEMPLATE → 换 TEMPLATE_MANUAL
3. 该 tag 是 vendor tag → App 层 Key 定义不对

Q2: CaptureResult 中某个 tag 值总是 0 或 null

可能原因:
1. 该 tag 只在特定模式下有效(如只在 STILL_CAPTURE 时有值)
2. 该 tag 尚未被任何 Node 填充(HAL 侧没有实现)
3. 该 tag 在 metadata 中被 publish 了但没有写入正确的值

Q3: Vendor Tag 注册后在 App 层看不到

检查:
1. get_vendor_tag_ops() 是否在 camera_module_t 中正确设置
2. ChiContext 初始化时是否注册了 vendor tag
3. dumpsys 中是否有 vendor tag 列表

8.7 本章总结

CameraMetadata = 为跨进程传输优化的 key-value 容器
  连续内存设计 → 直接 flatten 成字节流

完整的一次 metadata 旅程:
  ① App set → Metadata 容器
  ② flatten → Binder → unflatten(App → system_server)
  ③ HIDL → 再次 flatten/unflatten(system_server → Provider)
  ④ HAL 内部: Session → Pipeline → Node(property 依赖过滤)
  ⑤ ISP 处理完后: Node → Pipeline → Session(汇聚 result)
  ⑥ 原路返回: HAL → Provider → system_server → App

关键检查点:
  dumpsys media.camera → 看 Static Metadata
  dumpsys media.camera | grep "Vendor tags" → 看 Vendor Tag

动手验证

  1. 执行 adb shell dumpsys media.camera | grep -A 30 "Camera 0",列出后摄的 5 个 Static Metadata 项及其值。

  2. 找到至少两个 Standard Tag 和两个 Vendor Tag。确认它们的 tag ID 前缀(判断 Standard 还是 Vendor)。

  3. 在 App 中设置 CONTROL_AE_MODE = OFF 和一个手动曝光时间,然后在 onCaptureCompleted 中读取 SENSOR_EXPOSURE_TIME,确认 HAL 层是否应用了你的设置。

常见误解