3、HAL3 核心接口详解:camera3_device_ops、camera3_callback_ops、关键数据结构
好,咱们直接进入正题。HAL3 的接口设计,说白了就是一套「协议」。你实现了这套协议,Framework 就能跟你愉快地玩耍。我当年刚接触 HAL3 时,看着那堆结构体指针,说实话有点懵。但摸清楚套路后,发现其实就那么几个核心角色。
3.1 camera3_device_ops:你的「能力清单」
这个结构体,你可以把它理解成一份「能力清单」。Framework 通过它来调用你 HAL 层的各种功能。每个函数指针都是一个「承诺」——你承诺能做什么,Framework 就按这个来调用你。
核心函数一览:
| 函数指针 | 作用 | 我的经验 |
|---|---|---|
| initialize() | 初始化设备,传入 callback 句柄 | 这里最容易踩坑的是 callback 的线程安全 |
| configure_streams() | 配置数据流,决定输出格式和尺寸 | 我曾经因为 stream 配置顺序不对,导致预览黑屏 |
| process_capture_request() | 处理一次拍照请求 | 这是最核心的函数,性能瓶颈常在这里 |
| flush() | 清空所有待处理的请求 | 嗯,这个函数实现不好,容易死锁 |
| dump() | 输出调试信息 | 平时不起眼,出问题时它就是救命稻草 |
你想想看,Framework 调用 configure_streams() 时,它会传进来一个 camera3_stream_configuration_t。这里面包含了所有 stream 的列表。我个人的习惯是,在这里做一次「全量校验」——检查每个 stream 的格式、尺寸、usage 标志是否合法。别偷懒,否则后面跑起来再报错,定位成本高得多。
// 一个典型的 configure_streams 实现片段
int my_configure_streams(const struct camera3_device *device,
camera3_stream_configuration_t *stream_list) {
// 我个人习惯先清空旧配置
clear_previous_streams();
for (size_t i = 0; i < stream_list->num_streams; i++) {
camera3_stream_t *stream = stream_list->streams[i];
// 检查格式是否支持
if (!is_format_supported(stream->format)) {
ALOGE("不支持的格式: %d", stream->format);
return -EINVAL;
}
// 注意:stream->usage 标志位很关键
// 我曾经遇到过 GRALLOC_USAGE_HW_VIDEO_ENCODER 没设置
// 导致编码器拿不到数据
stream->priv = allocate_internal_stream(stream);
}
return 0;
}
避坑指南:我曾经在 configure_streams() 里直接修改了 stream 的 dimensions,想着「优化一下」。结果 Framework 那边拿到的尺寸跟我改的不一致,直接 crash。记住:不要修改 stream 的公共字段,除非你明确知道后果。
3.2 camera3_callback_ops:你的「汇报通道」
这个结构体是 Framework 给你的「汇报通道」。你 HAL 层处理完数据,得通过它告诉 Framework:「嘿,结果出来了!」。说白了,它是从 HAL 到 Framework 的「反向通道」。
核心就三个回调:
- process_capture_result():返回拍照结果,包括 metadata 和 buffer
- notify():发送异步事件,比如快门事件、错误事件
- (Android 11+)signal_stream_flush():通知 Framework 某个 stream 正在 flush
这里有个细节很多人会忽略:回调函数的线程安全性。Framework 不保证这些回调在哪个线程调用,但你 HAL 层调用它们时,必须保证线程安全。我见过一个案例,同事在多个线程同时调用 process_capture_result(),结果 Framework 那边 metadata 错乱了。
我的建议:在 HAL 内部维护一个「结果队列」,所有回调都通过一个专门的线程来投递。这样既保证了顺序,又避免了并发问题。嗯,这个方案我用了好几年,没出过问题。
3.3 关键数据结构:看懂它们,你就懂了 HAL3
HAL3 的数据结构设计得很精巧。我挑几个最核心的说说。
3.3.1 camera3_stream_t
这个结构体描述了一条数据流。它包含:
stream_type:CAMERA3_STREAM_OUTPUT(输出)或 CAMERA3_STREAM_INPUT(输入)width/height:图像尺寸format:像素格式,比如 HAL_PIXEL_FORMAT_YCbCr_420_888usage:Gralloc 使用标志,告诉图形系统怎么用这块 buffermax_buffers:Framework 承诺最多同时给多少个 bufferpriv:HAL 私有指针,你可以挂任何你想挂的东西
你想想看,max_buffers 这个字段很关键。Framework 说「我最多给你 4 个 buffer」,你 HAL 就得按这个来规划内部 buffer 池。我曾经遇到一个 case,Framework 给了 4 个,我内部却分配了 6 个,结果内存泄漏了。
3.3.2 camera3_capture_request_t
每次拍照请求,Framework 都会发一个这个结构体给你。它包含:
frame_number:帧号,唯一标识一次请求settings:本次请求的 metadata,比如 AE/AWB 参数input_buffer:输入 buffer(用于 reprocessing 场景)num_output_buffers / output_buffers:输出 buffer 列表
// 处理请求时的典型流程
void process_capture_request(const camera3_capture_request_t *request) {
// 1. 解析 settings
camera_metadata_ro_entry_t entry;
find_camera_metadata_ro_entry(request->settings,
ANDROID_SENSOR_EXPOSURE_TIME, &entry);
int64_t exposure_time = entry.data.i64[0];
// 2. 处理每个 output buffer
for (size_t i = 0; i < request->num_output_buffers; i++) {
const camera3_stream_buffer_t &buf = request->output_buffers[i];
// buf.stream 指向对应的 camera3_stream_t
// buf.buffer 是具体的 gralloc buffer handle
// buf.status 初始为 CAMERA3_BUFFER_STATUS_OK
// 3. 填充数据...
fill_buffer(buf.buffer, exposure_time);
}
// 4. 返回结果(通过回调)
camera3_capture_result_t result;
result.frame_number = request->frame_number;
result.result = build_result_metadata(request->settings);
result.num_output_buffers = request->num_output_buffers;
result.output_buffers = request->output_buffers;
// ... 填充其他字段
g_callback_ops->process_capture_result(g_callback_ops, &result);
}
关键点:每个请求的 frame_number 必须唯一且递增。Framework 靠它来匹配请求和结果。我见过有人重复使用 frame_number,结果 Framework 那边直接报错「duplicate frame number」。
3.3.3 camera3_capture_result_t
这是你返回给 Framework 的结果结构体。注意几个容易出错的地方:
partial_result:如果你分多次返回结果,这个字段标识是第几次。Framework 会等所有 partial result 都到齐了才合并input_buffer:如果请求有 input buffer,这里要返回它,并标记状态physical_camera_settings:多摄场景下,每个物理相机的 settings
我个人习惯是,每次返回结果时,先检查 partial_result 是否设置正确。Framework 对 partial result 的期待很严格——你设了 3 次,它就必须收到 3 次,少一次都不行。
3.4 接口调用流程:一张图说清楚
虽然不能画图,但我用文字描述一下典型流程:
- Framework 调用
open()打开 camera 设备 - Framework 调用
initialize(),传入camera3_callback_ops - Framework 调用
configure_streams(),配置预览/拍照/录像的 stream - Framework 调用
process_capture_request()发送拍照请求 - HAL 处理请求,通过
notify()发送快门事件 - HAL 处理完数据,通过
process_capture_result()返回结果 - 重复步骤 4-6,直到关闭
- Framework 调用
flush()清空所有待处理请求 - Framework 调用
close()关闭设备
这个流程看起来简单,但每个步骤都有坑。比如步骤 5 和 6 的顺序——必须先 notify 再返回 result。Framework 期待先收到快门事件,再收到数据。我曾经搞反了顺序,结果 Framework 那边认为数据是「无快门」的,直接丢弃了。
调试小技巧:在 HAL 里加一个「请求生命周期日志」。每次收到请求、处理完成、返回结果时都打一条 log。这样出问题时,你只要看 log 就能定位是哪个环节卡住了。嗯,这个习惯帮我省了不少时间。
好了,HAL3 的核心接口就这些。说白了就是:你实现 ops,Framework 给你回调,你们通过数据结构来交换数据。理解了这个模型,剩下的就是细节问题了。下一章我们聊聊 metadata 系统,那个才是真正让人头疼的东西。