4. CameraCaptureSession:创建CaptureSession、配置输出Surface、处理Session状态回调

好,咱们接着往下聊。上一节我们把 CameraDevice 打开了,但光有设备还不行——你得告诉它「往哪儿输出数据」。这就轮到 CameraCaptureSession 登场了。

说白了,CaptureSession 就是摄像头和输出目标之间的桥梁。你想想看,摄像头采集到的每一帧数据,总得有个去处吧?是显示到屏幕上,还是编码成视频文件,或者交给图像处理器做分析?这些目标,就是 Surface。而 CaptureSession 负责把摄像头和这些 Surface 绑定起来,然后启动数据流。

核心要点:CaptureSession 的生命周期 = 配置 Surface → 创建会话 → 处理回调 → 关闭会话。一步都不能乱。

4.1 创建 CaptureSession 的完整流程

创建会话,我习惯分三步走:

  1. 准备好输出 Surface——比如 TextureView 的 Surface、MediaCodec 的输入 Surface、ImageReader 的 Surface。
  2. 构建请求模板——用 CameraDevice.createCaptureRequest() 生成 CaptureRequest。
  3. 调用 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。

我曾经踩过的坑:早期做项目时,我在 onConfigured 回调外面持有了 session 引用,结果在另一个线程里直接调用 session.capture()——崩溃了。原因是回调还没触发,session 还是 null。后来我加了个 AtomicReference 做保护,才稳下来。

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,这在某些场景下能省内存。

小技巧:如果你只需要预览 + 拍照,其实不用创建两个 Surface。拍照时临时把预览的 Surface 替换成 ImageReader 的 Surface 就行。但如果你需要同时预览和录像,那就必须用两个 Surface 了。

4.3 处理 Session 状态回调

CameraCaptureSession.StateCallback 里有几个关键回调,我列个表方便你对照:

回调方法 触发时机 常见处理
onConfigured 会话配置成功,可以开始捕获 启动预览循环(setRepeatingRequest)
onConfigureFailed 配置失败,无法使用会话 释放资源,提示用户重试
onReady 会话空闲,没有正在进行的请求 可以安全地关闭会话或切换配置
onActive 会话开始处理请求 更新 UI 状态(比如显示录制中)
onClosed 会话已关闭 清理资源,置空引用
onSurfacePrepared 某个 Surface 已准备好接收数据 用于延迟添加 Surface 的场景

这里面,onConfiguredonConfigureFailed 是最常用的。但 onReadyonActive 也很有用——比如你想在用户开始录像时显示一个红点,就可以在 onActive 里更新 UI。

我记得有一次做直播项目,用户反馈偶尔会黑屏。排查了半天,发现是 onConfigureFailed 回调里没有做任何处理,导致会话创建失败后程序继续往下走,最终 Surface 一直没数据。后来我在回调里加了重试逻辑,问题就解决了。

避坑指南:onConfigureFailed 不常见,但一旦触发,通常意味着 Surface 配置有问题。比如 Surface 尺寸不被支持,或者 Surface 已经被释放了。建议在回调里打印详细日志,并尝试用 CameraCharacteristics 查询支持的尺寸。

4.4 核心逻辑流程图

下面这张图概括了 CaptureSession 的完整生命周期。我画的时候特意把「创建」和「关闭」两个关键路径标红了,你重点关注一下。

CameraCaptureSession 生命周期 创建 Session onConfigured onActive(开始捕获) onReady(空闲) 关闭 Session onConfigureFailed setRepeatingRequest 正常路径 失败/关闭路径

从图里你能看到,正常流程是:创建 → onConfigured → onActive(开始捕获)→ onReady(空闲)→ 关闭。如果配置失败,直接跳到 onConfigureFailed,然后也必须关闭会话。

这里有个细节:onReady 和 onActive 会交替触发。当你调用 setRepeatingRequest 后,会话进入 onActive;当所有请求完成(比如你停止了重复请求),会话回到 onReady。所以 onReady 是关闭会话的最佳时机——确保没有正在进行的请求,避免数据丢失。

4.5 会话关闭与资源释放

最后聊一下关闭。关闭会话很简单,调用 session.close() 就行。但要注意顺序:

  1. 先停止所有正在进行的请求(调用 session.stopRepeating()session.abortCaptures()
  2. 再关闭会话
  3. 最后关闭 CameraDevice

为什么?因为如果你先关设备,会话还在跑,系统会报错。我见过不少开发者把顺序搞反,结果 logcat 里刷出一堆 IllegalStateException

我的习惯:在 onPause() 或 onDestroy() 里统一做清理。我会用一个状态机来跟踪当前会话的状态,确保不会重复关闭。比如用一个 int 变量表示状态:STATE_IDLE、STATE_OPENING、STATE_OPENED、STATE_CLOSING。这样能避免很多并发问题。

好了,关于 CaptureSession 的核心内容就这些。你只要记住:创建时配好 Surface,回调里处理状态,关闭时注意顺序,基本就不会出大问题。下一节我们会聊到 CaptureRequest 的详细配置,到时候你会看到 Session 和 Request 是怎么配合工作的。


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