5. CaptureRequest与CaptureResult:创建CaptureRequest、设置预览参数、处理Capture回调

好,咱们今天来聊聊 Camera2 里最核心的一对兄弟——CaptureRequestCaptureResult

说白了,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);
}

你想想看,如果不做这个检查,在一些低端机上直接就崩溃了。所以,永远不要假设设备支持所有模式

小技巧: 设置参数时,建议先通过 CameraCharacteristics 查询设备支持的能力范围,再设置具体的值。这样可以避免很多兼容性问题。

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
        // 可以在这里读取各种参数的实际值
    }
};

这里有个细节:onCaptureProgressedonCaptureCompleted 的区别。前者是「部分结果」,后者是「完整结果」。说白了,部分结果是相机还在处理中,先给你一些中间数据;完整结果是所有处理都完成了。

我曾经在调试自动曝光时,发现 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);
}
注意: CaptureResult 里的值不一定和你设置的一样。比如你设置了曝光时间为 1/30 秒,但相机可能因为硬件限制,实际用了 1/29.97 秒。所以,永远以 CaptureResult 里的值为准

5.4 知识体系总览

为了让你更直观地理解这一章的内容,我画了一张图,把 CaptureRequest 和 CaptureResult 的整个流程串起来:

CaptureRequest 与 CaptureResult 核心流程 1. 创建 Builder createCaptureRequest() 2. 添加 Surface addTarget() 3. 设置参数 set(Key, Value) 4. 发送 Request capture() 或 setRepeatingRequest() onCaptureStarted 捕获开始 onCaptureProgressed 部分结果 onCaptureCompleted 完整结果 CaptureResult 可读取:AF模式、曝光时间、ISO、白平衡、帧率等实际参数

这张图把整个流程分成了三行:上面是「创建阶段」,中间是「发送阶段」,下面是「回调阶段」。你跟着箭头走一遍,基本就掌握了 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