CaptureRequest详解:从构建到队列的完整旅程
大家好,我是你们的老朋友。今天我们来聊聊CaptureRequest——这个在Android相机系统中看似简单、实则暗藏玄机的核心对象。
说实话,我刚接触Camera2时,觉得CaptureRequest不就是个配置参数嘛,有什么好研究的?直到有一次,我在项目中遇到了预览画面卡顿、拍照延迟的问题,排查了整整两天,最后发现是Request构建方式不对。嗯,从那以后,我再也不敢小看它了。
1. Request构建:一切从这里开始
CaptureRequest的构建,说白了就是通过Builder模式一步步搭积木。我个人习惯把构建过程分为三步:
- 获取Builder对象:从CameraDevice创建
- 配置参数:设置各种控制项
- 添加Target:指定输出目标
来看一段最基础的代码:
// 从CameraDevice创建Builder
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
// 配置参数
builder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON);
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 添加Target
builder.addTarget(surfaceView.getSurface());
// 构建Request
CaptureRequest request = builder.build();
这里有个坑,我曾经踩过:build()方法调用后,Builder对象就不能再修改了。如果你需要不同的配置,必须重新创建Builder。所以,我建议把构建逻辑封装成一个方法,方便复用。
2. Target配置:输出目标的选择
Target,就是你的照片或视频最终要去哪里。可以是SurfaceView、ImageReader、MediaRecorder等等。
我记得有一次,同事问我:“为什么我加了两个Target,预览和拍照却不同步?”
问题出在——不同Target的帧率可能不一致。比如预览Surface是30fps,但拍照的ImageReader可能只捕获关键帧。这时候,你需要用addTarget和removeTarget动态管理。
常见的Target类型:
| Target类型 | 用途 | 注意事项 |
|---|---|---|
| SurfaceView | 预览显示 | 需要与Camera的尺寸匹配 |
| ImageReader | 拍照/帧处理 | 注意设置合适的ImageFormat |
| MediaRecorder | 视频录制 | 需要配置音频和视频轨道 |
| SurfaceTexture | OpenGL处理 | 常用于滤镜或特效 |
3. 参数设置:控制相机的每一个细节
CaptureRequest的参数设置,是控制相机行为的核心。说白了,就是告诉相机:“我要这样拍,不要那样拍。”
常用的参数分组:
- 曝光控制:AE模式、曝光补偿、ISO、快门速度
- 对焦控制:AF模式、对焦区域、对焦距离
- 白平衡:AWB模式、色温
- 图像处理:锐度、对比度、饱和度
- 输出控制:输出尺寸、格式、旋转角度
举个例子,如果你想实现“点击屏幕对焦”的功能:
// 设置对焦区域
MeteringRectangle focusRect = new MeteringRectangle(
x - 50, y - 50, 100, 100,
MeteringRectangle.METERING_WEIGHT_MAX);
builder.set(CaptureRequest.CONTROL_AF_REGIONS,
new MeteringRectangle[]{focusRect});
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_AUTO);
builder.set(CaptureRequest.CONTROL_AE_REGIONS,
new MeteringRectangle[]{focusRect});
// 触发对焦
builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_START);
你想想看,为什么对焦和测光区域要一起设置?因为大部分场景下,用户点击的位置既是焦点也是测光点。当然,你也可以分开设置,实现“对焦在A点,测光在B点”的效果。
4. Request队列管理:有序执行的关键
CaptureRequest不是发出去就完事了。它会进入一个队列,由相机HAL层按顺序处理。这个队列的管理,直接影响到拍照的响应速度。
我画了一张图,帮你理解Request的流转过程:
从图中可以看到,Request从应用层出发,进入队列,然后被HAL层逐帧处理。处理完成后,通过回调返回结果。
这里有个关键点:队列是先进先出的。如果你先发了一个拍照Request,又发了一个预览Request,拍照Request会先被处理。但如果你连续发了多个拍照Request,它们会排队等待,可能导致延迟。
5. 重复请求与单次请求:两种模式的抉择
这是CaptureRequest最核心的概念之一。说白了:
- 重复请求:一直发,直到你叫停。适合预览、视频录制。
- 单次请求:只发一次,拍完就完。适合拍照、对焦触发。
来看代码对比:
// 重复请求 - 预览
captureSession.setRepeatingRequest(previewRequest,
callback, handler);
// 单次请求 - 拍照
captureSession.capture(stillCaptureRequest,
callback, handler);
你可能会问:“为什么不用重复请求来拍照?”
嗯,这个问题我当年也想过。原因在于:重复请求会持续占用ISP资源。如果你用重复请求来拍照,意味着相机一直在全功率运行,功耗和发热都会增加。而单次请求只在需要时触发,更加高效。
我曾经在一个项目中,为了简化代码,用重复请求代替单次请求来拍照。结果手机发烫严重,用户反馈很差。后来改成单次请求,问题就解决了。
还有一个常见场景:先对焦,再拍照。流程是这样的:
- 发送一个单次请求,触发AF
- 等待AF完成回调
- 发送拍照请求
- 恢复预览的重复请求
这个流程看似简单,但实现起来有不少细节。我建议你把对焦和拍照的逻辑封装成状态机,避免回调嵌套过深。
CONTROL_AE_TARGET_FPS_RANGE来控制。比如设置成[15, 30],相机就会在15-30fps之间动态调整,平衡画质和功耗。
6. 避坑指南:我踩过的那些坑
最后,分享几个我实际项目中遇到的坑,希望能帮你少走弯路:
- Builder复用问题:build()之后不能修改,必须重新创建。我建议用工厂模式管理Builder。
- Target尺寸不匹配:Surface的尺寸必须和Camera支持的输出尺寸一致,否则会报错。记得用
StreamConfigurationMap.getOutputSizes()查询。 - 队列溢出:如果连续发送大量Request,队列可能溢出。HAL层会丢弃多余的Request。所以,控制好发送频率很重要。
- 回调线程:CaptureCallback默认在Camera的线程中执行,不要在里面做耗时操作。如果需要更新UI,记得切换到主线程。
好了,关于CaptureRequest的内容就讲到这里。希望这些经验对你有帮助。记住,理解Request的本质,是掌握Camera2 API的关键一步。
公众号:蓝海资料掘金营,微信deep3321