5、CaptureRequest 与 CaptureResult:CaptureRequest构建、CaptureResult回调机制、ResultMetadata解析、FrameNumber与RequestId管理

好,咱们今天来聊聊 CameraFramework 里最核心的一对“兄弟”——CaptureRequest 和 CaptureResult。说白了,这就是你给相机下指令,相机干完活给你回执的过程。我刚开始接触这套机制时,总觉得不就是发个请求收个结果嘛,能有多复杂?结果一上手,发现坑还真不少。

5.1 CaptureRequest 的构建:你给相机的“任务单”

CaptureRequest 是什么?就是你告诉相机:“嘿,帮我拍张照,参数是这样的:曝光用这个值,对焦在那个位置,输出格式要 JPEG。” 它本质上是一个任务描述对象。

构建一个 CaptureRequest,通常需要这几步:

  1. 从 CameraDevice 创建 Builder:调用 cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)。注意,模板类型决定了相机的初始状态。我个人习惯用 TEMPLATE_PREVIEW 做预览,TEMPLATE_STILL_CAPTURE 做拍照。
  2. 添加目标 Surface:告诉相机拍完的数据往哪送。可以是预览的 SurfaceView、TextureView,也可以是 ImageReader 的 Surface。
  3. 设置参数:通过 builder.set(CaptureRequest.CONTROL_AE_MODE, ...) 来配置。这里能设的东西太多了,从自动对焦模式到传感器灵敏度,应有尽有。
  4. 构建:调用 builder.build(),生成不可变的 CaptureRequest 对象。

核心要点:CaptureRequest 一旦 build 完成,就不能再改了。如果你需要不同的参数,必须重新创建一个新的 Builder。

来看一段实际代码:

// 创建预览用的 CaptureRequest
CaptureRequest.Builder previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.addTarget(previewSurface);
previewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
CaptureRequest previewRequest = previewBuilder.build();

嗯,这里要注意:addTarget 的顺序其实不影响结果,但如果你忘了加 Surface,那拍出来的数据就“无家可归”了。我曾经在调试一个项目时,花了整整一下午才发现少加了一个 Surface,结果预览黑屏——你说冤不冤?

5.2 CaptureResult 的回调机制:相机给你的“回执”

你发出请求后,相机处理完会通过回调把结果送回来。这个回调机制,说白了就是 CameraCaptureSession.CaptureCallback。它有多个回调方法,每个方法对应不同的处理阶段。

常用的回调方法有:

  • onCaptureStarted:相机开始处理这个请求了。这时候你可以知道请求的 timestamp,用来做同步。
  • onCaptureProgressed:中间状态,部分结果可用。我一般不太用这个,除非要做实时反馈。
  • onCaptureCompleted:整个请求处理完了,完整的 CaptureResult 回来了。这是最常用的。
  • onCaptureFailed:请求失败了。嗯,这个很重要,一定要处理。

避坑指南:我曾经在 onCaptureCompleted 里做了耗时操作,结果导致后续请求被阻塞。记住,这些回调是在相机线程里执行的,别做耗时操作。如果需要,请 post 到自己的 Handler 里。

回调的顺序是严格按请求顺序来的。你发请求 1、2、3,回调就会按 1、2、3 的顺序回来。但注意,onCaptureStartedonCaptureCompleted 可能不是成对出现的——如果请求失败,只有 onCaptureFailed 会被调用。

5.3 ResultMetadata 解析:从结果里“挖”出有用信息

CaptureResult 里装了一大堆元数据,比如实际的曝光时间、对焦状态、传感器温度等等。这些数据藏在 ResultMetadata 里,你需要通过 key 来取。

举个例子,你想知道这次拍照的对焦是否成功:

Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState != null) {
    switch (afState) {
        case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
            // 对焦成功且锁定
            break;
        case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
            // 对焦失败
            break;
        // 其他状态...
    }
}

你想想看,这些元数据有什么用?我举个例子:你在做 HDR 拍照时,可以通过 CaptureResult.SENSOR_EXPOSURE_TIME 来确认每帧的实际曝光时间是否和你设置的一致。如果不一致,可能是硬件限制导致的,需要调整策略。

