3、Camera HAL 接口详解:HAL3 vs HAL1对比、camera_module_t结构体、camera3_device_t结构体、open/close/initialize接口

好,咱们今天来聊聊 Camera HAL 接口。说实话,HAL 层是整个相机框架里最「硬核」的部分之一。它直接跟硬件打交道,Framework 层再花哨,最终都得通过 HAL 去操作摄像头。

我刚开始接触 Android Camera 时,面对 HAL1 和 HAL3 两套接口,说实话有点懵。为什么搞两套?它们到底差在哪?今天我就把这块掰开揉碎了讲清楚。

3.1 HAL1 vs HAL3:两代接口的恩怨

先说说背景。HAL1 是 Android 4.0 时代推出的,那时候手机拍照功能还比较简单。HAL3 则是 Android 5.0 引入的,目的是为了支持更复杂的拍照场景——比如专业模式、RAW 输出、多帧合成这些。

我个人习惯把 HAL1 叫做「黑盒模式」,把 HAL3 叫做「白盒模式」。为什么这么叫?你想想看:

  • HAL1:Framework 只管下发指令,HAL 层自己控制图像处理流程。你拿到的就是一张处理好的 JPEG,中间过程你管不了。
  • HAL3:Framework 可以精细控制每一帧的 capture 流程,包括曝光、增益、白平衡、对焦等参数。说白了,你把控制权从 HAL 手里「抢」回来了。

我在项目中遇到过不少厂商只实现了 HAL1,结果客户想要个 RAW 输出功能,根本没法做。后来 Android 8.0 开始强制要求 HAL3,情况才好转。

核心区别一句话总结:HAL1 是「你只管按快门,剩下交给我」;HAL3 是「你告诉我每一步怎么走,我来执行」。

来看个对比表格,更直观:

对比项 HAL1 HAL3
控制粒度 粗粒度,一次 capture 一个结果 细粒度,逐帧控制参数
输出格式 通常只有 JPEG 支持 YUV、RAW、JPEG 等多种格式
多帧处理 不支持 支持 burst、ZSL、HDR 等
性能要求 较低 较高,需要更快的 ISP 和 CPU
实现复杂度 简单 复杂,需要实现大量回调

3.2 camera_module_t 结构体:HAL 的「身份证」

不管用 HAL1 还是 HAL3,Framework 要找到摄像头设备,第一步就是通过 camera_module_t。这个结构体定义了 HAL 模块的基本信息,相当于 HAL 的「身份证」。

来看看它的核心字段:

typedef struct camera_module {
    hw_module_t common;
    int (*get_number_of_cameras)(void);
    int (*get_camera_info)(int camera_id, struct camera_info *info);
    int (*set_callbacks)(const camera_module_callbacks_t *callbacks);
    void (*get_vendor_tag_ops)(vendor_tag_ops_t *ops);
    int (*open_legacy)(const struct hw_module_t* module, const char* id,
                       uint32_t halVersion, struct hw_device_t** device);
    int (*set_torch_mode)(const char* camera_id, bool enabled);
    int (*init)(void);
} camera_module_t;

嗯,这里要注意几个关键点:

  • get_number_of_cameras:返回摄像头数量。我见过一些设备返回 0 的情况,那基本是驱动没加载成功。
  • get_camera_info:获取某个摄像头的静态信息,比如朝向、是否支持闪光灯等。
  • set_callbacks:注册回调,Framework 通过这个接口接收 HAL 层的通知,比如摄像头热插拔事件。
  • open_legacy:这个接口很有意思。如果设备只实现了 HAL3,但 App 要求用 HAL1,Framework 会尝试通过这个接口「降级」使用。不过说实话,这个接口实现起来挺坑的,我建议尽量别依赖它。
避坑指南:我曾经在调试某款手机时,发现 get_number_of_cameras 返回了 3,但实际只有 2 个摄像头可用。后来发现是 HAL 层在初始化时,第三个摄像头 probe 失败了,但没有清理计数。所以,一定要确保返回的数量与实际可用数量一致

3.3 camera3_device_t 结构体:HAL3 的核心

如果你用的是 HAL3,那 camera3_device_t 就是你打交道最多的结构体。它代表了单个摄像头设备,所有操作都通过它来完成。

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

结构很简单,就三个字段。但核心在 ops 这个指针上,它指向一个函数表,包含了所有操作接口:

