CameraDevice与Session:CameraDevice接口、CaptureSession创建、OutputConfiguration、Session参数配置
大家好,我是你们的老朋友。今天我们来聊聊Camera HAL层之上的两个核心概念——CameraDevice和CaptureSession。说白了,这两个东西就是相机应用的“遥控器”和“拍摄现场”。
我个人习惯把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需要三个要素:
- CameraDevice(已经打开的)
- OutputConfiguration列表(你要把数据输出到哪里)
- 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_SESSION或PER_REQUEST的参数才有效。具体可以查CameraCharacteristics.REQUEST_AVAILABLE_SESSION_KEYS。
知识体系总览
下面这张图概括了CameraDevice、Session、OutputConfiguration和参数配置之间的关系:
从图中可以看到,CameraDevice是入口,OutputConfiguration和Session参数是配置原料,最终产出的是一个可用的CaptureSession。我个人习惯把Session的生命周期管理得特别严格——每次关闭Session前,先停止所有重复请求,再close(),最后才close() CameraDevice。顺序反了,轻则报错,重则死机。
好了,关于CameraDevice和Session的核心内容就这些。记住一句话:设备是基础,会话是舞台,参数是剧本。把这三者关系理清了,Camera2开发就成功了一半。
公众号:蓝海资料掘金营,微信deep3321