Camera HAL 核心数据结构与接口

做 Camera HAL 开发,说白了就是跟几个关键结构体打交道。我刚开始接触瑞芯微平台时,看着 camera_module_t、camera_device_t、camera3_device_ops_t 这些名字,说实话有点懵。但摸清楚它们之间的关系后,整个 HAL 的骨架就清晰了。

今天咱们就掰开揉碎,把这三个核心结构体讲透。你想想看,它们就像三层楼——底层是模块,中间是设备,顶层是操作接口。每一层各司其职,又紧密配合。

一、camera_module_t:HAL 的入口大门

camera_module_t 是 Camera HAL 的起点。Android 系统加载 HAL 库时,第一个找的就是它。我习惯把它比作「前台接待处」——你进门先看到它,它告诉你这里有哪些摄像头可用。

这个结构体定义在 hardware/libhardware/include/hardware/camera_common.h 中。核心成员如下:

成员 类型 说明
common hw_module_t 标准 HAL 模块头,包含模块 ID、版本号等
get_number_of_cameras 函数指针 返回系统支持的摄像头数量
get_camera_info 函数指针 获取指定摄像头的静态信息(如朝向、支持的分辨率)

嗯,这里要注意:get_camera_info 返回的是 camera_info 结构体,里面包含了 device_version 字段。这个字段决定了后续用 HAL1 还是 HAL3 的接口。我在项目中就遇到过,有人把版本号写错了,结果 HAL3 的设备被当成了 HAL1 来初始化,折腾了两天才定位到问题。

二、camera_device_t:具体的摄像头实例

当系统调用 camera_module_t->common.methods->open() 时,就会得到一个 camera_device_t。它代表一个具体的摄像头硬件实例。

说白了,camera_module_t 是「摄像头列表」,camera_device_t 是「列表中的某一个摄像头」。我经常跟团队说:模块是静态的,设备是动态的——模块只负责查询,设备才真正干活。

camera_device_t 的核心成员:

  • common:标准设备头,包含打开/关闭等基础操作
  • ops:指向 camera_device_ops_t 的指针,这是 HAL1 时代的操作接口
  • priv:私有数据指针,驱动开发者可以挂载自己的上下文结构体

我个人习惯在 priv 里放一个自定义的结构体,把 sensor 驱动句柄、ISP 配置、流状态等信息都塞进去。这样代码清爽,也方便调试。

三、camera3_device_ops_t:HAL3 的操作核心

到了 HAL3 时代,操作接口从 camera_device_ops_t 升级成了 camera3_device_ops_t。这个结构体才是真正干活的——它定义了所有摄像头操作的回调函数。

我画了一张图,帮你理清它们之间的调用关系:

camera_module_t get_number_of_cameras() → 返回摄像头数量 get_camera_info() → 填充 camera_info 结构体 open() 调用 camera_device_t common:标准设备头(含 close 函数) priv:私有数据指针(驱动上下文) ops 指针指向 camera3_device_ops_t initialize() → configure_streams() → process_capture_request() flush() → dump() → ... 调用链方向

从图中你能看到清晰的调用链:

  1. 系统先通过 camera_module_t 枚举摄像头
  2. 调用 open() 得到 camera_device_t
  3. 通过 camera_device_t 的 ops 字段,找到 camera3_device_ops_t
  4. 最终所有拍照、预览、配置操作都通过 camera3_device_ops_t 中的函数指针完成

四、关键函数指针详解

camera3_device_ops_t 里函数不少,但真正核心的就几个。我挑最重要的说:

initialize():HAL3 的初始化入口。系统会传入 camera3_callback_ops_t,HAL 通过这个回调把数据(如拍照完成的帧)送回给 Framework。

我曾经踩过一个坑:在 initialize() 里忘记保存回调指针,结果后面拍照完成时,数据送不回去,画面一直卡住。排查了半天才发现是回调没注册上。

configure_streams():配置数据流。系统告诉 HAL:「我要一个 1080p 的预览流和一个 4K 的拍照流」。HAL 需要根据硬件能力,决定是否支持、如何分配 buffer。

我建议你在实现这个函数时,先检查硬件是否支持请求的格式和分辨率。如果不支持,尽早返回错误,别等到 process_capture_request 才报错。

process_capture_request():这是最核心的函数,每次拍照或预览帧都会调用它。系统传入一个 camera3_capture_request_t,HAL 需要处理并返回结果。

注意:这个函数可能被多线程并发调用。我见过有人没加锁,结果两个线程同时修改同一个 buffer 队列,导致花屏。嗯,锁一定要加,但别加得太粗,否则性能会掉。

五、它们之间的调用关系

咱们用一段伪代码来展示完整的调用流程:

// 1. 系统加载 HAL 库,获取 camera_module_t
camera_module_t *module = get_camera_module();

// 2. 查询摄像头数量
int num = module->get_number_of_cameras();

// 3. 获取第 0 个摄像头的信息
camera_info info;
module->get_camera_info(0, &info);

// 4. 打开摄像头,得到 camera_device_t
camera_device_t *device;
module->common.methods->open(module, "0", (hw_device_t**)&device);

// 5. 获取 HAL3 操作接口
camera3_device_ops_t *ops = (camera3_device_ops_t*)device->ops;

// 6. 初始化
ops->initialize(device, callback_ops);

// 7. 配置流
camera3_stream_configuration_t config;
ops->configure_streams(device, &config);

// 8. 处理请求
camera3_capture_request_t request;
ops->process_capture_request(device, &request);

// 9. 关闭设备
device->common.close((hw_device_t*)device);

这段代码虽然简化了,但核心流程就是如此。你想想看,从模块到设备再到操作接口,每一层都是上一层的「具体化」——模块是抽象的集合,设备是具体的实例,操作接口是实例的行为。

六、实际项目中的注意事项

最后分享几个我在瑞芯微平台上的实战经验:

  • 版本匹配:camera_module_t 的 common.module_api_version 必须和 camera_device_t 的 common.device_version 对应。HAL3 要求 device_version >= HARDWARE_DEVICE_API_VERSION(3, 0)。
  • 内存管理:camera3_stream_buffer_t 中的 buffer 句柄,记得在 process_capture_request 返回前释放引用。我见过内存泄漏,就是因为忘记调用 release_buffer。
  • 调试技巧:在 get_camera_info 里加日志,打印出返回的 device_version。这样能快速确认 HAL 层是否正确识别了版本。

好了,这一章的内容就到这。核心就是记住那三个结构体,以及它们之间的调用链。下一章咱们会深入 configure_streams 的细节,看看流配置里有哪些坑等着我们。


可可精品资料库