基于Camera2 API实现自动对焦锁定后无阻塞拍照的方案咨询
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()); } }
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()callsmCaptureSession.stopRepeating()andmCaptureSession.abortCaptures(): These pause the preview stream, causing the stutter.- You're creating a new
CaptureCallbackfor 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
CaptureSessionhandles 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()andabortCaptures()eliminates the gap in the preview stream that caused the stutter.
Additional Tips
- Use a dedicated
HandlerThreadfor all camera operations (yourmBackgroundHandler) to keep the UI thread free. - Ensure your
ImageReaderuses 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

