第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的实现细节,聊聊怎么处理那些复杂的拍照参数。到时候见。