Android Camera2 API中ImageReader回调失效,无法抓取对焦锁定预览帧求助
我之前在Camera2框架里折腾预览帧捕获的时候,也碰到过几乎一模一样的问题!咱们一步步拆解解决:
问题根源分析
你说调试时程序会退出ImageReader回调,大概率是这几个原因:
- 图像缓冲区没及时释放:Camera2的预览帧是循环复用的,如果在回调里没调用
image.close(),系统会认为缓冲区被占用,直接终止回调不再发帧; - 耗时操作阻塞回调线程:如果在回调里直接做保存图片的IO操作,系统会因为怕影响预览流畅度,强制终止这个回调;
- 对焦锁定与帧捕获的时机不匹配:如果是临时绑定ImageReader,可能还没等帧过来,对焦状态已经变化了。
具体解决方案
1. 提前绑定ImageReader到预览会话
不要等对焦锁定了才去创建ImageReader,初始化CameraCaptureSession时就把它和预览Surface(比如TextureView)一起绑定,这样预览流会同时输出到两个Surface,完全不阻塞预览:
// 初始化ImageReader,尺寸要和预览尺寸一致,格式用YUV_420_888(通用预览格式) ImageReader imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); // 缓冲区设2个,避免卡顿 imageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); // 把预览Surface和ImageReader的Surface都加入输出列表 List<Surface> surfaces = new ArrayList<>(); surfaces.add(mTextureView.getSurface()); surfaces.add(imageReader.getSurface()); // 创建CaptureSession时传入这些Surface cameraDevice.createCaptureSession(surfaces, mSessionStateCallback, mBackgroundHandler);
2. 在ImageReader回调里判断对焦状态
不用主动“抓取”帧,而是在回调里检查当前对焦是否锁定,只有锁定时才处理这一帧,同时一定要用try-with-resources自动释放图像缓冲区:
private boolean mIsFocusLocked = false; private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // try-with-resources会自动调用image.close(),避免缓冲区泄漏 try (Image image = reader.acquireNextImage()) { if (image == null || !mIsFocusLocked) return; // 这里只处理对焦锁定后的帧,而且要把保存操作扔到后台线程 savePreviewFrame(image); // 如果只需要抓一次,记得把标记改回去,避免重复保存 mIsFocusLocked = false; } catch (Exception e) { Log.e(TAG, "帧处理失败", e); } } };
3. 异步处理图片保存,绝对不能阻塞回调
文件写入是耗时操作,必须放到单独的后台线程,不然回调线程会被系统终止:
private void savePreviewFrame(Image image) { mBackgroundHandler.post(() -> { // 把YUV格式转成JPEG(这里用YUV转Bitmap的示例) Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuffer = planes[0].getBuffer(); ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); byte[] nv21 = new byte[yBuffer.remaining() + uBuffer.remaining() + vBuffer.remaining()]; yBuffer.get(nv21, 0, yBuffer.remaining()); vBuffer.get(nv21, yBuffer.remaining(), vBuffer.remaining()); uBuffer.get(nv21, yBuffer.remaining() + vBuffer.remaining(), uBuffer.remaining()); Bitmap bitmap = BitmapFactory.decodeByteArray(nv21, 0, nv21.length, null); if (bitmap != null) { try (FileOutputStream fos = new FileOutputStream(new File(getExternalFilesDir(null), "focused_preview.jpg"))) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); Log.d(TAG, "锁定对焦后的预览帧保存成功!"); } catch (IOException e) { Log.e(TAG, "保存图片失败", e); } bitmap.recycle(); } }); }
4. 正确触发对焦锁定并标记状态
在需要对焦的时候发送锁定请求,等系统回调确认锁定后,再打开帧捕获的开关:
// 触发对焦锁定 CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); mCaptureSession.capture(previewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); if (afState != null && afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED) { Log.d(TAG, "对焦已锁定,开始捕获预览帧"); mIsFocusLocked = true; // 恢复正常预览请求 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); mCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, mBackgroundHandler); } else { Log.w(TAG, "对焦锁定失败,恢复预览"); previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); mCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, mBackgroundHandler); } } }, mBackgroundHandler);
关键注意点
- 永远不要忘记释放Image对象!用
try-with-resources是最省心的方式,手动调用image.close()也行,但一定要做; - ImageReader的缓冲区数量至少设2个,避免预览流因为缓冲区不足卡顿;
- 如果只需要抓一次锁定后的帧,记得及时把
mIsFocusLocked设回false,或者暂时移除ImageReader的监听。
内容的提问来源于stack exchange,提问作者Monica Das
相关产品推荐
相关产品推荐

