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

基于Camera2 API实现自动对焦锁定后无阻塞拍照的方案咨询

Problem

I've built a feature based on the Google Camera2 sample that captures a frame immediately after auto-focus locks, but the preview stutters briefly when taking the picture. I need a solution to capture frames in the background without blocking the preview at all. Here's my current code:

boolean areWeFocused = false; int counter =0;
/**
 * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
 */
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
    private void process(CaptureResult result) {
        Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
        switch (mState) {
            case STATE_PREVIEW: {
                // We have nothing to do when the camera preview is working normally.
                if (CaptureResult.CONTROL_AF_TRIGGER_START == afState) {
                    if (areWeFocused) {
                        //Run specific task here
                        counter++;
                        takePicture();
                        Log.d("FocussedState","I am focussed now");
                    }
                }
                if (CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState) {
                    areWeFocused = true;
                } else {
                    areWeFocused = false;
                    Log.d("FocussedState","I am not focussed now");
                }
                break;
            }
        }
    }

    private void takePicture() {
        lockFocus();
    }

    /**
     * Lock the focus as the first step for a still image capture.
     */
    private void lockFocus() {
        try {
            // This is how to tell the camera to lock focus.
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
            // Tell #mCaptureCallback to wait for the lock.
            mState = STATE_WAITING_LOCK;
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void captureStillPicture() {
        try {
            final Activity activity = getActivity();
            if (null == activity || null == mCameraDevice) {
                return;
            }
            // This is the CaptureRequest.Builder that we use to take a picture.
            final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());

            // Use the same AE and AF modes as the preview.
            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // setAutoFlash(captureBuilder);

            // Orientation
            int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

            CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    showToast("Saved: " + mFile);
                    Log.d(TAG, mFile.toString());
                    unlockFocus();
                }
            };
            mCaptureSession.stopRepeating();
            mCaptureSession.abortCaptures();
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private ImageReader mImageReader;

    /**
     * This is the output file for our picture.
     */
    private File mFile;

    /**
     * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
     * still image is ready to be saved.
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }
    };

    private static class ImageSaver implements Runnable {
        /**
         * The JPEG image
         */
        private final Image mImage;
        /**
         * The file we save the image into.
         */
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * Compares two {@code Size}s based on their areas.
     */
    static class CompareSizesByArea implements Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
        }
    }
Solution

Hey there! That preview stutter happens because your current workflow stops the repeating preview request and aborts ongoing captures when taking a picture—this breaks the continuous preview stream. Camera2 actually supports capturing stills without interrupting the preview if you structure your session and requests correctly. Let's fix this step by step:

Key Issues in Your Current Code

  • captureStillPicture() calls mCaptureSession.stopRepeating() and mCaptureSession.abortCaptures(): These pause the preview stream, causing the stutter.
  • You're creating a new CaptureCallback for still captures unnecessarily, which can complicate state management.

Fixed Implementation

Here's how to modify your code to keep preview running smoothly while capturing stills:

1. Update Session Initialization (Ensure Both Surfaces Are Added)

When creating your CameraCaptureSession, make sure you include both the preview Surface and the ImageReader's Surface in the list of outputs. This lets the camera send frames to both targets simultaneously:

// When setting up the session (from the Camera2 sample's openCamera method)
List<Surface> surfaces = new ArrayList<>();
surfaces.add(mPreviewSurface); // Your preview's SurfaceTexture/Surface
surfaces.add(mImageReader.getSurface());
mCameraDevice.createCaptureSession(surfaces, mSessionCallback, mBackgroundHandler);

2. Rewrite captureStillPicture() to Avoid Stopping Preview

Remove the calls to stopRepeating() and abortCaptures(). Instead, send the still capture request while the preview's repeating request is still active:

private void captureStillPicture() {
    try {
        final Activity activity = getActivity();
        if (null == activity || null == mCameraDevice || null == mCaptureSession) {
            return;
        }

        final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(mImageReader.getSurface());

        // Reuse AF mode from preview, but maintain focus lock
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);

        // Set orientation
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

        // Use existing callback to avoid redundant object creation
        mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

3. Adjust lockFocus() and State Management

Modify your lock focus logic to trigger capture without switching states that interrupt preview:

private void lockFocus() {
    try {
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
        mState = STATE_WAITING_LOCK;
        // Send AF trigger while keeping preview running
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

// Update the process() method in mCaptureCallback
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
    switch (mState) {
        case STATE_WAITING_LOCK: {
            Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
            if (afState == CaptureResult.CONTROL_AF_STATE_LOCKED_FOCUSED) {
                // Focus locked—trigger capture immediately
                captureStillPicture();
                // Reset state to keep preview streaming
                mState = STATE_PREVIEW;
            } else {
                // Focus failed, revert to continuous AF
                unlockFocus();
            }
            break;
        }
        // Keep other state cases as needed
    }
}

4. Simplify unlockFocus() (No Need to Restart Repeating)

Since we never stopped the preview repeating request, unlock focus just resets the AF trigger:

private void unlockFocus() {
    try {
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
        mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
        mState = STATE_PREVIEW;
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

Why This Works

  • Camera2's CaptureSession handles multiple requests in parallel: the repeating preview runs continuously, and the single still capture is processed alongside it without pausing the stream.
  • Registering both surfaces in the session lets the camera hardware efficiently route frames to both preview and ImageReader without extra overhead.
  • Removing stopRepeating() and abortCaptures() eliminates the gap in the preview stream that caused the stutter.

Additional Tips

  • Use a dedicated HandlerThread for all camera operations (your mBackgroundHandler) to keep the UI thread free.
  • Ensure your ImageReader uses a size compatible with the preview size (check camera capabilities to avoid scaling overhead).
  • Test on different devices—older hardware may have limitations, but most modern Android devices support this parallel capture workflow.

内容的提问来源于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 07:58:47