4、CameraDevice 创建与配置:openCamera流程、CameraDeviceImpl初始化、SessionConfiguration配置、OutputConfiguration详解
好,咱们今天聊点硬核的。CameraDevice 的创建和配置,说白了就是相机从「休眠」到「就绪」的关键一步。很多开发者觉得 openCamera 就是个回调,但里面门道不少。我当年第一次对接 HAL 层时,就被 openCamera 的异步流程坑过——回调没来,我以为是手机坏了,结果是自己没理解 binder 的线程模型。
4.1 openCamera 流程:从 App 到 HAL 的漫长旅程
你调用 CameraManager.openCamera() 时,系统到底在干嘛?我画了个简图,帮你理清脉络。
嗯,这里有个关键点: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 完整配置流程:从创建到启动
把上面这些串起来,一个典型的配置流程是这样的:
- 调用 openCamera,等待 onOpened 回调
- 在 onOpened 中,创建 OutputConfiguration 列表
- 创建 SessionConfiguration,传入输出列表和回调
- 调用 cameraDevice.createCaptureSession(sessionConfig)
- 等待 onConfigured 回调,表示会话就绪
- 创建 CaptureRequest,开始预览或拍照
我曾经在步骤 4 和 5 之间踩过坑:createCaptureSession 也是异步的。你不能在调用后立即发送 CaptureRequest,必须等 onConfigured 回调。否则系统会报错,甚至导致相机服务崩溃。
避坑指南:我曾经在 onConfigured 回调里直接发送 CaptureRequest,结果发现预览画面是黑的。排查了半天,原来是 Surface 还没准备好。后来我加了一个标志位,等 Surface 的 first frame 回调后再发送请求,问题就解决了。
4.6 性能与兼容性考量
最后聊点实际的。不同厂商的 HAL 实现差异很大,我遇到过的情况:
- 小米手机:对 OutputConfiguration 的尺寸要求严格,差一个像素都不行
- 三星手机:支持多个输出,但数量有限制(通常 3 个以内)
- 华为手机:对 SessionConfiguration 的 type 参数敏感,选错会导致性能下降
我的建议是:在配置前,先做一次兼容性检查。用 CameraCharacteristics 查询硬件支持的能力,再动态调整配置。不要写死任何参数。
好了,CameraDevice 的创建和配置就聊到这。记住:异步、异步、异步——重要的事情说三遍。所有操作都要等回调,别偷懒。