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 在内存中的布局分为三个区域:
- 头部区:
camera_metadata_t结构体本身(固定大小,约 48 字节)。 - Entry 区: 连续的
camera_metadata_buffer_entry_t数组,每个 entry 32 字节。 - 数据区: 存储所有外联数据的连续字节流。
下图展示了 buffer 的物理布局与 entry 的数据引用关系:
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,数据区布局的计算过程如下:
- Entry A: tag=0x001, type=INT64, count=1 → 数据大小 8 字节,对齐要求 8 字节。
- 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 字节
下图展示了上述示例的数据区布局:
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 不会导致数据区访问越界。