Camera Metadata 从硬件到软件完整详解

一、硬件层面:Metadata 的来源

1.1 Sensor 嵌入式数据(Embedded Data Lines)

Sensor 芯片在输出每帧图像时,会在有效像素行的前面或后面附加几行”非图像数据”:

一帧 CSI-2 传输的完整内容:

┌─────────────────────────────────────────────────┐
│ Frame Start (FS) 短包                            │  ← CSI-2 协议帧开始标记
├─────────────────────────────────────────────────┤
│ Embedded Data Line 0  (Data Type = 0x12)        │  ← 寄存器镜像:曝光、增益
│ Embedded Data Line 1  (Data Type = 0x12)        │  ← 帧计数、温度、时间戳
├─────────────────────────────────────────────────┤
│ Image Line 0          (Data Type = 0x2B=Raw10)  │  ← 真正的像素数据开始
│ Image Line 1                                     │
│ ...                                              │
│ Image Line 1943                                  │  ← 最后一行像素
├─────────────────────────────────────────────────┤
│ Embedded Data Line N  (Data Type = 0x12)        │  ← (可选) 帧尾统计
├─────────────────────────────────────────────────┤
│ Frame End (FE) 短包                              │  ← CSI-2 协议帧结束标记
└─────────────────────────────────────────────────┘

Embedded data 的物理内容(以 OV5647/IMX219 为例):

Embedded Line 0 (字节流):
┌────┬────┬────┬────┬────┬────┬────┬────┬────────────────┐
│0x0A│0x0A│0x0A│0x0A│0x3D│0x01│0x00│0x10│ ...            │
└────┴────┴────┴────┴────┴────┴────┴────┴────────────────┘
 │              │         │         │
 │              │         │         └─ 寄存器值 (如 analog gain = 0x10)
 │              │         └─ 寄存器地址 (如 0x0157 = analog gain reg)
 │              └─ Tag byte (0x3D = valid register data)
 └─ Padding / line sync

实际就是 sensor 内部寄存器的镜像,告诉你这一帧
实际用了什么曝光时间、增益、帧号等。

为什么需要 embedded data?

因为 sensor 的寄存器设置和实际生效之间有延迟(通常 2-3 帧)。你在第 N 帧设置了曝光=10ms,可能第 N+2 帧才生效。Embedded data 告诉你”这一帧实际用的参数是什么”。

1.2 CSI-2 Data Type 区分

CSI-2 协议用 Data Type 字段区分不同类型的数据:

Data Type    含义                    用途
─────────────────────────────────────────────────
0x00-0x07    短包 (Frame/Line Sync)  帧/行同步
0x12         Embedded 8-bit          Sensor 嵌入式数据
0x2A         Raw 8-bit               8bit 图像
0x2B         Raw 10-bit              10bit 图像 (OV5647)
0x2C         Raw 12-bit              12bit 图像
0x2D         Raw 14-bit              14bit 图像

CSI-2 接收器(CSI RX)硬件根据 Data Type 将数据路由到不同的 DMA 通道:

CSI-2 总线
    │
    ├─ DT=0x2B → DMA Channel 0 → DDR (图像 buffer)
    │
    └─ DT=0x12 → DMA Channel 1 → DDR (metadata buffer)

二、CSI 接收器硬件

2.1 硬件寄存器配置

/* CSI-2 接收器需要配置哪些 virtual channel / data type 要捕获 */

/* 以 imx8 MIPI CSI-2 为例,寄存器配置: */
/*
 * CH0_CFG: data_type=0x2B, virtual_channel=0  → 图像
 * CH1_CFG: data_type=0x12, virtual_channel=0  → embedded data
 *
 * 每个 channel 有独立的 DMA 目标地址和 buffer 大小
 */

/* 硬件框图:
 *
 * MIPI D-PHY → CSI-2 Controller → VC/DT Demux → CH0 DMA → DDR (image)
 *                                              → CH1 DMA → DDR (metadata)
 */

