From bc3c72cdc8d67579426e031db3382131d4c3b94f Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 25 Jul 2019 13:38:04 +0200 Subject: [PATCH] Fix Camera2 picture recorder (#521) * Fix Camera2 picture recorder * Improve logic, check focusLock when releasing --- .../cameraview/engine/Camera2Engine.java | 4 +- .../picture/Full2PictureRecorder.java | 132 ++++++++++++------ 2 files changed, 92 insertions(+), 44 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 392529e1..24c36554 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -629,7 +629,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv try { CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); applyAllParameters(builder); - mPictureRecorder = new Full2PictureRecorder(stub, this, mSession, + mPictureRecorder = new Full2PictureRecorder(stub, this, + mCameraCharacteristics, + mSession, mRepeatingRequestBuilder, mRepeatingRequestCallback, builder, diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java index d7a9ecaf..96a033a2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java @@ -1,8 +1,8 @@ package com.otaliastudios.cameraview.picture; -import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; @@ -10,9 +10,7 @@ import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; import android.os.Build; -import android.view.Surface; -import com.otaliastudios.cameraview.CameraException; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.internal.utils.ExifHelper; @@ -46,6 +44,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader private static final int REQUEST_TAG = CameraDevice.TEMPLATE_STILL_CAPTURE; private CameraCaptureSession mSession; + private CameraCharacteristics mCharacteristics; private CaptureRequest.Builder mBuilder; private CameraCaptureSession.CaptureCallback mCallback; private ImageReader mPictureReader; @@ -55,6 +54,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader public Full2PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener, + @NonNull CameraCharacteristics characteristics, @NonNull CameraCaptureSession session, @NonNull CaptureRequest.Builder builder, @NonNull CameraCaptureSession.CaptureCallback callback, @@ -62,6 +62,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader @NonNull ImageReader pictureReader, boolean stopPreviewBeforeCapture) { super(stub, listener); + mCharacteristics = characteristics; mSession = session; mBuilder = builder; mCallback = callback; @@ -76,34 +77,72 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader runFocusLock(); } + private boolean supportsFocusLock() { + //noinspection ConstantConditions + int afMode = mBuilder.get(CaptureRequest.CONTROL_AF_MODE); + // Exclude OFF and EDOF as per their docs. + return afMode == CameraCharacteristics.CONTROL_AF_MODE_AUTO + || afMode == CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE + || afMode == CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_VIDEO + || afMode == CameraCharacteristics.CONTROL_AF_MODE_MACRO; + } + private void runFocusLock() { - try { - mState = STATE_WAITING_FOCUS_LOCK; - mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_START); - mSession.capture(mBuilder.build(), mCallback, null); - } catch (CameraAccessException e) { - mResult = null; - mError = e; - dispatchResult(); + if (supportsFocusLock()) { + try { + mState = STATE_WAITING_FOCUS_LOCK; + mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + mSession.capture(mBuilder.build(), mCallback, null); + } catch (CameraAccessException e) { + mResult = null; + mError = e; + dispatchResult(); + } + } else { + LOG.w("Device does not support focus lock. Running precapture."); + runPrecapture(null); } } - private void runPrecapture() { - try { - mState = STATE_WAITING_PRECAPTURE_START; - mBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); - mSession.capture(mBuilder.build(), mCallback, null); - } catch (CameraAccessException e) { - mResult = null; - mError = e; - dispatchResult(); + @SuppressWarnings("ConstantConditions") + private boolean supportsPrecapture() { + // Precapture is not supported on legacy devices. + int level = mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) return false; + // We still have to check the current AE mode, see CaptureResult.CONTROL_AE_STATE. + int aeMode = mBuilder.get(CaptureRequest.CONTROL_AE_MODE); + return aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH + || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE + || aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */; + } + + private void runPrecapture(@Nullable CaptureResult lastResult) { + //noinspection ConstantConditions + boolean shouldSkipPrecapture = lastResult != null + && lastResult.get(CaptureResult.CONTROL_AE_STATE) != null + && lastResult.get(CaptureResult.CONTROL_AE_STATE) == CaptureResult.CONTROL_AE_STATE_CONVERGED; + if (supportsPrecapture() && !shouldSkipPrecapture) { + try { + mState = STATE_WAITING_PRECAPTURE_START; + mBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + mSession.capture(mBuilder.build(), mCallback, null); + } catch (CameraAccessException e) { + mResult = null; + mError = e; + dispatchResult(); + } + } else { + LOG.w("Device does not support precapture. Running capture."); + runCapture(); } } private void runCapture() { try { + mState = STATE_WAITING_CAPTURE; mPictureBuilder.setTag(REQUEST_TAG); mPictureBuilder.addTarget(mPictureReader.getSurface()); mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation); @@ -143,26 +182,21 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader case STATE_IDLE: break; case STATE_WAITING_FOCUS_LOCK: { Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); - if (afState == null) { - runCapture(); - } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED + if (afState == null + || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { - // CONTROL_AE_STATE can be null on some devices - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { - mState = STATE_WAITING_CAPTURE; - runCapture(); - } else { - runPrecapture(); - } + runPrecapture(result); } break; } case STATE_WAITING_PRECAPTURE_START: { Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - if (aeState == null || - aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || - aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { + if (aeState == null + || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE + // The one above is a transient state, which means it might not be reported + // by the camera. So in addition let's also check for the precature end states. + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { mState = STATE_WAITING_PRECAPTURE_END; } break; @@ -170,8 +204,15 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader case STATE_WAITING_PRECAPTURE_END: { Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null - || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { - mState = STATE_WAITING_CAPTURE; + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED + // The two above are the correct states. However, just for safety, and + // since we got STATE_WAITING_PRECAPTURE_START already, let's accept anything + // other than the precapturing state. We don't want to get stuck here. + || aeState == CaptureRequest.CONTROL_AE_STATE_SEARCHING // Camera is in normal AE routine. Should never happen. + || aeState == CaptureRequest.CONTROL_AE_STATE_INACTIVE // AE is OFF. Should never happen. + || aeState == CaptureResult.CONTROL_AE_STATE_LOCKED // AE has been locked. Should never happen. + ) { runCapture(); } break; @@ -188,6 +229,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader @Override public void onImageAvailable(ImageReader reader) { + LOG.i("onImageAvailable started."); mState = STATE_IDLE; // Read the JPEG. @@ -219,13 +261,17 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader } catch (IOException ignore) { } // Before leaving, unlock focus. - try { - mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); - mSession.capture(mBuilder.build(), mCallback, null); - } catch (CameraAccessException ignore) { } + if (supportsFocusLock()) { + try { + mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + mSession.capture(mBuilder.build(), mCallback, null); + } catch (CameraAccessException ignore) { + } + } // Leave. + LOG.i("onImageAvailable ended."); dispatchResult(); }