4. CameraCaptureSession:创建CaptureSession、配置输出Surface、处理Session状态回调
好,咱们接着往下聊。上一节我们把 CameraDevice 打开了,但光有设备还不行——你得告诉它「往哪儿输出数据」。这就轮到 CameraCaptureSession 登场了。
说白了,CaptureSession 就是摄像头和输出目标之间的桥梁。你想想看,摄像头采集到的每一帧数据,总得有个去处吧?是显示到屏幕上,还是编码成视频文件,或者交给图像处理器做分析?这些目标,就是 Surface。而 CaptureSession 负责把摄像头和这些 Surface 绑定起来,然后启动数据流。
4.1 创建 CaptureSession 的完整流程
创建会话,我习惯分三步走:
- 准备好输出 Surface——比如 TextureView 的 Surface、MediaCodec 的输入 Surface、ImageReader 的 Surface。
- 构建请求模板——用 CameraDevice.createCaptureRequest() 生成 CaptureRequest。
- 调用 CameraDevice.createCaptureSession()——传入 Surface 列表和回调。
来看一段实际代码。这是我项目里常用的写法:
// 1. 准备输出 Surface
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface previewSurface = new Surface(texture);
// 2. 构建预览请求
CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW
);
previewRequestBuilder.addTarget(previewSurface);
// 3. 创建 CaptureSession
cameraDevice.createCaptureSession(
Arrays.asList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
// 会话创建成功,可以开始预览了
captureSession = session;
try {
session.setRepeatingRequest(previewRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// 配置失败,需要处理
Log.e(TAG, "会话配置失败");
}
},
backgroundHandler
);
嗯,这里要注意:createCaptureSession() 是异步方法。你调用它之后,不会立刻得到会话对象。系统会在内部完成配置,然后通过回调告诉你结果。所以千万别在调用之后马上用 session,否则你会拿到 null。
4.2 配置输出 Surface:单路与多路输出
大多数场景下,我们只需要一个输出 Surface——比如预览。但有些需求需要同时输出多路数据,比如:
- 预览 + 拍照
- 预览 + 视频编码
- 预览 + 图像分析(比如人脸检测)
这时候,你传给 createCaptureSession() 的 Surface 列表里就要包含多个 Surface。但有个限制:所有 Surface 必须来自同一个 CameraDevice,而且它们的尺寸和格式要兼容。
我个人习惯用 SessionConfiguration 来配置多路输出,这是 Android 8.0 引入的 API,更清晰:
List<OutputConfiguration> outputConfigs = new ArrayList<>();
outputConfigs.add(new OutputConfiguration(previewSurface));
outputConfigs.add(new OutputConfiguration(imageReaderSurface));
SessionConfiguration sessionConfig = new SessionConfiguration(
SessionConfiguration.SESSION_REGULAR,
outputConfigs,
backgroundExecutor,
stateCallback
);
cameraDevice.createCaptureSession(sessionConfig);
为什么推荐这种方式?因为 OutputConfiguration 可以设置更多参数,比如 Surface 的旋转角度、压缩格式等。而且它支持 SurfaceSharing——多个 CaptureRequest 共享同一个 Surface,这在某些场景下能省内存。
4.3 处理 Session 状态回调
CameraCaptureSession.StateCallback 里有几个关键回调,我列个表方便你对照:
| 回调方法 | 触发时机 | 常见处理 |
|---|---|---|
| onConfigured | 会话配置成功,可以开始捕获 | 启动预览循环(setRepeatingRequest) |
| onConfigureFailed | 配置失败,无法使用会话 | 释放资源,提示用户重试 |
| onReady | 会话空闲,没有正在进行的请求 | 可以安全地关闭会话或切换配置 |
| onActive | 会话开始处理请求 | 更新 UI 状态(比如显示录制中) |
| onClosed | 会话已关闭 | 清理资源,置空引用 |
| onSurfacePrepared | 某个 Surface 已准备好接收数据 | 用于延迟添加 Surface 的场景 |
这里面,onConfigured 和 onConfigureFailed 是最常用的。但 onReady 和 onActive 也很有用——比如你想在用户开始录像时显示一个红点,就可以在 onActive 里更新 UI。
我记得有一次做直播项目,用户反馈偶尔会黑屏。排查了半天,发现是 onConfigureFailed 回调里没有做任何处理,导致会话创建失败后程序继续往下走,最终 Surface 一直没数据。后来我在回调里加了重试逻辑,问题就解决了。
4.4 核心逻辑流程图
下面这张图概括了 CaptureSession 的完整生命周期。我画的时候特意把「创建」和「关闭」两个关键路径标红了,你重点关注一下。
从图里你能看到,正常流程是:创建 → onConfigured → onActive(开始捕获)→ onReady(空闲)→ 关闭。如果配置失败,直接跳到 onConfigureFailed,然后也必须关闭会话。
这里有个细节:onReady 和 onActive 会交替触发。当你调用 setRepeatingRequest 后,会话进入 onActive;当所有请求完成(比如你停止了重复请求),会话回到 onReady。所以 onReady 是关闭会话的最佳时机——确保没有正在进行的请求,避免数据丢失。
4.5 会话关闭与资源释放
最后聊一下关闭。关闭会话很简单,调用 session.close() 就行。但要注意顺序:
- 先停止所有正在进行的请求(调用
session.stopRepeating()或session.abortCaptures()) - 再关闭会话
- 最后关闭 CameraDevice
为什么?因为如果你先关设备,会话还在跑,系统会报错。我见过不少开发者把顺序搞反,结果 logcat 里刷出一堆 IllegalStateException。
好了,关于 CaptureSession 的核心内容就这些。你只要记住:创建时配好 Surface,回调里处理状态,关闭时注意顺序,基本就不会出大问题。下一节我们会聊到 CaptureRequest 的详细配置,到时候你会看到 Session 和 Request 是怎么配合工作的。
公众号:蓝海资料掘金营,微信deep3321