4. notify()回调详解:快门回调、错误回调、3A回调、元数据回调

好,咱们接着聊。上一节我们把 process_capture_result() 讲透了,这一节轮到 notify() 了。

说实话,notify() 这个回调在 HAL 层里看着不起眼,但它承担的任务非常关键。它不像 process_capture_result() 那样频繁调用,可一旦被触发,往往意味着发生了「大事」——要么是快门按下了,要么是出错了,要么是 3A 状态变了。

我个人习惯把 notify() 比作「报警器」。它不负责传输图像数据,只负责传递「事件」。Camera Framework 通过它来感知底层发生了什么。

核心要点:notify() 回调传递的是 camera3_notify_msg_t 结构体,里面包含三种消息类型:快门消息、错误消息、以及 3A/元数据消息。

4.1 回调接口定义

先看一眼接口长什么样:

typedef struct camera3_callback_ops {
    // ... 其他回调 ...
    void (*notify)(const struct camera3_callback_ops *ops,
                   const camera3_notify_msg_t *msg);
} camera3_callback_ops_t;

嗯,很简洁。传入一个 msg 指针,里面就是事件内容。那 camera3_notify_msg_t 长什么样?

typedef struct camera3_notify_msg {
    uint32_t type;
    union {
        camera3_shutter_msg_t shutter;
        camera3_error_msg_t   error;
        camera3_3a_msg_t      three_a;
    } message;
} camera3_notify_msg_t;

你看,type 字段决定了 message 联合体里哪个成员有效。取值有三种:

type 值 对应结构体 含义
CAMERA3_MSG_SHUTTER camera3_shutter_msg_t 快门回调
CAMERA3_MSG_ERROR camera3_error_msg_t 错误回调
CAMERA3_MSG_3A camera3_3a_msg_t 3A/元数据回调

4.2 快门回调:最常用的通知

快门回调,说白了就是告诉 Framework:「传感器已经曝光了,你可以记录时间戳了。」

结构体定义:

typedef struct camera3_shutter_msg {
    uint32_t frame_number;
    uint64_t timestamp;
} camera3_shutter_msg_t;

这里有两个字段:frame_number 对应你提交的请求帧号,timestamp 是曝光开始的时间戳,单位是纳秒。

我的经验:时间戳必须用 CLOCK_MONOTONIC,不能用 CLOCK_REALTIME。否则在 CTS 测试里会挂掉。我曾经有个同事用了系统时间,结果 CTS 跑了一晚上全红了……

什么时候触发快门回调?

  • 对于 JPEG 或 RAW 输出:在传感器开始曝光时触发
  • 对于 YUV 或 HEIC 输出:在传感器开始曝光时触发
  • 对于 ZSL 模式:在传感器曝光完成时触发

你想想看,Framework 拿到这个时间戳后,会用它来做什么?

  • 计算拍照延迟(shutter lag)
  • 同步多摄像头的时间轴
  • 给应用层提供「按下快门」的反馈

所以,这个回调必须及时。我建议在传感器中断服务函数里直接调用 notify(),不要放到工作线程里排队。否则时间戳就不准了。

4.3 错误回调:出了问题怎么办

错误回调是 HAL 向 Framework 报告「我出事了」的唯一通道。结构体如下:

typedef struct camera3_error_msg {
    uint32_t frame_number;
    int      error_stream_id;
    int      error_code;
} camera3_error_msg_t;

error_code 定义了错误的类型:

错误码 含义 影响范围
CAMERA3_MSG_ERROR_DEVICE 设备级错误 整个 Camera 设备不可用
CAMERA3_MSG_ERROR_REQUEST 请求级错误 单个 capture request 失败
CAMERA3_MSG_ERROR_RESULT 结果级错误 某个 result 无法生成
CAMERA3_MSG_ERROR_BUFFER Buffer 级错误 某个 stream 的 buffer 出错

注意:设备级错误一旦上报,Framework 会直接关闭 Camera。所以除非万不得已,不要轻易报 CAMERA3_MSG_ERROR_DEVICE

我曾经在一个项目里遇到过:ISP 的 3A 统计模块偶尔会挂死,导致某个 request 的 result 出不来。当时我犯了个错——报了设备级错误,结果整个相机都打不开了。后来改成报 CAMERA3_MSG_ERROR_REQUEST,Framework 只是丢掉这一帧,用户几乎感觉不到。

