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 三者的协作关系
这三个结构体不是孤立的。它们在实际运行中是这样配合的:
- Framework 调用
open()拿到camera3_device_t,然后调用initialize()传入camera3_callback_ops_t。 - HAL 保存回调指针,并在
configure_streams()中接收camera3_stream_t数组。 - 每次拍照或预览,Framework 通过
ops->process_capture_request()提交请求,HAL 处理完后通过回调返回结果。 - 整个过程中,
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 里的各个操作函数,看看每个函数在真实项目中是怎么实现的。到时候我会拿我踩过的坑当反面教材,保证让你印象深刻。