5. CaptureRequest与CaptureResult:创建CaptureRequest、设置预览参数、处理Capture回调
好,咱们今天来聊聊 Camera2 里最核心的一对兄弟——CaptureRequest 和 CaptureResult。
说白了,CaptureRequest 就是你给相机下达的「拍照指令」,而 CaptureResult 就是相机执行完指令后返回的「执行报告」。你想想看,没有指令,相机不知道要干什么;没有报告,你也不知道相机干得怎么样。所以这俩东西,是 Camera2 开发绕不开的核心。
我个人习惯把 CaptureRequest 理解成一张「任务单」。上面写着:你要用什么分辨率、要不要自动对焦、闪光灯开不开、曝光补偿调多少……然后你把这张单子交给相机,相机按单子干活,干完了给你一张回执——就是 CaptureResult。
5.1 创建CaptureRequest
创建 CaptureRequest 其实不复杂,但有几个坑。我先说标准流程。
你需要先拿到 CameraDevice,然后通过它创建 CaptureRequest.Builder:
// 假设你已经有了 cameraDevice
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
这里有个参数叫 TEMPLATE_PREVIEW,它决定了这个 Request 的「模板」。Camera2 提供了好几种模板:
| 模板常量 | 适用场景 |
|---|---|
| TEMPLATE_PREVIEW | 预览画面,追求流畅 |
| TEMPLATE_RECORD | 视频录制,平衡画质和帧率 |
| TEMPLATE_STILL_CAPTURE | 拍照,优先保证画质 |
| TEMPLATE_VIDEO_SNAPSHOT | 录像时拍照 |
| TEMPLATE_MANUAL | 完全手动控制 |
嗯,这里要注意:模板只是「初始值」,你后面可以随意修改。比如你用 TEMPLATE_PREVIEW 创建,但把曝光时间改成手动,完全没问题。
创建完 Builder 之后,下一步是绑定输出 Surface:
// 假设你有一个预览用的 SurfaceView 的 Surface
requestBuilder.addTarget(previewSurface);
这一步很容易忘。我刚开始学的时候,经常忘记 addTarget,结果预览画面一片黑。后来养成了习惯:创建完 Builder 第一件事就是加 Surface。
5.2 设置预览参数
Builder 创建好了,接下来就是往里面塞参数。Camera2 的参数控制是通过 set 方法,配合 CaptureRequest.Key 来完成的。
常用的参数设置:
// 设置自动对焦模式为连续自动对焦
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
// 设置闪光灯模式
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
// 设置预览帧率范围
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<Integer>(30, 30));
我在项目中遇到过一个问题:有些手机对 CONTROL_AF_MODE_CONTINUOUS_PICTURE 支持不好,预览时对焦会来回抽动。后来我加了一个判断,先检查设备是否支持这个模式:
int[] afModes = characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
boolean supportsContinuous = false;
for (int mode : afModes) {
if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
supportsContinuous = true;
break;
}
}
if (supportsContinuous) {
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
} else {
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
}
你想想看,如果不做这个检查,在一些低端机上直接就崩溃了。所以,永远不要假设设备支持所有模式。
5.3 处理Capture回调
参数设置好了,接下来就是把 Request 发出去,并接收回调。Camera2 提供了两种回调方式:
- CameraCaptureSession.CaptureCallback:最常用,每次捕获都会回调
- CameraCaptureSession.StateCallback:用于监听 Session 状态变化
我们主要用 CaptureCallback。它的核心方法有这几个:
private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
long timestamp, long frameNumber) {
// 捕获开始,可以在这里更新UI,比如显示"正在拍照"
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
// 部分结果回调,中间状态
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
// 捕获完成,拿到完整的 CaptureResult
// 可以在这里读取各种参数的实际值
}
};
这里有个细节:onCaptureProgressed 和 onCaptureCompleted 的区别。前者是「部分结果」,后者是「完整结果」。说白了,部分结果是相机还在处理中,先给你一些中间数据;完整结果是所有处理都完成了。
我曾经在调试自动曝光时,发现 onCaptureProgressed 里拿到的 AE 状态经常不准,后来改用 onCaptureCompleted 才稳定。所以,如果你需要精确的参数值,建议等 onCaptureCompleted。
从 CaptureResult 里读取参数也很简单:
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
// 读取实际的对焦模式
Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE);
// 读取实际的曝光时间
Long exposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
// 读取实际的ISO
Integer iso = result.get(CaptureResult.SENSOR_SENSITIVITY);
Log.d("Camera", "AF模式: " + afMode + ", 曝光时间: " + exposureTime + ", ISO: " + iso);
}
5.4 知识体系总览
为了让你更直观地理解这一章的内容,我画了一张图,把 CaptureRequest 和 CaptureResult 的整个流程串起来:
这张图把整个流程分成了三行:上面是「创建阶段」,中间是「发送阶段」,下面是「回调阶段」。你跟着箭头走一遍,基本就掌握了 Camera2 预览的核心链路。
5.5 完整示例:启动预览
最后,我把前面讲的东西串成一个完整的预览启动代码。你直接拿去用,改改 Surface 就能跑:
private void startPreview(CameraDevice cameraDevice, Surface previewSurface) {
try {
// 1. 创建 Builder,使用预览模板
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 2. 绑定输出 Surface
requestBuilder.addTarget(previewSurface);
// 3. 设置预览参数
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
// 4. 创建 CaptureSession
cameraDevice.createCaptureSession(Arrays.asList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
try {
// 5. 发送重复请求(预览)
session.setRepeatingRequest(requestBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e("Camera", "配置 Session 失败");
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
- CaptureRequest 是「指令」,CaptureResult 是「报告」
- 创建 Builder 时选对模板,但后续可以随意修改
- 一定要 addTarget,否则画面出不来
- 设置参数前先检查设备是否支持
- 回调里用 onCaptureCompleted 拿最终结果
嗯,这一章的内容就到这里。CaptureRequest 和 CaptureResult 是 Camera2 的基石,你后面做拍照、录像、手动控制,都离不开它们。多写几遍,自然就熟了。
公众号:蓝海资料掘金营,微信deep3321