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_888
  • usage:Gralloc 使用标志,告诉图形系统怎么用这块 buffer
  • max_buffers:Framework 承诺最多同时给多少个 buffer
  • priv: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 接口调用流程:一张图说清楚

虽然不能画图,但我用文字描述一下典型流程:

  1. Framework 调用 open() 打开 camera 设备
  2. Framework 调用 initialize(),传入 camera3_callback_ops
  3. Framework 调用 configure_streams(),配置预览/拍照/录像的 stream
  4. Framework 调用 process_capture_request() 发送拍照请求
  5. HAL 处理请求,通过 notify() 发送快门事件
  6. HAL 处理完数据,通过 process_capture_result() 返回结果
  7. 重复步骤 4-6,直到关闭
  8. Framework 调用 flush() 清空所有待处理请求
  9. Framework 调用 close() 关闭设备

这个流程看起来简单,但每个步骤都有坑。比如步骤 5 和 6 的顺序——必须先 notify 再返回 result。Framework 期待先收到快门事件,再收到数据。我曾经搞反了顺序,结果 Framework 那边认为数据是「无快门」的,直接丢弃了。

调试小技巧:在 HAL 里加一个「请求生命周期日志」。每次收到请求、处理完成、返回结果时都打一条 log。这样出问题时,你只要看 log 就能定位是哪个环节卡住了。嗯,这个习惯帮我省了不少时间。

好了,HAL3 的核心接口就这些。说白了就是:你实现 ops,Framework 给你回调,你们通过数据结构来交换数据。理解了这个模型,剩下的就是细节问题了。下一章我们聊聊 metadata 系统,那个才是真正让人头疼的东西。