3. Metadata 数据结构:camera_metadata_t 结构体、entry 与 buffer 布局、内存对齐规则

Camera HAL3 的 Metadata 传递依赖一套精心设计的内存数据结构。理解 camera_metadata_t 的布局、entry 的存储方式以及内存对齐规则,是编写正确 HAL 实现和测试用例的基础。本章从底层结构出发,逐步拆解 Metadata 的物理存储模型。

3.1 camera_metadata_t 结构体总览

camera_metadata_t 是 Android Camera 框架中所有静态、动态及控制 Metadata 的容器。它采用紧凑的连续内存布局,将元数据信息(tag、type、count)与数据值(value)分离存储,以支持高效的序列化与跨进程传递。

核心设计原则: 所有 Metadata 数据存储在一块连续的内存块中,通过偏移量(offset)而非指针进行寻址,确保数据在进程间传递时无需序列化/反序列化。

结构体定义位于 system/media/camera/include/system/camera_metadata.h,其核心字段如下:

字段名 类型 说明
version uint32_t 结构体版本号,用于兼容性检查
size uint32_t 整个 buffer 的总字节数(包括头部、entry 区、数据区)
entry_count uint32_t 当前存储的 entry 数量
entry_capacity uint32_t entry 区的最大容量(以 entry 个数计)
entries camera_metadata_buffer_entry_t[] entry 数组起始位置(柔性数组)
data uint8_t[] 数据区起始位置(紧接 entry 数组之后)
测试要点: 在验证 Metadata 传递时,应检查 size 字段是否等于 sizeof(camera_metadata_t) + entry_capacity * sizeof(camera_metadata_buffer_entry_t) + data_capacity,这是判断 buffer 完整性的基本依据。

3.2 Entry 数据结构与布局

每个 Metadata 条目(entry)使用 camera_metadata_buffer_entry_t 结构体描述,该结构体固定为 32 字节(在 64 位系统上)。

typedef struct camera_metadata_buffer_entry {
    uint32_t tag;       // 元数据标签,如 ANDROID_SENSOR_EXPOSURE_TIME
    uint32_t type;      // 数据类型,如 TYPE_INT64
    uint32_t count;     // 数据个数(非字节数)
    union {
        uint32_t offset; // 数据在 data 区中的偏移量(当数据 > 4 字节时)
        uint32_t value;  // 直接存储 4 字节以内的数据(内联存储)
    } data_field;
    uint8_t data_type;  // 冗余存储 type 的低 8 位,用于快速访问
    uint8_t reserved[3];// 保留字节,用于对齐
} camera_metadata_buffer_entry_t;

Entry 的数据存储采用 内联/外联混合策略:

  • 内联存储(Inline): 当数据总大小 ≤ 4 字节时,直接存储在 data_field.value 中,不占用 data 区空间。
  • 外联存储(Out-of-line): 当数据总大小 > 4 字节时,data_field.offset 指向 data 区中的起始位置,实际数据存储在 data 区。
常见陷阱: 测试时容易忽略 count 字段的含义——它表示元素个数,而非字节数。例如 ANDROID_SENSOR_TIMESTAMP 的 type 为 TYPE_INT64,count 为 1,实际占用 8 字节,因此必须使用外联存储。

3.3 Buffer 整体布局与内存对齐

一个完整的 camera_metadata_t buffer 在内存中的布局分为三个区域:

  1. 头部区: camera_metadata_t 结构体本身(固定大小,约 48 字节)。
  2. Entry 区: 连续的 camera_metadata_buffer_entry_t 数组,每个 entry 32 字节。
  3. 数据区: 存储所有外联数据的连续字节流。

下图展示了 buffer 的物理布局与 entry 的数据引用关系:

camera_metadata_t Buffer 布局与 Entry 数据引用 头部区 camera_metadata_t ~48 字节 Entry 区 camera_metadata_buffer_entry_t[] 每个 entry 32 字节(固定) 数据区 uint8_t data[] 存储外联数据(变长) size 字段记录的总字节数 Entry A(内联) tag=0x001, type=INT32, count=1 data_field.value = 0x0000000A Entry B(外联) tag=0x100, type=INT64, count=1 data_field.offset → 数据区 外联数据 0x00000000 0x0000002A 内联存储:数据 ≤ 4 字节时直接存入 entry;外联存储:数据 > 4 字节时通过 offset 引用数据区