三、内核驱动层

3.1 CSI 驱动:区分 image 和 metadata

/* CSI 驱动为 image 和 metadata 创建不同的 pad/video node */

struct csi_device {
    struct v4l2_subdev sd;
    struct media_pad pads[3];
    /* pad 0: sink (来自 sensor) */
    /* pad 1: source (image → ISP 或 video node) */
    /* pad 2: source (embedded data → meta video node) */
};

3.2 Video Device 注册

/* 驱动注册两种 video device */

/* 图像捕获节点: /dev/video0 */
struct video_device vdev_image = {
    .name = "csi-capture",
    .vfl_type = VFL_TYPE_VIDEO,        /* 普通视频设备 */
    .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING,
};

/* Metadata 捕获节点: /dev/video1 */
struct video_device vdev_meta = {
    .name = "csi-meta",
    .vfl_type = VFL_TYPE_VIDEO,        /* 也是 video 设备 */
    .device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING,
};

3.3 Metadata 格式描述

/* 用户空间通过 ioctl 查询 metadata 格式 */
struct v4l2_format {
    __u32 type;                         /* V4L2_BUF_TYPE_META_CAPTURE */
    union {
        struct v4l2_meta_format meta;
        ...
    } fmt;
};

struct v4l2_meta_format {
    __u32 dataformat;    /* 如 V4L2_META_FMT_GENERIC_8
                          *    V4L2_META_FMT_IMX219_EMBEDDED
                          *    V4L2_META_FMT_SENSOR_DATA */
    __u32 buffersize;    /* 一帧 metadata 的字节数
                          * = embedded_lines × line_width
                          * 如 2 lines × 2592 bytes = 5184 bytes */
};

3.4 DMA Buffer 中 Metadata 的内存布局

/* Embedded data buffer 内容 (mmap 后用户空间看到的) */

/*
 * 以 IMX219 为例, 2 行 embedded data, 每行宽度同图像宽度:
 *
 * Offset 0x0000: Line 0 (3280 bytes)
 *   ┌──────────────────────────────────────────────────────┐
 *   │ Tag │ Reg_H │ Reg_L │ Value │ Tag │ Reg_H │ ...     │
 *   │ 0xAA│ 0x01  │ 0x5A  │ 0x04  │ 0xAA│ 0x01  │ ...     │
 *   └──────────────────────────────────────────────────────┘
 *         │         │         │
 *         │         │         └─ coarse_integration_time[15:8] = 0x04
 *         │         └─ register address 0x015A
 *         └─ tag: valid data
 *
 * Offset 0x0CD0: Line 1 (3280 bytes)
 *   ┌──────────────────────────────────────────────────────┐
 *   │ 更多寄存器镜像数据 ...                                 │
 *   └──────────────────────────────────────────────────────┘
 */

/* 解析 embedded data 得到实际参数: */
struct sensor_embedded_info {
    uint32_t frame_count;           /* 帧计数器 */
    uint32_t coarse_integration;    /* 曝光行数 (曝光时间 = 行数 × 行时间) */
    uint16_t analog_gain;           /* 模拟增益 */
    uint16_t digital_gain;          /* 数字增益 */
    uint16_t frame_length;          /* 帧长度 (用于计算帧率) */
    uint16_t line_length;           /* 行长度 */
    uint8_t  sensor_mode;           /* 当前模式 */
    int8_t   temperature;           /* 芯片温度 */
};

四、ISP 统计信息(3A Stats)

4.1 硬件产生统计数据

ISP 硬件内部:

