5、CaptureRequest 与 CaptureResult:CaptureRequest构建、CaptureResult回调机制、ResultMetadata解析、FrameNumber与RequestId管理
好,咱们今天来聊聊 CameraFramework 里最核心的一对“兄弟”——CaptureRequest 和 CaptureResult。说白了,这就是你给相机下指令,相机干完活给你回执的过程。我刚开始接触这套机制时,总觉得不就是发个请求收个结果嘛,能有多复杂?结果一上手,发现坑还真不少。
5.1 CaptureRequest 的构建:你给相机的“任务单”
CaptureRequest 是什么?就是你告诉相机:“嘿,帮我拍张照,参数是这样的:曝光用这个值,对焦在那个位置,输出格式要 JPEG。” 它本质上是一个任务描述对象。
构建一个 CaptureRequest,通常需要这几步:
- 从 CameraDevice 创建 Builder:调用
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)。注意,模板类型决定了相机的初始状态。我个人习惯用 TEMPLATE_PREVIEW 做预览,TEMPLATE_STILL_CAPTURE 做拍照。 - 添加目标 Surface:告诉相机拍完的数据往哪送。可以是预览的 SurfaceView、TextureView,也可以是 ImageReader 的 Surface。
- 设置参数:通过
builder.set(CaptureRequest.CONTROL_AE_MODE, ...)来配置。这里能设的东西太多了,从自动对焦模式到传感器灵敏度,应有尽有。 - 构建:调用
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 的顺序回来。但注意,onCaptureStarted 和 onCaptureCompleted 可能不是成对出现的——如果请求失败,只有 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,然后通过 CameraCaptureSession 发送出去,最后通过回调拿到 CaptureResult。底部则强调了 FrameNumber 和 RequestId 的管理关系——它们是串联整个流程的关键线索。
好了,关于 CaptureRequest 和 CaptureResult 的核心内容就这些。记住,构建请求时要细心,处理回调时要高效,解析元数据时要耐心,管理帧号时要严谨。把这四点做好,你在 CameraFramework 里就算入门了。