3.4 内存对齐规则详解

为了保证跨平台兼容性和访问效率,camera_metadata_t 的 buffer 遵循严格的内存对齐规则:

对齐规则 说明 测试验证方法
整体对齐 整个 buffer 起始地址必须 8 字节对齐 检查 (uintptr_t)buffer % 8 == 0
Entry 对齐 每个 entry 的起始地址必须 4 字节对齐(32 字节固定大小天然满足) 遍历 entry 数组,检查每个 entry 地址
数据对齐 数据区中每种类型的数据按其自然对齐方式存储(如 int64 按 8 字节对齐) 检查 offset % sizeof(type) == 0
填充字节 entry 区与数据区之间无填充,但数据区内部可能因对齐要求产生填充字节 验证 data_start 紧接在 entry 数组之后
对齐验证代码片段: 在测试中,可使用以下逻辑验证数据对齐:
// 验证 entry 中 offset 的对齐性
camera_metadata_buffer_entry_t *entry = &entries[i];
if (entry->count * camera_metadata_type_size[entry->type] > 4) {
    // 外联存储,检查 offset 对齐
    size_t type_size = camera_metadata_type_size[entry->type];
    if (entry->data_field.offset % type_size != 0) {
        // 报告对齐错误
    }
}

3.5 数据区布局与类型大小对照

数据区中每个外联数据的起始偏移量必须满足其类型的对齐要求。下表列出了常见 Metadata 类型的大小与对齐要求:

类型枚举 C 类型 大小(字节) 对齐要求(字节)
TYPE_BYTE uint8_t 1 1
TYPE_INT32 int32_t 4 4
TYPE_FLOAT float 4 4
TYPE_INT64 int64_t 8 8
TYPE_DOUBLE double 8 8
TYPE_RATIONAL camera_metadata_rational_t 8 4
测试技巧: 使用 camera_metadata_type_size[] 和 camera_metadata_type_alignment[] 这两个全局数组(定义在 camera_metadata.c 中)可以快速获取类型大小和对齐值,避免硬编码。

3.6 数据区偏移量计算示例

假设一个 buffer 包含两个外联 entry,数据区布局的计算过程如下:

  1. Entry A: tag=0x001, type=INT64, count=1 → 数据大小 8 字节,对齐要求 8 字节。
  2. Entry B: tag=0x002, type=INT32, count=3 → 数据大小 12 字节,对齐要求 4 字节。

数据区偏移量计算:

// 数据区起始偏移量 = 0
// Entry A 数据:offset = 0(满足 8 字节对齐)
// 占用 [0, 7]
// Entry B 数据:offset = 8(满足 4 字节对齐)
// 占用 [8, 19]
// 数据区总大小 = 20 字节

下图展示了上述示例的数据区布局:

数据区偏移量计算示例 数据区(20 字节) Entry A offset=0 Entry B offset=8 0 8 20 Entry A 起始偏移 0 满足 8 字节对齐;Entry B 起始偏移 8 满足 4 字节对齐

3.7 测试验证清单

在编写 Metadata 传递测试用例时,应至少覆盖以下数据结构验证点:

  • 完整性检查: 验证 size 字段与实际 buffer 大小一致。
  • Entry 遍历: 遍历所有 entry,检查 tag 是否在有效范围内,type 是否合法。
  • 内联/外联判断: 根据数据大小验证 entry 使用的是内联还是外联存储。
  • 偏移量范围: 外联 entry 的 offset 必须在 [0, data_capacity - data_size) 范围内。
  • 对齐验证: 外联数据的 offset 必须满足其类型的对齐要求。
  • 数据区不重叠: 不同外联 entry 的数据区间不能重叠。
边界情况: 当 count=0 时,entry 不占用数据区空间,但 entry 本身仍然存在。测试时应确保 count=0 的 entry 不会导致数据区访问越界。