第2章:Camera HAL层详解:HAL3架构、HAL接口定义、Camera HAL模块加载流程
好,我们接着往下聊。上一章我们把Camera的整体框架捋了一遍,从APP到Framework,再到HAL,最后到Kernel。这一章,咱们就扎进HAL层,好好看看这个承上启下的关键环节。
我个人习惯把HAL层比作一个「翻译官」。Framework那边说的是Android标准语言,而硬件厂商那边说的是自家芯片的方言。HAL的工作,就是把Framework的指令准确无误地翻译给硬件听,再把硬件采集到的数据包装好送回给Framework。翻译官当得好不好,直接决定了相机体验的优劣。
2.1 HAL3架构:从HAL1到HAL3的进化
在讲HAL3之前,得先提一嘴它的前辈——HAL1。我记得刚入行那会儿,Android 4.4、5.0时代,用的都是HAL1。那个架构怎么说呢,简单粗暴。它把拍照和预览当成两个独立的任务来处理。你按下快门,它就去拍一张照片;你打开预览,它就给你送一帧一帧的数据。
但问题来了。如果你在预览的同时想拍照,或者想录制视频的同时拍一张高分辨率的照片,HAL1就有点力不从心了。它内部的状态机切换很麻烦,经常出现「我在拍照,预览就卡住了」这种尴尬情况。
HAL3的出现,说白了就是为了解决这个痛点。它引入了一个核心概念——Camera3Stream。你可以把它理解成一条「数据管道」。Framework可以同时打开多条管道,比如一条给预览,一条给拍照,一条给视频录制。这些管道互不干扰,并行工作。
为什么会这样?因为HAL3把相机设备抽象成了一个状态机,并且引入了Request(请求)和Result(结果)的模型。Framework每次下发一个拍照或预览的请求,HAL3就根据这个请求去配置硬件、采集数据、返回结果。每个请求都是独立的,所以可以并发处理。
HAL3的核心优势:
- 并行流处理:预览、拍照、录像可以同时进行,互不阻塞。
- 逐帧控制:每一帧的曝光、增益、白平衡等参数都可以独立设置。
- 低延迟:通过Pipeline(流水线)机制,减少了帧与帧之间的等待时间。
- 灵活性高:支持YUV、RAW、JPEG等多种输出格式,以及不同分辨率的组合。
嗯,这里要注意一点。HAL3虽然强大,但对硬件的要求也更高了。它需要ISP(图像信号处理器)支持Pipeline模式,能够处理多个并发的请求。如果硬件比较老旧,强行上HAL3反而可能出问题。我在项目中就遇到过,某款低端芯片的ISP只能同时处理一个流,结果一开HAL3就各种超时、丢帧。最后没办法,只能降级回HAL1。
2.2 HAL接口定义:Framework与HAL的契约
HAL3的接口定义,其实就藏在Android源码的hardware/libhardware/include/hardware/camera3.h这个文件里。这个文件,可以说是Framework和HAL之间的「合同」。双方都得按照这个合同来办事。
我们先看几个核心的结构体:
// camera3_device_t:代表一个物理相机设备
typedef struct camera3_device {
hw_device_t common;
camera3_device_ops_t *ops;
void *priv;
} camera3_device_t;
// camera3_device_ops_t:设备操作函数表
typedef struct camera3_device_ops {
int (*initialize)(const struct camera3_device *, const camera3_callback_ops_t *);
int (*configure_streams)(const struct camera3_device *, camera3_stream_configuration_t *);
int (*register_stream_buffers)(const struct camera3_device *, const camera3_stream_buffer_set_t *);
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 *);
void (*dump)(const struct camera3_device *, int fd);
int (*flush)(const struct camera3_device *);
void (*signal_stream_flush)(const struct camera3_device *, uint32_t num_streams, const camera3_stream_t *const *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;
你想想看,这个ops结构体里,每一个函数指针都是一个「契约条款」。Framework会调用这些函数,HAL必须实现它们。其中最重要的几个:
| 函数名 | 作用 | 我的经验 |
|---|---|---|
initialize |
初始化相机设备,注册回调函数 | 这里要特别注意回调函数的线程安全,我曾经在这里踩过死锁的坑 |
configure_streams |
配置数据流,告诉HAL你要什么格式、什么分辨率的流 | 这是最容易被Framework拒绝的地方,配置不对直接返回-ENOSYS |
process_capture_request |
下发一个捕获请求,HAL需要处理并返回结果 | 这个函数必须是非阻塞的,否则预览会卡成PPT |
flush |
清空所有待处理的请求 | 一般在关闭相机或切换场景时调用,处理不好会导致ANR |
还有一个容易被忽略的结构体——camera3_callback_ops_t。这是HAL用来回调Framework的接口:
typedef struct camera3_callback_ops {
void (*process_capture_result)(const struct camera3_callback_ops *, const camera3_capture_result_t *);
void (*notify)(const struct camera3_callback_ops *, const camera3_notify_msg_t *);
void (*process_capture_result_with_physcam)(const struct camera3_callback_ops *, const camera3_capture_result_t *, const camera3_physcam_metadata_t *);
} camera3_callback_ops_t;
我建议你重点看process_capture_result。HAL处理完一个请求后,必须通过这个回调把结果(包括图像数据、元数据)送回给Framework。如果这个回调没有被正确调用,Framework就会一直等,直到超时。
避坑指南:我曾经在调试一款新平台时,发现拍照后APP一直卡在「正在处理」界面。查了两天,最后发现是HAL在返回结果时,frame_number字段填错了。Framework根据这个字段来匹配请求和结果,对不上号就永远等下去。所以,务必保证每个请求都有且只有一个对应的结果返回。
2.3 Camera HAL模块加载流程
好了,接口定义清楚了,那HAL模块是怎么被加载到系统中的呢?这个过程,其实是从Framework的CameraService开始的。
简单来说,流程是这样的:
- 打开HAL模块:
CameraService调用hw_get_module(),根据硬件ID(比如CAMERA_HARDWARE_MODULE_ID)去查找对应的.so文件。 - 获取模块信息:找到
.so后,通过HAL_MODULE_INFO_SYM这个符号,拿到hw_module_t结构体。这个结构体里包含了模块的版本、作者、以及最重要的open函数指针。 - 打开设备:调用
module->methods->open(),传入设备ID(比如0代表后置摄像头,1代表前置摄像头),得到一个hw_device_t。 - 转换为camera3_device:把
hw_device_t强转为camera3_device_t,然后就可以调用ops里的函数了。
代码层面,核心逻辑在CameraModule.cpp里:
// 伪代码,展示核心逻辑
status_t CameraModule::init() {
const hw_module_t *rawModule = NULL;
// 1. 加载HAL模块
int ret = hw_get_module(CAMERA_HARDWARE_MODULE_ID, &rawModule);
if (ret != 0) {
ALOGE("Failed to load camera HAL module");
return -ENODEV;
}
// 2. 获取模块结构体
mModule = reinterpret_cast<const camera_module_t*>(rawModule);
// 3. 获取摄像头数量
int numCameras = mModule->get_number_of_cameras();
ALOGI("Found %d cameras", numCameras);
return OK;
}
status_t CameraModule::openCamera(int cameraId, struct hw_device_t** device) {
// 4. 打开指定摄像头
return mModule->common.methods->open(
&mModule->common,
String8::format("%d", cameraId).string(),
device);
}
这里有个细节,hw_get_module()是怎么找到.so文件的?它会在系统路径(比如/vendor/lib/hw/或/system/lib/hw/)下,按照命名规则去查找。命名规则是:camera.<variant>.so。比如camera.msm8998.so、camera.exynos9810.so。
小技巧:如果你在调试时发现HAL模块加载失败,可以用adb shell ls /vendor/lib/hw/camera*看看对应的.so文件是否存在。如果文件存在但加载失败,多半是权限问题或者.so的依赖库缺失。用adb shell ldd /vendor/lib/hw/camera.xxx.so可以检查依赖是否完整。
加载完成后,CameraService会调用initialize()来建立与HAL的通信通道。这个过程中,HAL会返回一个camera_metadata_t,里面包含了摄像头的能力信息,比如支持的分辨率列表、帧率范围、AE/AWB模式等。Framework拿到这些信息后,才能知道这个摄像头能做什么,不能做什么。
嗯,整个加载流程,说白了就是「找文件 -> 打开文件 -> 获取函数表 -> 初始化设备」这么几步。但每一步都可能出问题。我建议你在做HAL开发时,先在init()和open()函数里加足量的日志,把关键变量的值打印出来。这样一旦出问题,看日志就能定位到是哪一步卡住了。
好了,这一章的内容就到这里。HAL3的架构、接口定义、加载流程,这三块是理解Camera HAL的基础。下一章,我们会深入configure_streams和process_capture_request这两个核心函数,看看数据到底是怎么在HAL里流转的。