3. HAL3 核心接口解析:camera3_device_t、camera3_callback_ops_t、camera3_stream_t 结构体详解

好,咱们直接进入正题。HAL3 的接口设计,说白了就是三个核心结构体撑起的一片天。你如果把这仨结构体搞明白了,整个 Camera HAL3 的骨架也就摸透了。我个人习惯把这三个结构体比作「三驾马车」——camera3_device_t 是车夫,camera3_callback_ops_t 是传令兵,camera3_stream_t 是货物。今天我就带你挨个拆解一遍。

3.1 camera3_device_t:HAL 设备的「身份证」

先看第一个。这个结构体,是 Framework 和 HAL 之间沟通的桥梁。每个 Camera 硬件设备,在 HAL 层都对应一个 camera3_device_t 实例。

typedef struct camera3_device {
    hw_device_t common;
    camera3_device_ops_t *ops;
    void *priv;
} camera3_device_t;

嗯,这里要注意。它其实就三个字段:

  • common:继承自 hw_device_t,这是 Android HAL 的通用头。里面包含了设备打开、关闭等基础操作。说白了,这是 Android 硬件抽象层的「户口本」。
  • ops:指向 camera3_device_ops_t 的函数指针表。这是真正的「干活」的地方。所有操作——初始化、配置流、提交请求——都通过这里。
  • priv:私有数据指针。我一般在这里挂载我自己定义的设备上下文结构体。比如传感器信息、ISP 状态、调试日志句柄等。

核心要点camera3_device_t 是 Framework 拿到 HAL 设备后的第一个接触点。你实现的 HAL 库,必须通过 open() 函数正确填充这个结构体,否则上层连初始化都过不去。

我在项目中遇到过一个问题:某个厂商的 HAL 实现里,priv 指针指向的内存被意外释放了,结果上层调用 ops 里的函数时直接段错误。排查了整整两天才发现是生命周期管理没做好。所以,priv 的生命周期一定要和 camera3_device_t 绑定。

3.2 camera3_callback_ops_t:从 HAL 到 Framework 的「反向通道」

第二个结构体,是回调接口。你想想看,HAL 不能主动调用 Framework 的函数,那拍完的照片、捕获的元数据怎么通知上层?答案就是回调。

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

这个结构体里最重要的两个回调:

  • process_capture_result:当一帧图像处理完成(包括 JPEG、YUV、RAW 等),HAL 通过这个回调把结果送回 Framework。注意,这里传递的是 camera3_capture_result_t,里面包含了 buffer、metadata、partial result 等信息。
  • notify:用于发送异步事件通知。比如快门事件、错误事件、缓冲区填满等。我建议你把这个回调当作「紧急通道」——只传关键事件,别什么都往里塞。

个人经验:我曾经在调试一个预览卡顿的问题时,发现 HAL 在每一帧都调用了 notify 发送一个无关紧要的状态事件。Framework 那边处理不过来,导致回调队列积压。后来我把非关键事件过滤掉,预览就流畅了。所以,回调不是越多越好,要克制。

这个结构体是由 Framework 在 initialize() 时传给 HAL 的。HAL 需要保存这个指针,后续所有结果上报都靠它。嗯,这里要特别提醒:回调函数必须线程安全。因为 Framework 可能在任何线程调用你的 ops,而你的 HAL 也可能在任意线程调用回调。我曾经见过一个死锁案例,就是 HAL 在回调里又去调用了 Framework 的某个同步接口,结果两边互相等,直接卡死。

3.3 camera3_stream_t:数据流的「管道」

第三个结构体,描述的是数据流。一个 Camera 会话可以包含多个流——比如预览流、拍照流、视频流。每个流都由 camera3_stream_t 定义。

typedef struct camera3_stream {
    int stream_type;        // CAMERA3_STREAM_OUTPUT, INPUT, BIDIRECTIONAL
    uint32_t width;
    uint32_t height;
    int format;             // HAL_PIXEL_FORMAT_*
    uint32_t usage;         // GRALLOC_USAGE_*
    uint32_t max_buffers;
    const camera3_stream_buffer_t *buffers;
    void *priv;
    // ... 其他字段
} camera3_stream_t;

关键字段解析:

字段 说明 我的建议
stream_type 流方向:输出(给上层)、输入(来自上层)、双向(如 ZSL) 大部分场景用 OUTPUT,INPUT 用于 reprocessing
width/height 图像分辨率 必须和 sensor 输出能力匹配,否则会报错
format 像素格式:YUV、NV12、NV21、RAW10、BLOB(JPEG)等 注意不同平台对格式的支持差异很大
usage Gralloc 使用标志,决定 buffer 分配策略 比如 GRALLOC_USAGE_HW_TEXTURE 用于 GPU 渲染
max_buffers HAL 请求的最大 buffer 数量 别设太大,否则内存吃紧;也别太小,否则丢帧
priv HAL 私有数据,Framework 不碰 我常用来挂流级别的上下文,比如 DMA 通道句柄

避坑指南:我曾经在配置流时,把 format 设成了 HAL_PIXEL_FORMAT_RAW10,但 sensor 实际输出的是 RAW12。结果 Framework 那边拿到 buffer 后解析出来的图像全是花的。调试了三天才发现是格式不匹配。所以,format 必须和 sensor 硬件能力严格对应,别想当然。

还有一个容易被忽略的点:usage 字段。它决定了 Gralloc 如何分配 buffer 内存。比如你希望 buffer 能被 GPU 直接纹理化,就要加上 GRALLOC_USAGE_HW_TEXTURE。如果希望 CPU 能直接读写,就加 GRALLOC_USAGE_SW_READ_OFTEN。我建议你在配置流时,和 Gralloc 模块确认一下当前平台支持的 usage 组合,否则分配 buffer 时会失败。

3.4 三者的协作关系

这三个结构体不是孤立的。它们在实际运行中是这样配合的:

  1. Framework 调用 open() 拿到 camera3_device_t,然后调用 initialize() 传入 camera3_callback_ops_t
  2. HAL 保存回调指针,并在 configure_streams() 中接收 camera3_stream_t 数组。
  3. 每次拍照或预览,Framework 通过 ops->process_capture_request() 提交请求,HAL 处理完后通过回调返回结果。
  4. 整个过程中,camera3_stream_t 的 buffer 在 HAL 和 Framework 之间流转,通过 camera3_stream_buffer_t 描述状态。

你想想看,这个设计其实很巧妙。Framework 不关心 HAL 内部怎么处理图像,HAL 也不关心 Framework 怎么展示。双方只通过这三个结构体定义的契约来交互。说白了,这就是一个「接口隔离」的经典案例。

总结一句话camera3_device_t 是设备句柄,camera3_callback_ops_t 是反向通知通道,camera3_stream_t 是数据管道。搞懂这三者,HAL3 的架构你就掌握了 60%。剩下的 40% 是具体的操作函数和 buffer 管理,我们后面章节再细聊。

好,这一章就到这里。下一章我会带你深入 camera3_device_ops_t 里的各个操作函数,看看每个函数在真实项目中是怎么实现的。到时候我会拿我踩过的坑当反面教材,保证让你印象深刻。