Raw 图像数据 ──→ ┌─────────────────────────────────────────┐
                  │ ISP Pipeline                             │
                  │                                          │
                  │  ┌─────────┐   ┌─────────┐   ┌──────┐  │
                  │  │ BLC     │→  │ LSC     │→  │ AWB  │  │
                  │  │黑电平校正│   │镜头阴影 │   │白平衡│  │
                  │  └─────────┘   └─────────┘   └──┬───┘  │
                  │                                   │      │
                  │  ┌─────────┐   ┌─────────┐       │      │
                  │  │ AE 统计 │   │ AF 统计 │       │      │
                  │  │ 模块    │   │ 模块    │       │      │
                  │  └────┬────┘   └────┬────┘       │      │
                  │       │             │             │      │
                  └───────┼─────────────┼─────────────┼──────┘
                          │             │             │
                          ▼             ▼             ▼
                  ┌─────────────────────────────────────────┐
                  │ Stats DMA → DDR (stats buffer)          │
                  │                                          │
                  │ AE: 每个区域的亮度均值/直方图             │
                  │ AWB: 每个区域的 R/G/B 均值               │
                  │ AF: 每个区域的高频分量(锐度)             │
                  └─────────────────────────────────────────┘

4.2 Stats 数据结构

/* 以 rkisp1 为例 (各平台结构不同但概念相同) */

/* AE 统计: 将图像分成 5×5=25 个区域 */
struct rkisp1_cif_isp_ae_stat {
    __u8 exp_mean[25];    /* 每个区域的平均亮度 (0-255) */
    /*
     * 图像被分成 5×5 网格:
     * ┌────┬────┬────┬────┬────┐
     * │ 45 │ 50 │ 52 │ 48 │ 44 │  ← 各区域平均亮度
     * ├────┼────┼────┼────┼────┤
     * │ 60 │128 │135 │130 │ 58 │
     * ├────┼────┼────┼────┼────┤
     * │ 65 │140 │180 │138 │ 62 │  ← 中心最亮(主体)
     * ├────┼────┼────┼────┼────┤
     * │ 55 │120 │125 │118 │ 52 │
     * ├────┼────┼────┼────┼────┤
     * │ 40 │ 45 │ 48 │ 44 │ 38 │
     * └────┴────┴────┴────┴────┘
     */
};

/* AWB 统计: 每个区域的颜色信息 */
struct rkisp1_cif_isp_awb_stat {
    struct {
        __u16 mean_r;     /* 区域内红色均值 */
        __u16 mean_g;     /* 区域内绿色均值 */
        __u16 mean_b;     /* 区域内蓝色均值 */
        __u16 pixel_cnt;  /* 有效像素数 */
    } awb_mean[25];
    /*
     * 用户空间 AWB 算法根据这些值计算:
     * R_gain = G_mean / R_mean
     * B_gain = G_mean / B_mean
     * 使得白色物体的 R=G=B
     */
};

/* AF 统计: 对焦锐度 */
struct rkisp1_cif_isp_af_stat {
    struct {
        __u32 sharpness;   /* 高通滤波后的能量值 */
        __u32 luminance;   /* 区域亮度 */
    } af_window[3];        /* 通常 3 个对焦窗口 */
    /*
     * 对焦算法移动镜头,找到 sharpness 最大的位置
     *
     * sharpness
     *    ▲
     *    │      ╱╲
     *    │     ╱  ╲
     *    │    ╱    ╲
     *    │   ╱      ╲
     *    │──╱────────╲──→ 镜头位置
     *              ↑
     *          最佳对焦点
     */
};

/* 直方图 */
struct rkisp1_cif_isp_hist_stat {
    __u32 hist_bins[32];   /* 32 个亮度区间的像素计数 */
    /*
     * hist_bins[0]  = 暗部像素数 (亮度 0-7)
     * hist_bins[1]  = 亮度 8-15 的像素数
     * ...
     * hist_bins[31] = 最亮像素数 (亮度 248-255)
     *
     *  像素数
     *    ▲
     *    │   ██
     *    │   ██ ██
     *    │██ ██ ██ ██
     *    │██ ██ ██ ██    ██
     *    │██ ██ ██ ██ ██ ██
     *    └──────────────────→ 亮度
     *    暗              亮
     */
};