typedef struct camera3_device_ops {
    int (*initialize)(const struct camera3_device *,
                      const camera3_callback_ops_t *callback_ops);
    int (*configure_streams)(const struct camera3_device *,
                             camera3_stream_configuration_t *stream_list);
    int (*register_stream_buffers)(const struct camera3_device *,
                                   const camera3_stream_buffer_set_t *buffer_set);
    const camera_metadata_t* (*construct_default_request_settings)
                            (const struct camera3_device *, int type);
    int (*process_capture_request)(const struct camera3_device *,
                                   camera3_capture_request_t *request);
    void (*dump)(const struct camera3_device *, int fd);
    int (*flush)(const struct camera3_device *);
    void (*signal_stream_flush)(const struct camera3_device*,
                                uint32_t* stream_ids, uint32_t num_streams);
    int (*is_reconfiguration_required)(const struct camera3_device*,
                                       const camera_metadata_t* old_session,
                                       const camera_metadata_t* new_session);
} camera3_device_ops_t;

这里我挑几个重点讲:

  • initialize:初始化设备,注册回调。这个后面单独说。
  • configure_streams:配置数据流。比如你要预览 + 拍照,就需要配置两个 stream。这个接口调用时机很关键,我后面会讲。
  • process_capture_request:提交 capture 请求。这是 HAL3 的核心操作,每一帧的拍摄都通过这个接口触发。
  • flush:清空所有未处理的请求。我在调试时经常用这个接口来「重置」状态。
注意:register_stream_buffers 在 Android 8.0 之后已经废弃了。如果你在写新代码,不要依赖它。Framework 现在通过 configure_streams 直接传递 buffer handles。

3.4 open/close/initialize 接口详解

这三个接口是 HAL 设备生命周期管理的核心。咱们一个一个说。

3.4.1 open 接口

open 是 HAL 模块的入口。Framework 通过 hw_module_t->methods->open 来打开摄像头设备。它的原型是:

int open(const struct hw_module_t* module, const char* id,
         struct hw_device_t** device);

这里 id 就是摄像头 ID,比如 "0"、"1"。Framework 会为每个摄像头调用一次 open

我在项目中遇到过一个问题:某个设备在 open 时做了大量初始化操作,导致打开摄像头需要 2 秒多。用户体验极差。后来我们把耗时的操作移到 initialize 里,open 只做最必要的资源分配,才把时间降到 500ms 以内。

建议:open 接口尽量轻量。只做内存分配、设备句柄获取这类操作。复杂的初始化放到 initialize 里做。

3.4.2 initialize 接口

initialize 是 HAL3 特有的接口。它在 open 之后被调用,用于建立 Framework 和 HAL 之间的通信通道。

int (*initialize)(const struct camera3_device *,
                  const camera3_callback_ops_t *callback_ops);

关键参数 callback_ops 是一个回调函数表,HAL 通过它把结果返回给 Framework。比如:

  • process_capture_result:返回 capture 结果
  • notify:发送错误或事件通知

嗯,这里要注意:initialize 可能会被多次调用。比如摄像头被重置后,Framework 会重新调用 initialize。所以你的实现要能正确处理重复初始化的情况。

3.4.3 close 接口

close 用于释放资源。它的原型很简单:

int close(struct hw_device_t* device);

但实现起来要注意几点:

  • 确保所有未完成的 capture 请求都被取消或完成
  • 释放所有分配的内存和硬件资源
  • 如果摄像头正在 streaming,要先停止

我曾经在调试时发现,close 之后再次 open 会 crash。原因是 close 里释放了某个全局变量,但没有置空。第二次 open 时又去访问它,就挂了。所以,释放后记得置空指针

3.5 知识体系总览

为了让你更直观地理解整个 HAL 接口的层次关系,我画了一张图:

Camera HAL 接口层次结构 Android Framework camera_module_t get_number_of_cameras | get_camera_info | set_callbacks camera3_device_t (HAL3) 或 camera_device_t (HAL1) open | close | initialize | configure_streams | process_capture_request HAL1 实现 黑盒模式 | 一次 capture 一个结果 HAL3 实现 白盒模式 | 逐帧控制 | 多格式输出 open → initialize → configure_streams → process_capture_request → close 生命周期:从打开到关闭,每一步都有明确的职责

从这张图可以清楚看到,Framework 通过 camera_module_t 找到设备,然后通过 camera3_device_t 操作设备。HAL1 和 HAL3 在设备层分叉,但上层的模块接口是统一的。

3.6 小结

今天咱们把 Camera HAL 接口的核心内容过了一遍。从 HAL1 和 HAL3 的对比,到 camera_module_tcamera3_device_t 的结构,再到 open/close/initialize 的具体实现要点。这些东西看起来是死板的结构体定义,但实际开发中,每一个字段、每一个回调都可能成为坑点。

我个人觉得,理解 HAL 接口的关键在于「控制权」三个字。HAL1 把控制权交给厂商,HAL3 把控制权交还给 Framework。你掌握了这个核心思想,再看那些结构体和函数,就会觉得顺理成章了。

好,今天就到这里。下次咱们聊聊 configure_streamsprocess_capture_request 这两个核心接口的细节——它们才是真正干活的地方。


公众号:蓝海资料掘金营,微信deep3321