You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Camera2 API中ImageReader回调失效,无法抓取对焦锁定预览帧求助

我之前在Camera2框架里折腾预览帧捕获的时候,也碰到过几乎一模一样的问题!咱们一步步拆解解决:

问题根源分析

你说调试时程序会退出ImageReader回调,大概率是这几个原因:

  1. 图像缓冲区没及时释放:Camera2的预览帧是循环复用的,如果在回调里没调用image.close(),系统会认为缓冲区被占用,直接终止回调不再发帧;
  2. 耗时操作阻塞回调线程:如果在回调里直接做保存图片的IO操作,系统会因为怕影响预览流畅度,强制终止这个回调;
  3. 对焦锁定与帧捕获的时机不匹配:如果是临时绑定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:01:30