五、3A 参数的两条控制路径

3A 参数并不是只配置在 ISP 上,实际上分为两条路径:

┌────────────────────────────────────────────────────────────────┐
│ 路径 1: 控制 Sensor (通过 V4L2 control / I2C)                   │
│                                                                 │
│ 用户空间 → VIDIOC_S_CTRL → sensor subdev → I2C → sensor 寄存器 │
│                                                                 │
│ 控制内容:                                                       │
│   - 曝光时间 (V4L2_CID_EXPOSURE)                                │
│   - 模拟增益 (V4L2_CID_ANALOGUE_GAIN)                           │
│   - 数字增益 (V4L2_CID_DIGITAL_GAIN)                            │
│   - 翻转 (V4L2_CID_HFLIP / V4L2_CID_VFLIP)                    │
│   - 测试图案 (V4L2_CID_TEST_PATTERN)                            │
│                                                                 │
│ 为什么必须在 sensor 端:                                          │
│   曝光和增益控制的是物理进光量和信号放大,                         │
│   ISP 收到的 Raw 数据亮度已经固定,无法事后改变。                  │
└────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────┐
│ 路径 2: 控制 ISP (通过 meta output buffer)                      │
│                                                                 │
│ 用户空间 → meta output video node → ISP 硬件寄存器              │
│                                                                 │
│ 控制内容:                                                       │
│   - 白平衡增益 (AWB gain)                                       │
│   - 色彩校正矩阵 (CCM)                                          │
│   - Gamma 曲线                                                  │
│   - 镜头阴影校正 (LSC)                                          │
│   - 坏点校正 (DPCC)                                             │
│   - 降噪参数 (NR)                                               │
│                                                                 │
│ 为什么在 ISP 端:                                                 │
│   这些是对已采集 Raw 数据的数学运算,                              │
│   在 ISP 做更灵活,且不同 sensor 可以共用同一套 ISP 算法。        │
└────────────────────────────────────────────────────────────────┘

两者配合才是完整的 3A 控制:

AE 算法 → 输出曝光/增益 → 写 Sensor (路径 1)
AWB 算法 → 输出白平衡增益/CCM → 写 ISP (路径 2)
AF 算法 → 输出对焦位置 → 写 Lens Actuator subdev (也是路径 1 的方式)

对应的内核接口:

/* 路径 1: V4L2 control 写 sensor */
struct v4l2_control ctrl = {
    .id = V4L2_CID_EXPOSURE,
    .value = 1000,  /* 曝光行数 */
};
ioctl(sensor_fd, VIDIOC_S_CTRL, &ctrl);
/* 驱动内部: i2c_write(client, EXPOSURE_REG, 1000) */

/* 路径 2: meta output buffer 写 ISP */
struct rkisp1_params_cfg *params = mmap(...);
params->awb_gain.gain_red = 307;   /* 1.2x */
params->awb_gain.gain_blue = 384;  /* 1.5x */
ioctl(params_fd, VIDIOC_QBUF, &buf);
/* 驱动内部: writel(307, isp_base + AWB_GAIN_R_REG) */

六、ISP 参数输入详细数据结构

6.1 用户空间算法计算结果写回 ISP

/* 用户空间 3A 算法 (如 libcamera 的 IPA) 计算出参数后,
 * 通过 meta output video node 写入 ISP */

struct rkisp1_params_cfg {
    __u32 module_en_update;    /* 哪些模块需要更新 */
    __u32 module_ens;          /* 模块使能位 */
    __u32 module_cfg_update;   /* 哪些模块配置需要更新 */