注意:不是所有 key 在所有设备上都支持。在解析之前,最好先通过 Characteristics 检查一下是否可用。否则 get 回来 null,你还要做空判断。

常用的元数据 key 包括:

Key 说明 常见用途
SENSOR_EXPOSURE_TIME 实际曝光时间(纳秒) 验证曝光设置是否生效
SENSOR_SENSITIVITY 实际 ISO 值 检查感光度
CONTROL_AF_STATE 自动对焦状态 判断对焦是否成功
STATISTICS_FACES 检测到的人脸信息 人脸追踪
JPEG_ORIENTATION JPEG 图片旋转角度 确保图片方向正确

5.4 FrameNumber 与 RequestId 管理:给每一帧一个“身份证”

在连续拍照或录制视频时,你会发出很多请求。怎么区分哪个结果对应哪个请求?这就需要 FrameNumber 和 RequestId 了。

  • RequestId:你创建 CaptureRequest 时,系统会自动分配一个递增的 ID。这个 ID 在你的一次 capture 会话中是唯一的。
  • FrameNumber:这是硬件层面的帧序号。每次传感器输出一帧,FrameNumber 就加 1。注意,不是每个请求都会产生一帧——比如重复的预览请求,可能多个请求对应同一个 FrameNumber。

我个人的经验是:在调试时,把 RequestId 和 FrameNumber 都打出来,能帮你快速定位问题。比如:

@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
    long frameNumber = result.getFrameNumber();
    int requestId = request.getTag(); // 注意:需要你自己 setTag
    Log.d("Camera", "请求 " + requestId + " 完成,帧号: " + frameNumber);
}

这里有个小技巧:你可以通过 request.setTag(Object tag) 给每个请求打上自己的标签,比如一个整数 ID 或者一个字符串描述。这样在回调里就能轻松知道是哪个请求完成了。

核心关系:一个 RequestId 可能对应多个 FrameNumber(比如重复请求),但一个 FrameNumber 只对应一个 RequestId。记住这个关系,能帮你理解很多奇怪的现象。

为什么会这样?因为相机硬件是流水线作业。你发了一个请求,它可能被拆成多个帧来处理(比如 HDR 需要多帧合成)。反过来,如果你发的是重复预览请求,硬件可能只输出一帧,但多个请求都“共享”了这一帧。

我曾经在做一个高速连拍功能时,发现某些帧的 CaptureResult 里 FrameNumber 是跳变的。查了半天,原来是硬件 buffer 满了,丢了几帧。通过 FrameNumber 的连续性检查,我很快就定位到了问题。

知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

CaptureRequest 与 CaptureResult 核心流程 CaptureRequest 构建 1. createCaptureRequest(template) 2. addTarget(surface) 3. set(key, value) 4. build() → 不可变对象 CameraCaptureSession capture(request, callback, handler) setRepeatingRequest(request, ...) CaptureResult 回调 onCaptureStarted onCaptureProgressed onCaptureCompleted onCaptureFailed 发送 返回 FrameNumber 与 RequestId 管理 RequestId: 由系统分配,每个请求唯一,用于区分不同的 capture 请求 FrameNumber: 硬件帧序号,每输出一帧加 1,用于追踪硬件处理进度 关系: 一个 RequestId 可能对应多个 FrameNumber(如 HDR 多帧合成) 一个 FrameNumber 只对应一个 RequestId

这张图从左到右展示了完整的流程:你先构建 CaptureRequest,然后通过 CameraCaptureSession 发送出去,最后通过回调拿到 CaptureResult。底部则强调了 FrameNumber 和 RequestId 的管理关系——它们是串联整个流程的关键线索。

好了,关于 CaptureRequest 和 CaptureResult 的核心内容就这些。记住,构建请求时要细心,处理回调时要高效,解析元数据时要耐心,管理帧号时要严谨。把这四点做好,你在 CameraFramework 里就算入门了。