第2章 HAL接口定义详解:ICameraDevice.hal、ICameraProvider.hal、ICameraDeviceSession.hal接口分析
好,咱们直接进入正题。这一章要聊的,是HAL层最核心的三个接口文件。说白了,它们就是相机HAL驱动的“骨架”。你写的所有代码,最终都要跟这三个接口打交道。
我记得刚入行那会儿,第一次看到这些.hal文件,头都大了。一堆方法、回调、数据结构,完全不知道从哪下手。后来做多了才发现,其实每个接口都有它明确的职责分工。咱们一个一个来看。
2.1 ICameraProvider.hal —— 设备发现的“门面”
这个接口是干什么的?简单说,就是让上层知道:你到底支持哪些摄像头设备。
我习惯把它比作酒店的前台。你走进酒店,前台告诉你“我们有标准间、豪华套房、总统套房”。ICameraProvider就是干这个的——它枚举出所有可用的摄像头,比如后置主摄、超广角、前置等等。
来看核心方法:
interface ICameraProvider {
/**
* 返回当前设备支持的摄像头ID列表
*/
getCameraIdList() generates (Status status, string[] cameraIds);
/**
* 根据摄像头ID,获取对应的设备接口
*/
getCameraDevice(string cameraId) generates (Status status, ICameraDevice device);
/**
* 设置回调,用于通知上层设备状态变化
*/
setCallback(ICameraProviderCallback callback) generates (Status status);
}
这里有个坑,我踩过。你看getCameraIdList返回的是字符串数组。有些厂商喜欢把ID写成“0”、“1”这种数字,有些写成“back”、“front”。我个人建议统一用数字,因为后面做日志分析时,数字比字符串好解析得多。
getCameraIdList里返回硬编码的列表。我曾经见过一个项目,开发人员图省事,直接写死了一个数组。结果换了硬件后,摄像头死活枚举不出来。正确的做法是从底层硬件抽象层去读取真实的设备列表。
2.2 ICameraDevice.hal —— 设备的“遥控器”
拿到设备之后,你总得操作它吧?ICameraDevice就是干这个的。它代表一个具体的摄像头设备实例。
你可以把它想象成电视遥控器。你按“开机”,电视就通电;你按“调音量”,声音就变大。ICameraDevice提供的就是这些“操作按钮”。
interface ICameraDevice {
/**
* 获取当前设备的元数据,比如传感器尺寸、支持的帧率等
*/
getCameraCharacteristics() generates (Status status, CameraMetadata characteristics);
/**
* 打开摄像头,准备开始工作
*/
open() generates (Status status, ICameraDeviceSession session);
/**
* 关闭摄像头,释放资源
*/
close() generates (Status status);
/**
* 设置设备级别的回调
*/
setCallback(ICameraDeviceCallback callback) generates (Status status);
}
嗯,这里要注意open()方法。它返回的不是一个状态码,而是一个ICameraDeviceSession对象。这个设计很有意思——它把“设备管理”和“会话操作”分开了。
为什么这么设计?你想想看,一个摄像头设备可以被多个应用同时打开吗?理论上不行。但一个应用可以多次打开同一个设备吗?也不行。所以open()返回一个session,这个session就代表了一次“使用权”。你拿着这个session,才能去配置流、拍照、录视频。
open()时,我建议加一个引用计数。因为有时候上层框架会不小心多次调用open(),如果没有引用计数,第二次调用就会直接崩溃。加个计数,第一次真正打开,后续的调用只增加计数,这样更安全。
2.3 ICameraDeviceSession.hal —— 真正的“干活人”
这个接口才是重头戏。所有的图像数据流、拍照请求、参数配置,都在这里完成。
我常说,ICameraProvider是前台,ICameraDevice是遥控器,那ICameraDeviceSession就是那个真正在后台干活的工人。你按了“拍照”按钮,工人就去执行曝光、对焦、成像这一整套流程。
interface ICameraDeviceSession {
/**
* 配置输出流,比如预览流、拍照流、视频流
*/
configureStreams(StreamConfiguration config) generates (Status status, HalStream[] halStreams);
/**
* 提交一个拍照或预览请求
*/
processCaptureRequest(CaptureRequest request,
CaptureResultCallback callback) generates (Status status);
/**
* 刷新所有待处理的请求
*/
flush() generates (Status status);
/**
* 关闭会话,释放所有资源
*/
close() generates (Status status);
}
这里最核心的就是processCaptureRequest。每次上层应用按快门,都会调用这个方法。它会传进来一个CaptureRequest,里面包含了所有参数——曝光时间、ISO、白平衡、对焦区域等等。
你作为HAL开发者,要做的就是解析这个request,然后驱动底层硬件去干活。干完活之后,通过CaptureResultCallback把结果返回给上层。
processCaptureRequest是异步的。你调用它之后,要立刻返回Status,表示“我收到请求了”。真正的处理结果,通过回调来通知。千万别在processCaptureRequest里做同步等待,否则整个相机框架都会卡死。
我记得有一次,客户反馈说相机预览卡顿。我查了半天,发现就是有人在processCaptureRequest里加了一个耗时的图像处理操作。嗯,从那以后,我每次code review都会特别留意这个函数的实现。
2.4 三个接口的关系总结
咱们用一张表来理清它们的关系:
| 接口 | 职责 | 生命周期 | 典型方法 |
|---|---|---|---|
| ICameraProvider | 设备发现与枚举 | 进程级别,常驻 | getCameraIdList, getCameraDevice |
| ICameraDevice | 设备管理与控制 | 设备打开到关闭 | open, close, getCameraCharacteristics |
| ICameraDeviceSession | 数据流处理与请求执行 | 一次open到close | configureStreams, processCaptureRequest |
说白了,调用流程就是:先用Provider找到设备,再用Device打开设备拿到Session,最后用Session干活。干完活,按相反顺序关闭。
这个流程我建议你记在心里。因为后面写代码时,所有的逻辑都是围绕这个流程展开的。你只要把这三个接口的职责搞清楚了,HAL开发就成功了一半。
configureStreams时,没有处理好流配置的兼容性。上层要求一个1080p的预览流和一个4K的拍照流,但我的硬件只支持到1080p。结果configureStreams返回了失败,上层直接崩溃。后来我学会了——在configureStreams里,一定要检查所有流的组合是否合法,如果不合法,要返回具体的错误码,而不是直接抛异常。
好了,这一章就到这里。下一章咱们会深入processCaptureRequest的实现细节,聊聊怎么处理那些复杂的拍照参数。到时候见。