    /* 各模块参数: */
    struct rkisp1_cif_isp_awb_gain_config awb_gain;
    struct rkisp1_cif_isp_aec_config aec;
    struct rkisp1_cif_isp_bls_config bls;       /* 黑电平 */
    struct rkisp1_cif_isp_dpcc_config dpcc;     /* 坏点校正 */
    struct rkisp1_cif_isp_lsc_config lsc;       /* 镜头阴影校正 */
    struct rkisp1_cif_isp_ccm_config ccm;       /* 色彩校正矩阵 */
    struct rkisp1_cif_isp_goc_config goc;       /* Gamma */
    ...
};

/* AWB 增益参数 */
struct rkisp1_cif_isp_awb_gain_config {
    __u16 gain_red;        /* 如 1.2x = 307 (256=1.0x) */
    __u16 gain_green_r;    /* 1.0x = 256 */
    __u16 gain_green_b;    /* 1.0x = 256 */
    __u16 gain_blue;       /* 如 1.5x = 384 */
};

/* 色彩校正矩阵 3×3 */
struct rkisp1_cif_isp_ccm_config {
    __s16 matrix[3][3];
    /*  ┌                    ┐   ┌   ┐     ┌   ┐
     *  │ 1.8  -0.5  -0.3   │   │ R │     │ R'│
     *  │-0.2   1.6  -0.4   │ × │ G │  =  │ G'│
     *  │-0.1  -0.6   1.7   │   │ B │     │ B'│
     *  └                    ┘   └   ┘     └   ┘
     */
    __s16 offsets[3];      /* RGB 偏移 */
};

七、用户空间 3A 控制循环

┌─────────────────────────────────────────────────────────┐
│                    用户空间 (libcamera IPA)              │
│                                                          │
│   ┌──────────┐     ┌──────────┐     ┌──────────┐       │
│   │ AE 算法  │     │ AWB 算法 │     │ AF 算法  │       │
│   │          │     │          │     │          │       │
│   │ 输入:    │     │ 输入:    │     │ 输入:    │       │
│   │  ae_stat │     │ awb_stat │     │ af_stat  │       │
│   │  hist    │     │          │     │          │       │
│   │          │     │          │     │          │       │
│   │ 输出:    │     │ 输出:    │     │ 输出:    │       │
│   │ exposure │     │ awb_gain │     │ lens_pos │       │
│   │ gain     │     │ ccm      │     │          │       │
│   └─────┬────┘     └─────┬────┘     └─────┬────┘       │
│         │                 │                 │            │
└─────────┼─────────────────┼─────────────────┼────────────┘
          │                 │                 │
          ▼                 ▼                 ▼
┌─────────────────┐  ┌──────────────┐  ┌──────────────┐
│ V4L2 控制       │  │ Meta output  │  │ V4L2 控制    │
│ VIDIOC_S_CTRL   │  │ params buffer│  │ (lens subdev)│
│ → sensor 寄存器 │  │ → ISP 寄存器 │  │ → 马达驱动   │
│ (曝光/增益)     │  │ (AWB/CCM/γ)  │  │ (对焦位置)   │
└────────┬────────┘  └──────┬───────┘  └──────┬───────┘
         │                   │                  │
         ▼                   ▼                  ▼
┌─────────────────────────────────────────────────────────┐
│                      硬件                                │
│  Sensor          ISP                    Lens Actuator   │
│  (新曝光/增益)   (新白平衡/色彩)        (新焦距)        │
│       │               │                      │          │
│       └───────────────┼──────────────────────┘          │
│                       ▼                                  │
│              下一帧图像 + 新的 stats                      │
│                       │                                  │
└───────────────────────┼──────────────────────────────────┘
                        │
                        ▼
                回到用户空间 3A 算法 (循环)

八、时间同步:Metadata 与图像帧的对应

/* 关键问题: 如何保证 stats 和 params 与正确的帧对应? */

/* V4L2 Request API 解决这个问题 */
struct media_request {
    /* 一个 request 绑定了同一帧的所有 buffer 和控制 */
};

