2、HAL 接口详解:ICameraDevice、ICameraDeviceSession 等核心接口解析。

好,咱们直接进入正题。Camera HAL 层,说白了就是 Android 系统和硬件之间的翻译官。系统说我要拍照,HAL 得告诉 sensor 怎么曝光、怎么读数据。而 ICameraDeviceICameraDeviceSession,就是这份翻译官合同里的核心条款。

我个人习惯,拿到一个新平台的 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 系统的流畅度。我画个简单的流程给你看:

  1. App 发起请求:App 调用 CameraManager.openCamera()
  2. Framework 调用 HAL:Framework 层调用 HAL 的 ICameraDevice.open()
  3. 创建会话:App 调用 createCaptureSession(),Framework 调用 HAL 的 ICameraDevice.createSession()
  4. 提交请求:App 调用 capture()setRepeatingRequest(),Framework 调用 HAL 的 ICameraDeviceSession.submitRequest()
  5. 返回结果:HAL 处理完硬件数据后,通过 ICameraDeviceSession.processCaptureResult() 回调给 Framework。
  6. 关闭会话: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 接口的问题?

我的做法是三步走:

  1. 检查日志:搜索 ICameraDeviceICameraDeviceSession 的关键字。看 open() 是否返回成功,createSession() 是否被调用。
  2. 加打印:在 HAL 的接口实现里,每个方法的入口和出口都加上日志。打印出传入的参数和返回的结果。比如 submitRequest() 的 requestId 和 repeating 标志。
  3. 模拟测试:写一个简单的测试 App,只调用 open()createSession(),不提交任何请求。如果这一步都失败,那肯定是 HAL 接口实现有问题。如果成功,再逐步增加 submitRequest() 的调用。

我记得有一次,客户反馈某个手机上 Camera 打不开。我按照这个步骤,发现 open() 返回了 ERROR_CAMERA_IN_USE。但系统里明明没有其他 App 在用 Camera。最后查出来是 HAL 层的一个全局变量没有初始化,导致状态判断错误。嗯,这种低级错误,往往最隐蔽。

好了,关于 ICameraDeviceICameraDeviceSession 的核心内容,就讲到这里。这两个接口是 HAL 层的基石,你花再多时间去理解它们都不为过。下一章,我会深入讲解 CaptureRequestCaptureResult 的数据结构,以及它们如何影响最终的图像质量。