From 226b9bbacade6c2d60dc70393055437879e2c5ce Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 1 Sep 2019 19:44:39 +0200 Subject: [PATCH] Simplify logic --- .../cameraview/engine/Camera2Engine.java | 38 ++-- .../cameraview/engine/Locker.java | 19 +- .../picture/Snapshot2PictureRecorder.java | 65 ++++-- .../picture/Snapshot2PictureRecorderOld.java | 198 ------------------ 4 files changed, 72 insertions(+), 248 deletions(-) delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorderOld.java 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 5433c589..4d6d52a2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -105,7 +105,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // 3A metering private Meter mMeter; - private boolean mMeteringNeedsFlash; private Gesture mMeteringGesture; private Locker mLocker; private PictureResult.Stub mDelayedPictureStub; @@ -644,10 +643,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv mPictureRecorder = new Snapshot2PictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, + mCameraCharacteristics, mSession, mRepeatingRequestCallback, mRepeatingRequestBuilder, - getPictureSnapshotMetering() && mMeteringNeedsFlash); + mLastRepeatingResult); mPictureRecorder.take(); } } @@ -1273,11 +1273,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv */ @Override public void onMeteringEnd(@Nullable PointF point, boolean success) { - Integer aeState = mLastRepeatingResult.get(CaptureResult.CONTROL_AE_STATE); - mMeteringNeedsFlash = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED; LOG.w("onMeteringEnd - point:", point, "gesture:", mMeteringGesture, - "needsFlash:", mMeteringNeedsFlash, "success:", success); if (point != null) { mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point); @@ -1286,8 +1283,14 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv mHandler.post(getAutoFocusResetDelay(), mMeteringResetRunnable); } } else { - // Continue with picture capturing. - lockMetering(null); + LOG.w("onMeteringEnd - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot); + if (mDelayedPictureStub.isSnapshot) { + onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false); + } else { + onTakePicture(mDelayedPictureStub, false); + } + mDelayedPictureStub = null; + mDelayedPictureRatio = null; } } @@ -1321,6 +1324,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv //region 3A Locking + // TODO this might become public API private void unlockMetering() { if (getEngineState() == STATE_STARTED) { mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); @@ -1330,27 +1334,19 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv } } - private void lockMetering(@Nullable PointF point) { + // TODO this might become public API + @SuppressWarnings("unused") + private void lockMetering() { if (getPreviewState() == STATE_STARTED) { mLocker = new Locker(mCameraCharacteristics, this); - mLocker.lock(mLastRepeatingResult, point); + mLocker.lock(mLastRepeatingResult); } } @Override - public void onLocked(@Nullable PointF point, boolean success) { - LOG.w("onLocked - point:", point, "gesture:", mMeteringGesture, "success:", success); + public void onLocked(boolean success) { + LOG.w("onLocked - success:", success); mLocker = null; - if (point == null) { - LOG.w("onLocked - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot); - if (mDelayedPictureStub.isSnapshot) { - onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false); - } else { - onTakePicture(mDelayedPictureStub, false); - } - mDelayedPictureStub = null; - mDelayedPictureRatio = null; - } } @NonNull diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java index 03e8eb78..cbc17255 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java @@ -1,6 +1,5 @@ package com.otaliastudios.cameraview.engine; -import android.graphics.PointF; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; @@ -8,7 +7,6 @@ import android.hardware.camera2.TotalCaptureResult; import android.os.Build; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.otaliastudios.cameraview.CameraLogger; @@ -21,7 +19,7 @@ import com.otaliastudios.cameraview.engine.locking.Parameter; * Helps Camera2-based engines to perform 3A locking and unlocking. * Users are required to: * - * - Call {@link #lock(CaptureResult, PointF)} to start + * - Call {@link #lock(CaptureResult)} to start * - Call {@link #onCapture(CaptureResult)} when they have partial or total results, as long as the * locker is still in a locking operation, which can be checked through {@link #isLocking()} */ @@ -36,10 +34,9 @@ public class Locker { /** * Notifies that locking has ended. No action is required for implementors. * From now on, {@link #isLocking()} will return false. - * @param point a point * @param success success */ - void onLocked(@Nullable PointF point, boolean success); + void onLocked(boolean success); /** * Returns the currently used builder. This can change while a locking @@ -59,7 +56,6 @@ public class Locker { private final CameraCharacteristics mCharacteristics; private final Callback mCallback; - private PointF mPoint; private boolean mIsLocking; private Parameter mAutoFocus; private Parameter mAutoWhiteBalance; @@ -71,7 +67,6 @@ public class Locker { * @param characteristics the camera characteristics * @param callback the callback */ - @SuppressWarnings("WeakerAccess") public Locker(@NonNull CameraCharacteristics characteristics, @NonNull Callback callback) { mCharacteristics = characteristics; @@ -84,11 +79,9 @@ public class Locker { /** * Locks 3A values. * @param lastResult the last result - * @param point a point */ - public void lock(@NonNull CaptureResult lastResult, @Nullable PointF point) { + public void lock(@NonNull CaptureResult lastResult) { mIsLocking = true; - mPoint = point; mLockingStartTime = System.currentTimeMillis(); mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); mAutoWhiteBalance.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); @@ -97,7 +90,7 @@ public class Locker { /** * True if we're locking. False if we're not, for example - * if {@link #lock(CaptureResult, PointF)} was never called. + * if {@link #lock(CaptureResult)} was never called. * @return true if locking */ @SuppressWarnings("WeakerAccess") @@ -123,11 +116,11 @@ public class Locker { boolean success = mAutoFocus.isSuccessful() && mAutoExposure.isSuccessful() && mAutoWhiteBalance.isSuccessful(); - mCallback.onLocked(mPoint, success); + mCallback.onLocked(success); mIsLocking = false; } else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) { LOG.i("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end."); - mCallback.onLocked(mPoint, false); + mCallback.onLocked(false); mIsLocking = false; } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java index 1d124b32..fcedc7ca 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java @@ -3,6 +3,7 @@ package com.otaliastudios.cameraview.picture; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; @@ -14,6 +15,7 @@ import androidx.annotation.RequiresApi; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.engine.Camera2Engine; +import com.otaliastudios.cameraview.engine.Locker; import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.size.AspectRatio; @@ -35,49 +37,62 @@ import com.otaliastudios.cameraview.size.AspectRatio; * I'd still love to use the capture intent instead of this, but was not able yet. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder { +public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder implements Locker.Callback { private final static String TAG = Snapshot2PictureRecorder.class.getSimpleName(); private final static CameraLogger LOG = CameraLogger.create(TAG); private final static int STATE_IDLE = 0; - private final static int STATE_WAITING_FIRST_FRAME = 1; + private final static int STATE_WAITING_LOCKER = 1; private final static int STATE_WAITING_TORCH = 2; private final static int STATE_WAITING_IMAGE = 3; private final CameraCaptureSession mSession; private final CameraCaptureSession.CaptureCallback mCallback; private final CaptureRequest.Builder mBuilder; + private final CameraCharacteristics mCharacteristics; + private final CaptureResult mFirstResult; + private final boolean mActionNeeded; + private int mState = STATE_IDLE; private Integer mOriginalAeMode; private Integer mOriginalFlashMode; - private boolean mNeedsFlash; + private Locker mLocker; public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub, @NonNull Camera2Engine engine, @NonNull GlCameraPreview preview, @NonNull AspectRatio outputRatio, + @NonNull CameraCharacteristics characteristics, @NonNull CameraCaptureSession session, @NonNull CameraCaptureSession.CaptureCallback callback, @NonNull CaptureRequest.Builder builder, - boolean needsFlash) { + @NonNull CaptureResult lastResult) { super(stub, engine, preview, outputRatio); mSession = session; mCallback = callback; mBuilder = builder; - mNeedsFlash = needsFlash; + mCharacteristics = characteristics; + mFirstResult = lastResult; + + Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); + mActionNeeded = engine.getPictureSnapshotMetering() + && aeState != null + && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED; } @Override public void take() { - if (!mNeedsFlash) { + if (!mActionNeeded) { LOG.i("take:", "Engine does no metering or needs no flash, taking fast snapshot."); super.take(); } else { - LOG.i("take:", "Engine needs flash."); - mState = STATE_WAITING_FIRST_FRAME; + LOG.i("take:", "Engine needs flash. Locking parameters"); + mState = STATE_WAITING_LOCKER; mOriginalAeMode = mBuilder.get(CaptureRequest.CONTROL_AE_MODE); mOriginalFlashMode = mBuilder.get(CaptureRequest.FLASH_MODE); + mLocker = new Locker(mCharacteristics, this); + mLocker.lock(mFirstResult); } } @@ -91,15 +106,33 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder { } } + @NonNull + @Override + public CaptureRequest.Builder getLockingBuilder() { + return mBuilder; + } + + @Override + public void onLockingChange() { + applyBuilder(); + } + + @Override + public void onLocked(boolean success) { + LOG.i("onLocked:", "Parameters locked, opening torch."); + mState = STATE_WAITING_TORCH; + mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); + mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + applyBuilder(); + } + public void onCaptureCompleted(@NonNull TotalCaptureResult result) { - if (mState == STATE_WAITING_FIRST_FRAME) { - LOG.i("onCaptureCompleted:", "Flash needed, opening torch."); - mState = STATE_WAITING_TORCH; - mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); - mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - applyBuilder(); - - } else if (mState == STATE_WAITING_TORCH) { + if (mState == STATE_WAITING_LOCKER) { + mLocker.onCapture(result); + return; + } + + if (mState == STATE_WAITING_TORCH) { Integer flashState = result.get(CaptureResult.FLASH_STATE); if (flashState == null) { LOG.w("onCaptureCompleted:", "Waiting flash, but flashState is null! Taking snapshot."); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorderOld.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorderOld.java deleted file mode 100644 index 5c3e9ebc..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorderOld.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.otaliastudios.cameraview.picture; - -import android.graphics.SurfaceTexture; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCaptureSession; -import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.TotalCaptureResult; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.otaliastudios.cameraview.CameraLogger; -import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.engine.Camera2Engine; -import com.otaliastudios.cameraview.preview.GlCameraPreview; -import com.otaliastudios.cameraview.size.AspectRatio; - -/** - * Wraps {@link SnapshotGlPictureRecorder} for Camera2. - * - * Camera2 engine supports metering for snapshots and we expect for them to correctly fire flash as well. - * The first idea, and in theory, the most correct one, was to set {@link CaptureRequest#CONTROL_CAPTURE_INTENT} - * to {@link CaptureRequest#CONTROL_CAPTURE_INTENT_STILL_CAPTURE}. - * - * According to documentation, this will automatically trigger the flash if parameters says so. - * In fact this is what happens, but it is a very fast flash that only lasts for 1 or 2 frames. - * It's not easy to call super.take() at the exact time so that we capture the frame that was lit. - * I have tried by comparing {@link SurfaceTexture#getTimestamp()} and {@link CaptureResult#SENSOR_TIMESTAMP} - * to identify the correct frame. These timestamps match, but the frame is not the correct one. - * - * So what we do here is ignore the {@link CaptureRequest#CONTROL_CAPTURE_INTENT} and instead open the - * torch, if requested to do so. Then wait for exposure to settle again and finally take a snapshot. - * I'd still love to use the capture intent instead of this, but was not able yet. - */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class Snapshot2PictureRecorderOld extends SnapshotGlPictureRecorder { - - private final static String TAG = Snapshot2PictureRecorderOld.class.getSimpleName(); - private final static CameraLogger LOG = CameraLogger.create(TAG); - - private final static int STATE_IDLE = 0; - private final static int STATE_WAITING_FIRST_FRAME = 1; - private final static int STATE_WAITING_LOCK = 2; - private final static int STATE_WAITING_TORCH = 3; - private final static int STATE_WAITING_IMAGE = 4; - - private final Camera2Engine mEngine; - private final CameraCaptureSession mSession; - private final CameraCaptureSession.CaptureCallback mCallback; - private final CaptureRequest.Builder mBuilder; - private int mState = STATE_IDLE; - private Integer mOriginalAeMode; - private Integer mOriginalFlashMode; - private Integer mOriginalAfMode; - private boolean mNeedsFlash; - - public Snapshot2PictureRecorderOld(@NonNull PictureResult.Stub stub, - @NonNull Camera2Engine engine, - @NonNull GlCameraPreview preview, - @NonNull AspectRatio outputRatio, - @NonNull CameraCaptureSession session, - @NonNull CameraCaptureSession.CaptureCallback callback, - @NonNull CaptureRequest.Builder builder) { - super(stub, engine, preview, outputRatio); - mEngine = engine; - mSession = session; - mCallback = callback; - mBuilder = builder; - } - - @Override - public void take() { - if (!mEngine.getPictureSnapshotMetering()) { - LOG.i("take:", "Engine does no metering, taking fast snapshot."); - super.take(); - } else { - LOG.i("take:", "Engine does metering, caring about flash."); - mState = STATE_WAITING_FIRST_FRAME; - mOriginalAeMode = mBuilder.get(CaptureRequest.CONTROL_AE_MODE); - mOriginalFlashMode = mBuilder.get(CaptureRequest.FLASH_MODE); - mOriginalAfMode = mBuilder.get(CaptureRequest.CONTROL_AF_MODE); - } - } - - private void applyBuilder() { - try { - mSession.setRepeatingRequest(mBuilder.build(), mCallback, null); - } catch (CameraAccessException e) { - mResult = null; - mError = e; - dispatchResult(); - } - } - - /** - * This works by inspecting {@link CaptureResult#CONTROL_AE_STATE}, and specifically - * the {@link CaptureRequest#CONTROL_AE_STATE_FLASH_REQUIRED} value. - * The AE state will have this value iff: - * 1. flash is required - * 2. device has a flash - * 3. we are not in Flash.OFF - * So when we see this value we just have to open the torch and wait a bit more, - * just to have a correct exposure. - * - * @param result a total result - */ - public void onCaptureCompleted(@NonNull TotalCaptureResult result) { - if (mState == STATE_WAITING_FIRST_FRAME) { - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - mNeedsFlash = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED; - LOG.i("onCaptureCompleted:", "aeState:", aeState, "needsFlash:", mNeedsFlash); - mState = STATE_WAITING_LOCK; - - // Removing any ongoing precapture or trigger. This should not be needed. - if (Build.VERSION.SDK_INT >= 23) { - mBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL); - } - mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); - - // Lock AE, AWB, AF to their current values - mBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); - mBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true); - mBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); - applyBuilder(); - return; - } - - if (mState == STATE_WAITING_LOCK) { - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); - boolean aeLocked = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED; - boolean awbLocked = awbState != null && awbState == CaptureResult.CONTROL_AWB_STATE_LOCKED; - if (aeLocked && awbLocked) { - if (mNeedsFlash) { - LOG.i("onCaptureCompleted:", "AE/AWB locked!", "Flash needed, opening torch."); - mState = STATE_WAITING_TORCH; - mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); - mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - applyBuilder(); - } else { - LOG.i("onCaptureCompleted:", "AE/AWB locked!", "No flash needed, taking picture."); - mState = STATE_WAITING_IMAGE; - super.take(); - } - } else { - LOG.i("onCaptureCompleted:", "Waiting for AE/AWB locks...", - "aeState:", aeState, "aeLocked:", aeLocked, - "awbState:", awbState, "awbLocked:", awbLocked); - } - return; - } - - - if (mState == STATE_WAITING_TORCH) { - Integer flashState = result.get(CaptureResult.FLASH_STATE); - if (flashState == null) { - LOG.w("onCaptureCompleted:", "Waiting flash, but flashState is null! Taking snapshot."); - mState = STATE_WAITING_IMAGE; - super.take(); - } else if (flashState == CaptureResult.FLASH_STATE_FIRED) { - LOG.i("onCaptureCompleted:", "Waiting flash and we have FIRED state! Taking snapshot."); - mState = STATE_WAITING_IMAGE; - super.take(); - } else { - LOG.i("onCaptureCompleted:", "aeState is FLASH_REQUIRED but flashState is", - flashState, ". Waiting..."); - } - //noinspection UnnecessaryReturnStatement - return; - } - } - - @Override - protected void dispatchResult() { - if (mState == STATE_WAITING_IMAGE) { - // Revert our changes. - LOG.i("dispatchResult:", "Reverting the capture intent changes."); - try { - // See Camera2Engine.setFlash(). It's better to change these one by one. - mBuilder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode); - mSession.capture(mBuilder.build(), mCallback, null); - mBuilder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode); - mSession.capture(mBuilder.build(), mCallback, null); - // Revert locks. - mBuilder.set(CaptureRequest.CONTROL_AF_MODE, mOriginalAfMode); - mBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); - mBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, false); - mSession.setRepeatingRequest(mBuilder.build(), mCallback, null); - } catch (CameraAccessException ignore) {} - } - mState = STATE_IDLE; - super.dispatchResult(); - } -}