5、Camera2 API 实战:相机状态管理、预览流、拍照、视频录制、动态切换分辨率
Camera2 这套 API,说实话,刚接触的时候真有点劝退。它不像老旧的 Camera1 那样简单粗暴,而是把相机系统拆成了一个个组件,让你自己拼装。但拼装好了,你会发现它非常灵活,能做的事情多得多。
我个人习惯把 Camera2 理解成一个「流水线」。你想想看,相机传感器是原料,预览流是半成品,拍照和录像就是最终产品。而咱们要做的,就是管理好这条流水线的状态。
5.1 相机状态管理:别让 App 死在奇怪的地方
相机状态管理,说白了就是搞清楚「相机现在能不能用」。我见过太多新手直接调用 openCamera,结果设备被别的 App 占着,直接崩溃。
Camera2 的状态回调是 CameraDevice.StateCallback,它有三个关键方法:
onOpened:相机打开成功,可以开始干活了。onDisconnected:相机被系统强制断开,比如被电话打断。onError:出错了,可能是权限问题,也可能是硬件故障。
核心要点:一定要在 onOpened 里创建会话,在 onError 里释放资源。我曾经在 onDisconnected 里忘了释放,结果用户切回来时,相机一直黑屏。
这里有个小技巧:用一个状态枚举来跟踪相机状态,比如 STATE_CLOSED、STATE_OPENING、STATE_OPENED。每次回调都更新这个状态,其他地方判断起来就方便了。
5.2 预览流:让画面动起来
预览流是 Camera2 最基础的功能。你需要三样东西:CameraDevice、CameraCaptureSession、Surface(通常是 TextureView 或 SurfaceView)。
流程是这样的:
- 打开相机,拿到 CameraDevice。
- 用 CameraDevice 创建 CaptureRequest,指定目标 Surface。
- 创建 CameraCaptureSession,把 CaptureRequest 交出去。
嗯,这里要注意:创建 CaptureRequest 时,要设置 CaptureRequest.CONTROL_AF_MODE 为 CONTROL_AF_MODE_CONTINUOUS_PICTURE,这样预览画面才会自动对焦。我刚开始做的时候没设这个,画面一直模糊,还以为是硬件坏了。
个人经验:TextureView 比 SurfaceView 更灵活,可以方便地做旋转、缩放。但 SurfaceView 性能更好,如果你做高帧率预览,建议用 SurfaceView。
5.3 拍照:按下快门那一刻
拍照其实就是在预览流的基础上,多一个 ImageReader。ImageReader 会接收 JPEG 或 RAW 数据,然后回调给你。
关键代码片段:
ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
reader.setOnImageAvailableListener(reader1 -> {
Image image = reader1.acquireLatestImage();
// 处理 image,比如保存到文件
image.close(); // 别忘了关闭!
}, backgroundHandler);
这里有个坑:ImageReader 的 buffer 数量设为 1 就够了,设多了反而容易出问题。我曾经设了 3,结果拍照后画面卡住,因为 buffer 没及时释放。
拍照时还要注意:先锁定焦点和曝光,再拍照。不然拍出来的照片可能对焦不准。做法是创建一个新的 CaptureRequest,设置 CONTROL_AF_TRIGGER_START,等对焦完成后再拍照。
5.4 视频录制:MediaRecorder 与 Camera2 的配合
视频录制比拍照复杂一点,因为要同时处理音频和视频。MediaRecorder 是 Android 官方推荐的方案,它内部封装了编码器。
流程如下:
- 配置 MediaRecorder:设置音频源、视频源、输出格式、编码器、文件路径等。
- 调用
MediaRecorder.getSurface()获取一个 Surface。 - 把这个 Surface 作为预览流的目标之一,创建 CaptureRequest。
- 先 start 预览会话,再 start MediaRecorder。
注意:顺序很重要!必须先 start 预览会话,再 start MediaRecorder。反过来会导致录制文件损坏。我踩过这个坑,录了半小时的视频打不开,欲哭无泪。
另外,录制过程中如果要暂停,可以用 MediaRecorder.pause() 和 resume(),但要注意 API 24 以上才支持。
5.5 动态切换分辨率:别让用户等太久
动态切换分辨率,说白了就是重新创建预览流和拍照/录制配置。但你不能直接改,得先关闭当前会话,再重新打开。
步骤:
- 关闭当前的 CameraCaptureSession。
- 更新 TextureView 或 ImageReader 的尺寸。
- 重新创建 CaptureRequest 和 Session。
这里有个性能问题:频繁切换分辨率会导致画面短暂黑屏。我建议在切换时显示一个加载动画,或者用过渡帧来平滑切换。
另外,不是所有分辨率都支持。你可以通过 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP 获取支持的尺寸列表,然后让用户选择。我一般会预置几个常用分辨率:480p、720p、1080p、4K。
避坑指南:我曾经在切换分辨率时忘了释放 ImageReader,导致内存泄漏。每次切换都 new 一个 ImageReader,旧的没 close,最后 OOM 了。记得在切换前 close 掉旧的 ImageReader。
5.6 知识体系总览
下面这张图概括了 Camera2 的核心流程和状态管理。你可以把它当作开发时的参考地图。
这张图里,CameraDevice 是起点,状态回调负责监听它的生命周期。预览流是基础,拍照和录制都依赖它。动态切换分辨率则是一个「破坏-重建」的过程,需要小心处理资源释放。
好了,这一章的内容就到这里。Camera2 虽然复杂,但掌握了状态管理和流程控制,后面的事情就顺理成章了。
最后提醒一句:别忘了在 Activity 的 onPause 里关闭相机,在 onResume 里重新打开。这是最基本的生命周期管理,但也是最容易忘的。
公众号:蓝海资料掘金营,微信 deep3321