所以,我的建议是:

  • 能报 BUFFER 就别报 RESULT
  • 能报 RESULT 就别报 REQUEST
  • 能报 REQUEST 就别报 DEVICE

错误粒度越细,对用户体验的影响越小。

4.4 3A回调:自动对焦、自动曝光、自动白平衡

3A 回调是 Android 5.0 之后引入的,用来替代老旧的 auto_focusauto_exposure 回调。结构体如下:

typedef struct camera3_3a_msg {
    uint32_t frame_number;
    camera3_3a_mode_t mode;
    camera3_3a_trigger_t trigger;
    int32_t    af_trigger_id;
    int32_t    ae_trigger_id;
    int32_t    awb_trigger_id;
} camera3_3a_msg_t;

这里 modetrigger 是枚举类型,定义了 3A 的状态机变化。比如:

  • CAMERA3_3A_MODE_AF:自动对焦模式
  • CAMERA3_3A_MODE_AE:自动曝光模式
  • CAMERA3_3A_MODE_AWB:自动白平衡模式
  • CAMERA3_3A_TRIGGER_START:触发开始
  • CAMERA3_3A_TRIGGER_CANCEL:触发取消
  • CAMERA3_3A_TRIGGER_COMPLETE:触发完成

你可能会问:「这些信息不是已经在 result metadata 里了吗?为什么还要单独回调?」

嗯,问得好。原因很简单——实时性。result metadata 要等到整个 pipeline 处理完才能返回,而 3A 状态变化需要立即通知 Framework。比如用户按下了对焦按钮,Framework 需要马上知道对焦是否完成,不能等一帧图像处理完。

我的习惯:3A 回调只在触发状态发生变化时才调用。如果 3A 一直处于稳定状态,不要每帧都发。否则 Framework 那边会收到大量无用的通知,浪费 CPU。

4.5 元数据回调:其实是个「伪概念」

标题里提到了「元数据回调」,这里需要澄清一下。

在 Android Camera HAL3 的设计中,并没有独立的元数据回调。所谓的「元数据回调」,其实是指 CAMERA3_MSG_3A 消息里携带的 camera3_3a_msg_t 结构体。它里面包含了 AF、AE、AWB 的触发 ID 和状态信息,这些本质上就是元数据。

为什么大家会把它叫成「元数据回调」?

我猜是因为早期 Android 版本(4.x)里,有一个独立的 notify_metadata() 回调。到了 HAL3 时代,这个功能被合并到 notify() 里了。但很多老工程师还是习惯这么叫。

所以,你如果在代码里看到有人注释写「元数据回调」,别懵,指的就是 CAMERA3_MSG_3A

4.6 实战:如何正确实现 notify()

最后,我给出一个实际项目中用到的实现模板:

static void my_notify(const struct camera3_callback_ops *ops,
                      const camera3_notify_msg_t *msg) {
    // 获取 HAL 设备实例
    my_camera_device_t *dev = (my_camera_device_t *)ops;

    switch (msg->type) {
        case CAMERA3_MSG_SHUTTER: {
            // 快门回调:直接转发,不要延迟
            dev->callback_ops->notify(dev->callback_ops, msg);
            break;
        }
        case CAMERA3_MSG_ERROR: {
            // 错误回调:先记录日志,再转发
            ALOGE("Error on frame %d, stream %d, code %d",
                  msg->message.error.frame_number,
                  msg->message.error.error_stream_id,
                  msg->message.error.error_code);
            dev->callback_ops->notify(dev->callback_ops, msg);
            break;
        }
        case CAMERA3_MSG_3A: {
            // 3A 回调:只在状态变化时转发
            if (is_3a_state_changed(dev, &msg->message.three_a)) {
                dev->callback_ops->notify(dev->callback_ops, msg);
            }
            break;
        }
        default:
            ALOGW("Unknown notify type: %d", msg->type);
            break;
    }
}

避坑指南:千万不要在 notify() 里做耗时操作。我曾经见过有人在错误回调里写日志文件,结果导致 I/O 阻塞,整个 Camera pipeline 都卡住了。notify() 应该只做转发,最多加个简单的日志打印。

好了,这一节就到这里。下一节我们会讲 process_capture_result()notify() 的协同工作——这两个回调怎么配合才能保证 Camera 流畅运行。