CameraDevice与Session:CameraDevice接口、CaptureSession创建、OutputConfiguration、Session参数配置

大家好,我是你们的老朋友。今天我们来聊聊Camera HAL层之上的两个核心概念——CameraDeviceCaptureSession。说白了,这两个东西就是相机应用的“遥控器”和“拍摄现场”。

我个人习惯把CameraDevice理解成“相机设备的抽象句柄”,而CaptureSession则是“一次拍摄任务的上下文”。你想想看,没有设备句柄,你连相机都打不开;没有Session,你拍出来的东西往哪儿送?

CameraDevice接口:打开相机的那把钥匙

CameraDevice是Android Camera2 API中代表单个相机设备的接口。每个物理摄像头(比如后置主摄、前置、超广角)都对应一个CameraDevice实例。

获取CameraDevice的方式很简单——通过CameraManager.openCamera()。但这里有个坑:openCamera是异步的。我记得刚接触Camera2时,我习惯性地在openCamera后面直接调用createCaptureSession,结果总是报错。后来才明白,必须等CameraDevice.StateCallback里的onOpened回调触发后,才能进行后续操作。

核心接口方法:

  • createCaptureSession():创建拍照/预览会话
  • createReprocessableCaptureSession():创建可重处理的会话(比如YUV→JPEG)
  • createConstrainedHighSpeedCaptureSession():高速连拍专用
  • close():关闭设备,释放资源

这里有个经验之谈:千万不要在onDisconnected或onError回调里忽略资源清理。我曾经在一个项目里,因为相机被其他应用抢占后没有及时close(),导致系统相机服务卡死,最后只能重启设备。嗯,血的教训。

CaptureSession创建:从配置到运行

创建CaptureSession需要三个要素:

  1. CameraDevice(已经打开的)
  2. OutputConfiguration列表(你要把数据输出到哪里)
  3. SessionCallback(会话状态监听)

代码看起来是这样的:

// 假设cameraDevice已经通过onOpened回调拿到
List<OutputConfiguration> outputs = new ArrayList<>();
// 添加SurfaceView的Surface
outputs.add(new OutputConfiguration(surfaceView.getHolder().getSurface()));
// 添加ImageReader的Surface(用于拍照)
outputs.add(new OutputConfiguration(imageReader.getSurface()));

cameraDevice.createCaptureSession(
    outputs,
    new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            // 会话创建成功,可以开始预览或拍照了
            // 这里保存session引用
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            // 配置失败,通常是Surface不合法或参数冲突
        }
    },
    null // 可以传Handler,指定回调线程
);

为什么会有onConfigureFailed?我遇到过一种情况:同时给两个OutputConfiguration传了同一个Surface(比如两个ImageReader用了同一个Surface),结果底层HAL直接拒绝配置。所以,每个Surface在OutputConfiguration列表里只能出现一次

OutputConfiguration:输出目标的精细控制

OutputConfiguration不只是简单包装一个Surface。它还能做很多事情:

特性 说明 我的使用场景
Surface共享 多个OutputConfiguration可以共享同一个Surface(需设置isShared=true) 预览和拍照共用同一个Surface时,需要显式声明共享
物理相机ID 指定输出目标对应的物理摄像头(多摄场景) 超广角和主摄同时输出时,分别指定physicalCameraId
Surface尺寸 通过Surface的setDefaultBufferSize控制 避免ImageReader创建时尺寸不匹配

小技巧:如果你需要同时输出预览和拍照,建议用两个不同的Surface。预览用SurfaceView的Surface,拍照用ImageReader的Surface。这样预览帧率和拍照分辨率互不影响。

Session参数配置:让会话按你的意愿工作

Session参数配置是通过CaptureRequest.Builder来完成的。但注意,有些参数是“会话级别”的,必须在创建Session之前通过SessionConfiguration设置。

举个例子:

// 创建SessionConfiguration
SessionConfiguration config = new SessionConfiguration(
    SessionConfiguration.SESSION_REGULAR, // 普通会话
    outputs, // OutputConfiguration列表
    executor, // 回调执行器
    callback // StateCallback
);

// 设置会话参数(比如HDR模式、3A收敛等)
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
// ... 其他参数

// 将参数绑定到SessionConfiguration
config.setSessionParameters(builder.build());

// 创建会话
cameraDevice.createCaptureSession(config);

为什么要这么做?因为有些参数(比如AE模式、AF模式)会影响底层ISP的初始化流程。如果等Session创建完再设置,可能已经错过了最佳配置时机。我曾在某个平台上发现,如果不提前设置AE模式,预览的前几帧会过曝,直到AE收敛后才正常。后来把AE模式放到Session参数里,问题就解决了。

注意:不是所有参数都能作为Session参数。只有那些在CameraCharacteristics中标记为PER_SESSIONPER_REQUEST的参数才有效。具体可以查CameraCharacteristics.REQUEST_AVAILABLE_SESSION_KEYS

知识体系总览

下面这张图概括了CameraDevice、Session、OutputConfiguration和参数配置之间的关系:

CameraDevice openCamera() createCaptureSession() onConfigured() Session OutputConfiguration 列表 包含 SessionConfiguration.setSessionParameters() 绑定 关键流程: 1. 通过 CameraManager.openCamera() 获取 CameraDevice 2. 准备 OutputConfiguration 列表(每个 Surface 一个配置) 3. 可选:通过 SessionConfiguration 设置会话级参数 4. 调用 createCaptureSession(),等待 onConfigured 回调

从图中可以看到,CameraDevice是入口,OutputConfiguration和Session参数是配置原料,最终产出的是一个可用的CaptureSession。我个人习惯把Session的生命周期管理得特别严格——每次关闭Session前,先停止所有重复请求,再close(),最后才close() CameraDevice。顺序反了,轻则报错,重则死机。

好了,关于CameraDevice和Session的核心内容就这些。记住一句话:设备是基础,会话是舞台,参数是剧本。把这三者关系理清了,Camera2开发就成功了一半。


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