2、HAL 接口详解:ICameraDevice、ICameraDeviceSession 等核心接口解析。
好,咱们直接进入正题。Camera HAL 层,说白了就是 Android 系统和硬件之间的翻译官。系统说我要拍照,HAL 得告诉 sensor 怎么曝光、怎么读数据。而 ICameraDevice 和 ICameraDeviceSession,就是这份翻译官合同里的核心条款。
我个人习惯,拿到一个新平台的 HAL 代码,第一件事就是看这两个接口。为什么?因为它们定义了你能做什么,以及什么时候能做。搞懂了它们,你基本就抓住了 HAL 的命脉。
2.1 ICameraDevice:设备的大门
ICameraDevice 代表一个物理或逻辑摄像头设备。你可以把它想象成摄像头设备的大门。系统通过它来打开、关闭摄像头,以及创建真正的“工作会话”。
核心方法就那么几个,我列出来给你看:
// 获取摄像头的基本信息,比如支持的分辨率、帧率
getCameraInfo(int cameraId, CameraInfo cameraInfo);
// 打开摄像头,返回一个 ICameraDevice 实例
open(int cameraId, ICameraDeviceCallback callback);
// 创建一个新的会话,用于执行具体的拍照或预览操作
createSession(CaptureSessionConfig config, ICameraDeviceSessionCallback callback);
// 关闭摄像头,释放资源
close();
嗯,这里要注意。很多刚入行的同事会混淆 open() 和 createSession()。我解释一下:open() 是拿到设备控制权,就像你拿到了车钥匙。而 createSession() 是真正启动引擎,准备上路。你可以在 open() 之后多次 createSession(),但必须先 open() 才能 createSession()。
重要概念: ICameraDevice 的生命周期是全局的。一个 App 打开摄像头后,其他 App 在未关闭前是无法打开同一个物理摄像头的。这是 Android 的硬件资源管理策略。
2.2 ICameraDeviceSession:干活的地方
如果说 ICameraDevice 是门,那 ICameraDeviceSession 就是车间。所有具体的拍照、预览、参数配置,都在这个会话里完成。
这个接口的方法就多了,我挑几个最关键的:
// 提交一个捕获请求,比如拍一张照片或开始预览
int submitRequest(CaptureRequest request, bool repeating);
// 取消一个正在进行的捕获请求
int cancelRequest(int requestId);
// 设置或获取摄像头参数,比如曝光时间、ISO
int setParameters(CameraParameters params);
CameraParameters getParameters();
// 处理结果回调,比如拍照完成后的图像数据
void processCaptureResult(CaptureResult result);
// 通知错误,比如硬件故障
void notifyError(int errorCode, int requestId);
你想想看,submitRequest() 是核心中的核心。系统把拍照指令封装成 CaptureRequest 丢给你,你必须在 HAL 层把它翻译成硬件能懂的寄存器配置。我曾经在一个项目里,因为 CaptureRequest 里的一个 metadata 字段没处理好,导致预览画面一直黑屏。排查了整整两天,最后发现是 ANDROID_CONTROL_AE_MODE 设置成了 OFF,但 sensor 的自动曝光没关掉,两边打架了。
调试小技巧: 当遇到预览或拍照异常时,我建议你首先在 processCaptureResult() 里打印出 CaptureResult 的所有 metadata。很多时候,问题出在 HAL 返回的结果和系统预期的不一致。比如你明明设置了 30fps,但返回的帧率是 15fps,那肯定是 HAL 层某个地方配置错了。
2.3 接口之间的协作关系
这两个接口不是孤立的。它们之间的协作,决定了整个 Camera 系统的流畅度。我画个简单的流程给你看:
- App 发起请求:App 调用
CameraManager.openCamera()。 - Framework 调用 HAL:Framework 层调用 HAL 的
ICameraDevice.open()。 - 创建会话:App 调用
createCaptureSession(),Framework 调用 HAL 的ICameraDevice.createSession()。 - 提交请求:App 调用
capture()或setRepeatingRequest(),Framework 调用 HAL 的ICameraDeviceSession.submitRequest()。 - 返回结果:HAL 处理完硬件数据后,通过
ICameraDeviceSession.processCaptureResult()回调给 Framework。 - 关闭会话:App 关闭摄像头,Framework 调用
ICameraDeviceSession.close()和ICameraDevice.close()。
这个流程里,最容易出问题的是第 4 步到第 5 步的衔接。我曾经遇到过一个 case,submitRequest() 返回了成功,但 processCaptureResult() 迟迟不回调。最后发现是 HAL 层在处理请求时,有一个 buffer 队列满了,新的请求被阻塞了。嗯,这种问题在日志里很难看出来,需要你仔细跟踪每个请求的 ID 和状态。
2.4 避坑指南:接口实现中的常见陷阱
做 HAL 开发,有些坑是绕不开的。我把自己踩过的几个分享给你:
- 线程安全:
ICameraDeviceSession的方法可能被多个线程同时调用。比如submitRequest()和cancelRequest()可能来自不同线程。我曾经因为没加锁,导致一个请求被取消后,它的结果还在回调,系统直接崩溃了。 - 资源泄漏:每次
submitRequest()都会分配 buffer。如果cancelRequest()没有正确释放,内存会越涨越高。我建议你在cancelRequest()里显式地清理所有与该请求相关的资源。 - 状态机管理:HAL 内部有一个状态机。比如在
ERROR状态下,不应该再接受新的submitRequest()。我习惯在接口入口处加一个状态检查,如果状态不对,直接返回错误码。 - 超时处理:硬件操作可能超时。比如 sensor 初始化失败。我建议你在
submitRequest()里加一个 watchdog 定时器,如果超过一定时间没有回调,主动上报错误。
警告: 千万不要在 processCaptureResult() 回调里做耗时操作!这个回调是在 HAL 的 worker 线程里执行的,如果你阻塞了它,整个 Camera 管线都会卡住。我曾经见过有人在这个回调里写日志文件,导致预览帧率从 30fps 掉到了 5fps。
2.5 实战:如何快速定位接口问题
说了这么多理论,来点实际的。当你遇到 Camera 打不开或者预览卡死时,怎么快速定位是 HAL 接口的问题?
我的做法是三步走:
- 检查日志:搜索
ICameraDevice和ICameraDeviceSession的关键字。看open()是否返回成功,createSession()是否被调用。 - 加打印:在 HAL 的接口实现里,每个方法的入口和出口都加上日志。打印出传入的参数和返回的结果。比如
submitRequest()的 requestId 和 repeating 标志。 - 模拟测试:写一个简单的测试 App,只调用
open()和createSession(),不提交任何请求。如果这一步都失败,那肯定是 HAL 接口实现有问题。如果成功,再逐步增加submitRequest()的调用。
我记得有一次,客户反馈某个手机上 Camera 打不开。我按照这个步骤,发现 open() 返回了 ERROR_CAMERA_IN_USE。但系统里明明没有其他 App 在用 Camera。最后查出来是 HAL 层的一个全局变量没有初始化,导致状态判断错误。嗯,这种低级错误,往往最隐蔽。
好了,关于 ICameraDevice 和 ICameraDeviceSession 的核心内容,就讲到这里。这两个接口是 HAL 层的基石,你花再多时间去理解它们都不为过。下一章,我会深入讲解 CaptureRequest 和 CaptureResult 的数据结构,以及它们如何影响最终的图像质量。