/*
 * 时间线:
 *
 * 帧 N:   sensor 曝光 → CSI 传输 → ISP 处理 → image buffer N 完成
 *                                            → stats buffer N 完成
 *                                                    │
 *                                                    ▼
 *         用户空间收到 stats N → 3A 算法计算 → 产生 params
 *                                                    │
 *                                                    ▼
 *         params 写入 → ISP 在帧 N+1 或 N+2 应用
 *         sensor ctrl → sensor 在帧 N+2 或 N+3 生效
 *
 * 延迟 (pipeline delay):
 *   sensor 设置到生效: 通常 2 帧
 *   ISP params 到生效: 通常 1 帧
 *   总 3A 收敛延迟: 3-5 帧
 */

/* 每个 buffer 带时间戳用于匹配 */
struct v4l2_buffer {
    ...
    struct timeval timestamp;    /* 帧捕获时间 */
    __u32 sequence;              /* 帧序号,image/stats/embedded 用同一序号匹配 */
};

九、libcamera 中的 Metadata

/* libcamera 在用户空间统一管理 metadata */
/* 每帧的 Request 包含: */

/*
 * Request {
 *     ├─ Stream buffer (图像数据)
 *     ├─ ControlList metadata (输出):
 *     │    ExposureTime: 33333 us
 *     │    AnalogueGain: 2.0
 *     │    ColourTemperature: 5500K
 *     │    SensorTimestamp: 123456789 ns
 *     │    Lux: 300
 *     │
 *     └─ ControlList controls (输入):
 *          AeEnable: true
 *          AwbMode: Auto
 *          Brightness: 0.0
 * }
 */

十、完整 Pipeline(含 Metadata)

┌─────────────────────────────────────────────────────────────┐
│ Sensor                                                       │
│  ├─ source pad 0: image data (Raw10 GBRG)                   │
│  └─ source pad 1: embedded metadata (寄存器镜像/统计)        │
└──────────┬────────────────────────┬─────────────────────────┘
           │ CSI-2 (DT=0x2B)       │ CSI-2 (DT=0x12)
           ▼                        ▼
┌──────────────────────┐  ┌────────────────────────┐
│ CSI Receiver         │  │ CSI Receiver           │
│ image path           │  │ embedded data path     │
└──────────┬───────────┘  └────────────┬───────────┘
           │                            │
           ▼                            ▼
┌──────────────────────┐  ┌────────────────────────┐
│ ISP                  │  │ /dev/videoX (meta cap) │
│  ├─ 处理图像         │  │ V4L2_META_FMT_xxx     │
│  ├─ 产生 3A stats ──────→ /dev/videoY (stats)   │
│  └─ 接收 3A params ←────── /dev/videoZ (params) │
└──────────┬───────────┘  └────────────────────────┘
           │
           ▼
┌──────────────────────┐
│ /dev/video0          │
│ 处理后的 YUV/RGB     │
└──────────────────────┘

十一、总结:Metadata 在各层的表现

层次          图像数据                    Metadata
─────────────────────────────────────────────────────────────────
物理层        光子 → 电子 → ADC           sensor 寄存器状态

CSI-2 总线    DT=0x2B (Raw10)            DT=0x12 (Embedded)
              长包, 每行一个包             长包, 1-2 行

CSI RX 硬件   DMA CH0 → DDR              DMA CH1 → DDR

内核驱动      /dev/video0                 /dev/video1
              V4L2_BUF_TYPE_VIDEO_CAPTURE V4L2_BUF_TYPE_META_CAPTURE
              v4l2_pix_format             v4l2_meta_format

ISP 硬件      处理图像                    产生 3A stats
                                          接收 3A params

ISP 驱动      /dev/video0 (输出YUV)       /dev/video1 (stats 输出)
                                          /dev/video2 (params 输入)

用户空间      显示/编码/存储              3A 算法输入/输出
(libcamera)   FrameBuffer                 ControlList / metadata