4、CameraDevice 创建与配置:openCamera流程、CameraDeviceImpl初始化、SessionConfiguration配置、OutputConfiguration详解

好,咱们今天聊点硬核的。CameraDevice 的创建和配置,说白了就是相机从「休眠」到「就绪」的关键一步。很多开发者觉得 openCamera 就是个回调,但里面门道不少。我当年第一次对接 HAL 层时,就被 openCamera 的异步流程坑过——回调没来,我以为是手机坏了,结果是自己没理解 binder 的线程模型。

4.1 openCamera 流程:从 App 到 HAL 的漫长旅程

你调用 CameraManager.openCamera() 时,系统到底在干嘛?我画了个简图,帮你理清脉络。

openCamera 核心调用链 App 调用 openCamera(cameraId, callback, handler) CameraManager 权限检查 + 服务绑定 CameraService native 层调度 HAL 层 open() 设备驱动 CameraDeviceImpl Java 层代理对象 onOpened() 回调到 App 整个过程是异步的,App 不会阻塞 CameraDeviceImpl 在 onOpened 之后才可用

嗯,这里有个关键点:openCamera 是异步的。你调用后,系统会通过 Binder 跨进程通知 CameraService,CameraService 再往下走到 HAL。HAL 打开驱动成功后,一路回调回来,最终触发你的 onOpened()

核心要点:openCamera 返回的 CameraDevice 不是真正的设备,而是一个代理对象(CameraDeviceImpl)。真正的设备在 HAL 层,通过 Binder 通信。

4.2 CameraDeviceImpl 初始化:代理对象的诞生

CameraDeviceImpl 是什么?说白了,它就是 App 和 HAL 之间的「传话筒」。你调用的所有方法,比如 createCaptureSession(),最终都会通过它转发到 native 层。

初始化时,CameraDeviceImpl 会做几件事:

  • 绑定 CameraService 的代理:持有 ICameraDeviceUser 的 Binder 引用
  • 创建回调处理器:确保所有回调在正确的线程执行
  • 初始化状态机:从 INITIAL 状态开始,等待配置

我记得有一次,我在初始化时忘了设置 handler,结果回调跑到了主线程,导致 UI 卡顿。后来我养成了习惯:永远给 openCamera 传一个后台线程的 handler

我的建议:用 HandlerThread 创建专用线程,避免阻塞主线程。代码示例:

HandlerThread cameraThread = new HandlerThread("CameraThread");
cameraThread.start();
Handler handler = new Handler(cameraThread.getLooper());
cameraManager.openCamera(cameraId, callback, handler);

4.3 SessionConfiguration 配置:一次会话的蓝图

SessionConfiguration 是什么?你可以把它理解成「拍照会话的配置单」。你要拍照片还是录视频?用几个输出目标?这些都在这里定义。

配置时,你需要指定:

  • 会话类型:CameraDevice.TEMPLATE_PREVIEW、TEMPLATE_RECORD 等
  • 输出配置列表:一个或多个 OutputConfiguration
  • 回调:监听会话状态变化

我见过不少开发者把 SessionConfiguration 和 CaptureRequest 搞混。简单区分:SessionConfiguration 是「硬件配置」,决定数据流向;CaptureRequest 是「软件配置」,决定每帧的参数。

注意:SessionConfiguration 一旦提交,就不能再修改。如果你想改变输出目标,必须关闭当前会话,重新创建。我曾经在项目中为了动态切换分辨率,不得不维护多个会话,确实有点麻烦。

4.4 OutputConfiguration 详解:数据要送到哪?

OutputConfiguration 定义了相机数据的输出目标。说白了,就是告诉系统:「拍到的画面,请送到这个 Surface 上。」

常见的输出目标有:

输出类型 典型用途 注意事项
SurfaceView 预览画面 性能好,但需要自己管理生命周期
TextureView 预览 + 特效处理 比 SurfaceView 慢一点,但更灵活
ImageReader 拍照或帧处理 注意设置合适的 maxImages,避免丢帧
MediaRecorder 视频录制 需要配合 MediaCodec 使用

嗯,这里有个坑:OutputConfiguration 的尺寸必须和 Camera 支持的尺寸匹配。否则 createCaptureSession 会失败。我建议你在配置前,先通过 CameraCharacteristics 查询支持的尺寸列表。

实战经验:如果你需要同时预览和拍照,可以创建两个 OutputConfiguration:一个给 SurfaceView,一个给 ImageReader。然后通过 SessionConfiguration 一起提交。这样系统会复用同一个相机流,节省资源。

4.5 完整配置流程:从创建到启动

把上面这些串起来,一个典型的配置流程是这样的:

  1. 调用 openCamera,等待 onOpened 回调
  2. 在 onOpened 中,创建 OutputConfiguration 列表
  3. 创建 SessionConfiguration,传入输出列表和回调
  4. 调用 cameraDevice.createCaptureSession(sessionConfig)
  5. 等待 onConfigured 回调,表示会话就绪
  6. 创建 CaptureRequest,开始预览或拍照

我曾经在步骤 4 和 5 之间踩过坑:createCaptureSession 也是异步的。你不能在调用后立即发送 CaptureRequest,必须等 onConfigured 回调。否则系统会报错,甚至导致相机服务崩溃。

避坑指南:我曾经在 onConfigured 回调里直接发送 CaptureRequest,结果发现预览画面是黑的。排查了半天,原来是 Surface 还没准备好。后来我加了一个标志位,等 Surface 的 first frame 回调后再发送请求,问题就解决了。

4.6 性能与兼容性考量

最后聊点实际的。不同厂商的 HAL 实现差异很大,我遇到过的情况:

  • 小米手机:对 OutputConfiguration 的尺寸要求严格,差一个像素都不行
  • 三星手机:支持多个输出,但数量有限制(通常 3 个以内)
  • 华为手机:对 SessionConfiguration 的 type 参数敏感,选错会导致性能下降

我的建议是:在配置前,先做一次兼容性检查。用 CameraCharacteristics 查询硬件支持的能力,再动态调整配置。不要写死任何参数。

好了,CameraDevice 的创建和配置就聊到这。记住:异步、异步、异步——重要的事情说三遍。所有操作都要等回调,别偷懒。


公众号:蓝海资料掘金营,微信deep3321