4、Camera HAL层初始化:HAL Module加载流程、CameraProvider启动、CameraDeviceSession创建
好,咱们今天聊聊Camera HAL层初始化的核心流程。这部分内容,说白了就是回答一个问题:系统怎么找到你的Sensor,并且让它开始干活?
我刚开始接触HAL层时,觉得这玩意儿就是个黑盒子。后来踩了不少坑,才慢慢理清楚。今天我把整个流程拆成三个阶段来讲:HAL Module加载、CameraProvider启动、CameraDeviceSession创建。每个阶段我都会结合自己的项目经验,给你讲讲那些容易翻车的地方。
4.1 HAL Module加载流程
先说说HAL Module是什么。你可以把它理解成一个插件管理器。系统启动时,会去加载这个模块,然后从里面找到所有可用的Camera设备。
在Android系统中,HAL Module的入口函数是HAL_MODULE_INFO_SYM。这个结构体定义在camera_common.h里。我贴一段典型的代码,你感受一下:
// 这是HAL Module的核心结构体
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = CAMERA_MODULE_API_VERSION_2_4,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = CAMERA_HARDWARE_MODULE_ID,
.name = "My Camera HAL Module",
.author = "Your Company",
.methods = &hal_module_methods,
};
这里有个关键点——hal_module_methods。它里面定义了open函数指针。系统就是通过这个open来打开具体的Camera设备的。
核心要点:HAL Module加载时,系统会调用hw_get_module()函数。这个函数会根据id字段(这里是CAMERA_HARDWARE_MODULE_ID)去查找对应的.so文件。查找路径通常是/vendor/lib/hw/或/system/lib/hw/。
我记得有一次,我编译完HAL后,发现系统死活加载不到我的模块。查了半天,原来是.so文件名没按规范命名。Android要求文件名必须是camera.<variant>.so这种格式。比如camera.myplatform.so。这个坑,我劝你提前注意。
4.2 CameraProvider启动
HAL Module加载完成后,接下来就是CameraProvider的启动。这部分是Android Treble架构引入的,目的是把HAL层和Framework层解耦。
CameraProvider运行在一个独立的进程中,通过HIDL接口与Framework通信。启动流程大致如下:
- 注册服务:CameraProvider启动后,会向
hwservicemanager注册自己。 - 枚举设备:Framework通过HIDL调用
getCameraIdList(),获取所有可用的Camera ID。 - 获取信息:Framework再调用
getCameraCharacteristics(),获取每个Camera的静态信息。
我画个简单的流程图,帮你理解:
Framework (Java层)
↓ HIDL调用
CameraProvider (Native进程)
↓ 调用HAL Module
HAL Module (你的代码)
↓ 返回设备信息
CameraProvider
↓ 返回给Framework
Framework
个人经验:在实现getCameraIdList()时,一定要确保返回的ID是连续的。我曾经因为ID不连续,导致上层应用在切换摄像头时崩溃。嗯,这个坑我印象很深。
另外,CameraProvider启动时,会检查HAL Module的版本。如果你的HAL版本和Framework期望的不一致,Provider会直接拒绝启动。所以,module_api_version这个字段一定要填对。
4.3 CameraDeviceSession创建
最后一步,也是最关键的一步——创建CameraDeviceSession。这个Session,说白了就是你和Sensor之间的一个会话通道。所有拍照、预览的操作,都要通过这个Session来完成。
创建Session的流程,我拆成几步来讲:
4.3.1 打开设备
Framework调用open()函数,传入Camera ID。HAL层需要返回一个camera3_device_t结构体。这个结构体里,最重要的就是ops字段,它定义了所有操作的回调函数。
// camera3_device_t 结构体
typedef struct camera3_device {
hw_device_t common;
camera3_device_ops_t *ops;
void *priv;
} camera3_device_t;
这里的ops包含了initialize()、configure_streams()、process_capture_request()等关键函数。你想想看,这些函数就是你和Sensor交互的接口。
4.3.2 初始化Session
initialize()函数被调用时,HAL层需要做两件事:
- 分配资源:比如分配内存池、创建工作线程。
- 注册回调:把
camera3_callback_ops_t结构体保存下来。这个回调用于向Framework通知事件,比如拍照完成、错误发生等。
注意:在initialize()中,千万不要做耗时操作。比如初始化Sensor、加载固件这些,应该放到后面configure_streams()或第一次process_capture_request()时再做。否则,Framework可能会因为超时而杀掉你的Provider进程。
4.3.3 配置数据流
configure_streams()是Session创建中最复杂的一步。Framework会告诉HAL层,它需要哪些数据流(比如预览流、拍照流)。HAL层需要根据这些要求,配置Sensor的输出格式、分辨率、帧率等。
我举个例子,假设Framework要求两个流:
| Stream ID | 用途 | 分辨率 | 格式 |
|---|---|---|---|
| 0 | 预览 | 1920x1080 | NV12 |
| 1 | 拍照 | 4000x3000 | JPEG |
HAL层需要判断,Sensor能否同时输出这两个流。如果不能,就需要做Stream Split或Downscale。我个人的习惯是,在configure_streams()中先做一次可行性检查,如果不行,直接返回错误码-EINVAL。
4.3.4 开始捕获
Session创建完成后,Framework会通过process_capture_request()发送捕获请求。HAL层收到请求后,需要:
- 解析请求:提取出需要的参数,比如曝光时间、增益、对焦位置等。
- 配置Sensor:通过I2C/SPI接口,把这些参数写入Sensor寄存器。
- 触发捕获:发送软件或硬件触发信号,让Sensor开始曝光。
- 返回数据:曝光完成后,把图像数据通过回调函数返回给Framework。
避坑指南:我曾经在process_capture_request()中直接操作Sensor寄存器,结果导致UI卡顿。后来发现,千万不要在回调线程中做耗时操作。正确的做法是,把请求放入队列,由专门的工作线程去处理。这样既保证了响应速度,又避免了阻塞。
4.4 总结与经验分享
好了,整个HAL层初始化的流程,我带你走了一遍。从Module加载,到Provider启动,再到Session创建,每一步都有它的设计意图。
最后,我分享几个实战中的小技巧:
- 日志要打全:在关键函数入口和出口都加上
ALOGD。调试时,看日志就能定位问题。 - 超时处理:Framework对HAL的调用是有超时限制的。比如
configure_streams(),如果超过5秒没返回,Provider会被杀掉。所以,耗时操作一定要异步处理。 - 版本兼容:如果你的HAL要适配多个Android版本,记得检查
module_api_version和hal_api_version。不同版本对HAL的要求不一样。
嗯,今天就先聊到这儿。下一章,我会讲讲Sensor上电与初始化时序,那是另一个容易踩坑的地方。咱们到时候见。