From 53d044e3401d03b88bb97b99105bea2b6614f70d Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Wed, 4 Sep 2019 01:13:11 +0200 Subject: [PATCH] Move Snapshot2PictureRecorder using actions, rewrite lock package --- .../cameraview/engine/Camera2Engine.java | 93 ++++------ .../cameraview/engine/Locker.java | 126 ------------- .../cameraview/engine/action/Action.java | 4 +- .../engine/action/ActionCallback.java | 4 + .../engine/action/ActionHolder.java | 3 + .../engine/action/ActionWrapper.java | 49 +++++ .../cameraview/engine/action/Actions.java | 7 +- .../cameraview/engine/action/BaseAction.java | 14 +- .../engine/action/CompletionCallback.java | 20 +++ .../AutoExposureLock.java} | 49 +++-- .../AutoFocusLock.java} | 51 +++--- .../engine/lock/AutoWhiteBalanceLock.java | 64 +++++++ .../cameraview/engine/lock/BaseLock.java | 40 +++++ .../cameraview/engine/lock/LockAction.java | 26 +++ .../cameraview/engine/lock/UnlockAction.java | 25 +++ .../engine/locking/AutoWhiteBalance.java | 73 -------- .../cameraview/engine/locking/Parameter.java | 88 --------- .../picture/Snapshot2PictureRecorder.java | 169 +++++++----------- .../cameraview/video/Full2VideoRecorder.java | 9 +- 19 files changed, 395 insertions(+), 519 deletions(-) delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionWrapper.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/CompletionCallback.java rename cameraview/src/main/java/com/otaliastudios/cameraview/engine/{locking/AutoExposure.java => lock/AutoExposureLock.java} (58%) rename cameraview/src/main/java/com/otaliastudios/cameraview/engine/{locking/AutoFocus.java => lock/AutoFocusLock.java} (56%) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoWhiteBalanceLock.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/BaseLock.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/LockAction.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/UnlockAction.java delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.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 01deabae..dab96e3b 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -44,6 +44,7 @@ import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.engine.action.Action; import com.otaliastudios.cameraview.engine.action.ActionHolder; +import com.otaliastudios.cameraview.engine.lock.UnlockAction; import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper; import com.otaliastudios.cameraview.engine.offset.Axis; import com.otaliastudios.cameraview.engine.offset.Reference; @@ -70,8 +71,7 @@ import java.util.concurrent.ExecutionException; @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAvailableListener, ActionHolder, - Meter.Callback, - Locker.Callback { + Meter.Callback { private static final String TAG = Camera2Engine.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); @@ -110,7 +110,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // 3A metering private Meter mMeter; private Gesture mMeteringGesture; - private Locker mLocker; private PictureResult.Stub mDelayedPictureStub; private AspectRatio mDelayedPictureRatio; @@ -245,39 +244,38 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv } } - private final CameraCaptureSession.CaptureCallback mRepeatingRequestCallback = new CameraCaptureSession.CaptureCallback() { + private final CameraCaptureSession.CaptureCallback mRepeatingRequestCallback = + new CameraCaptureSession.CaptureCallback() { @Override - public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { + public void onCaptureStarted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + long timestamp, + long frameNumber) { for (Action action : mActions) { action.onCaptureStarted(Camera2Engine.this, request); } } @Override - public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { + public void onCaptureProgressed(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { if (mMeter != null && mMeter.isMetering()) { mMeter.onCapture(partialResult); } - if (mLocker != null && mLocker.isLocking()) { - mLocker.onCapture(partialResult); - } for (Action action : mActions) { action.onCaptureProgressed(Camera2Engine.this, request, partialResult); } } @Override - public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + public void onCaptureCompleted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { mLastRepeatingResult = result; - if (mPictureRecorder instanceof Snapshot2PictureRecorder) { - ((Snapshot2PictureRecorder) mPictureRecorder).onCaptureCompleted(result); - } if (mMeter != null && mMeter.isMetering()) { mMeter.onCapture(result); } - if (mLocker != null && mLocker.isLocking()) { - mLocker.onCapture(result); - } for (Action action : mActions) { action.onCaptureProgressed(Camera2Engine.this, request, result); } @@ -294,10 +292,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv try { CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); StreamConfigurationMap streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (streamMap == null) + if (streamMap == null) { throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); - // This works because our previews return either a SurfaceTexture or a SurfaceHolder, which are - // accepted class types by the getOutputSizes method. + } + // This works because our previews return either a SurfaceTexture or a SurfaceHolder, + // which are accepted class types by the getOutputSizes method. android.util.Size[] sizes = streamMap.getOutputSizes(mPreview.getOutputClass()); List candidates = new ArrayList<>(sizes.length); for (android.util.Size size : sizes) { @@ -327,13 +326,16 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // However, let's launch an unrecoverable exception. throw createCameraException(e); } - LOG.i("collectCameraInfo", "Facing:", facing, "Internal:", internalFacing, "Cameras:", cameraIds.length); + LOG.i("collectCameraInfo", "Facing:", facing, + "Internal:", internalFacing, + "Cameras:", cameraIds.length); for (String cameraId : cameraIds) { try { CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId); if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) { mCameraId = cameraId; - int sensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0); + int sensorOffset = readCharacteristic(characteristics, + CameraCharacteristics.SENSOR_ORIENTATION, 0); getAngles().setSensorOffset(facing, sensorOffset); return true; } @@ -381,8 +383,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv // However, using trySetException should address this problem - it will only trigger // if the task has no result. // - // Docs say to release this camera instance, however, since we throw an unrecoverable CameraException, - // this will trigger a stop() through the exception handler. + // Docs say to release this camera instance, however, since we throw an unrecoverable + // CameraException, this will trigger a stop() through the exception handler. task.trySetException(new CameraException(CameraException.REASON_DISCONNECTED)); } @@ -651,16 +653,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv } else { LOG.i("onTakePictureSnapshot:", "doMetering is false. Performing."); if (!(mPreview instanceof GlCameraPreview)) { - throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE"); + throw new RuntimeException("takePictureSnapshot with Camera2 is only " + + "supported with Preview.GL_SURFACE"); } mPictureRecorder = new Snapshot2PictureRecorder(stub, this, - (GlCameraPreview) mPreview, - outputRatio, - mCameraCharacteristics, - mSession, - mRepeatingRequestCallback, - mRepeatingRequestBuilder, - mLastRepeatingResult); + (GlCameraPreview) mPreview, outputRatio); mPictureRecorder.take(); } } @@ -1343,42 +1340,14 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv //region 3A Locking // TODO this might become public API + // TODO add lockMetering private void unlockMetering() { if (getEngineState() == STATE_STARTED) { - mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); - mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, false); - // TODO applyDefaultFocus(mRepeatingRequestBuilder); - applyRepeatingRequestBuilder(); // only if preview started + applyDefaultFocus(mRepeatingRequestBuilder); + new UnlockAction().start(this); } } - // TODO this might become public API - @SuppressWarnings("unused") - private void lockMetering() { - if (getPreviewState() == STATE_STARTED) { - mLocker = new Locker(mCameraCharacteristics, this); - mLocker.lock(mLastRepeatingResult); - } - } - - @Override - public void onLocked(boolean success) { - LOG.w("onLocked - success:", success); - mLocker = null; - } - - @NonNull - @Override - public CaptureRequest.Builder getLockingBuilder() { - return mRepeatingRequestBuilder; - } - - @Override - public void onLockingChange() { - LOG.i("onLockingChange:", "applying the builder."); - applyRepeatingRequestBuilder(); - } - //endregion //region Actions diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java deleted file mode 100644 index 46450cde..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.otaliastudios.cameraview.engine; - -import android.hardware.camera2.CameraCharacteristics; -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.engine.locking.AutoExposure; -import com.otaliastudios.cameraview.engine.locking.AutoFocus; -import com.otaliastudios.cameraview.engine.locking.AutoWhiteBalance; -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)} 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()} - */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class Locker { - - /** - * The locker callback. - */ - public interface Callback extends Parameter.LockingChangeCallback { - - /** - * Notifies that locking has ended. No action is required for implementors. - * From now on, {@link #isLocking()} will return false. - * @param success success - */ - void onLocked(boolean success); - - /** - * Returns the currently used builder. This can change while a locking - * operation happens, so the locker will never cache this value. - * It is the engine responsibility to copy over values to the new builder - * when it changes. - * @return a builder - */ - @NonNull - CaptureRequest.Builder getLockingBuilder(); - } - - private static final String TAG = Locker.class.getSimpleName(); - private static final CameraLogger LOG = CameraLogger.create(TAG); - private static final int FORCED_END_DELAY = 2500; - - private final CameraCharacteristics mCharacteristics; - private final Callback mCallback; - - private boolean mIsLocking; - private Parameter mAutoFocus; - private Parameter mAutoWhiteBalance; - private Parameter mAutoExposure; - private long mLockingStartTime; - - /** - * Creates a new locker. - * @param characteristics the camera characteristics - * @param callback the callback - */ - public Locker(@NonNull CameraCharacteristics characteristics, - @NonNull Callback callback) { - mCharacteristics = characteristics; - mCallback = callback; - mAutoFocus = new AutoFocus(callback); - mAutoExposure = new AutoExposure(callback); - mAutoWhiteBalance = new AutoWhiteBalance(callback); - } - - /** - * Locks 3A values. - * @param lastResult the last result - */ - public void lock(@NonNull CaptureResult lastResult) { - mIsLocking = true; - mLockingStartTime = System.currentTimeMillis(); - // TODO mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); - // TODO mAutoWhiteBalance.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); - mAutoExposure.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult); - } - - /** - * True if we're locking. False if we're not, for example - * if {@link #lock(CaptureResult)} was never called. - * @return true if locking - */ - @SuppressWarnings("WeakerAccess") - public boolean isLocking() { - return mIsLocking; - } - - /** - * Should be called when we have partial or total CaptureResults, - * but only while {@link #isLocking()} returns true. - * @param result result - */ - public void onCapture(@NonNull CaptureResult result) { - if (!mIsLocking) return; // We're not interested in results anymore - if (!(result instanceof TotalCaptureResult)) return; // Let's ignore these, contents are missing/wrong - - // TODO if (!mAutoFocus.isLocked()) mAutoFocus.onCapture(mCallback.getLockingBuilder(), result); - if (!mAutoExposure.isLocked()) mAutoExposure.onCapture(mCallback.getLockingBuilder(), result); - // TODO if (!mAutoWhiteBalance.isLocked()) mAutoWhiteBalance.onCapture(mCallback.getLockingBuilder(), result); - if (/* TODO mAutoFocus.isLocked() && */ mAutoExposure.isLocked() /* && mAutoWhiteBalance.isLocked() */) { - LOG.i("onCapture:", "all Parameters have converged. Dispatching onMeteringEnd"); - boolean success = /* TODO mAutoFocus.isSuccessful() - && */ mAutoExposure.isSuccessful() - /* TODO && mAutoWhiteBalance.isSuccessful() */; - mCallback.onLocked(success); - mIsLocking = false; - } else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) { - LOG.e("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end."); - mCallback.onLocked(false); - mIsLocking = false; - } - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Action.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Action.java index af69ad15..c51574ea 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Action.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Action.java @@ -3,12 +3,14 @@ package com.otaliastudios.cameraview.engine.action; 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; +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) public interface Action { - int STATE_CREATED = Integer.MIN_VALUE; int STATE_COMPLETED = Integer.MAX_VALUE; int getState(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionCallback.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionCallback.java index e28cc748..087f0b0a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionCallback.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionCallback.java @@ -1,7 +1,11 @@ package com.otaliastudios.cameraview.engine.action; +import android.os.Build; + import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) public interface ActionCallback { void onActionStateChanged(@NonNull Action action, int state); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionHolder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionHolder.java index aa65d900..f23c1910 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionHolder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionHolder.java @@ -5,9 +5,12 @@ import android.hardware.camera2.CameraCharacteristics; 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; +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) public interface ActionHolder { void addAction(@NonNull Action action); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionWrapper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionWrapper.java new file mode 100644 index 00000000..e6e1c030 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionWrapper.java @@ -0,0 +1,49 @@ +package com.otaliastudios.cameraview.engine.action; + +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; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public abstract class ActionWrapper extends BaseAction { + + @NonNull + public abstract BaseAction getAction(); + + @Override + protected void onStart(@NonNull ActionHolder holder) { + super.onStart(holder); + getAction().addCallback(new ActionCallback() { + @Override + public void onActionStateChanged(@NonNull Action action, int state) { + setState(state); + if (state == STATE_COMPLETED) { + action.removeCallback(this); + } + } + }); + getAction().onStart(holder); + } + + @Override + public void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request) { + super.onCaptureStarted(holder, request); + getAction().onCaptureStarted(holder, request); + } + + @Override + public void onCaptureProgressed(@NonNull ActionHolder holder, @NonNull CaptureRequest request, @NonNull CaptureResult result) { + super.onCaptureProgressed(holder, request, result); + getAction().onCaptureProgressed(holder, request, result); + } + + @Override + public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + super.onCaptureCompleted(holder, request, result); + getAction().onCaptureCompleted(holder, request, result); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Actions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Actions.java index cf81bf89..e052a60f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Actions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Actions.java @@ -3,21 +3,24 @@ package com.otaliastudios.cameraview.engine.action; 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 java.util.Arrays; import java.util.List; +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) public class Actions { @NonNull - public static Action together(@NonNull BaseAction... actions) { + public static BaseAction together(@NonNull BaseAction... actions) { return new TogetherAction(Arrays.asList(actions)); } @NonNull - public static Action sequence(@NonNull BaseAction... actions) { + public static BaseAction sequence(@NonNull BaseAction... actions) { return new SequenceAction(Arrays.asList(actions)); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/BaseAction.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/BaseAction.java index 2e475a71..025f1850 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/BaseAction.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/BaseAction.java @@ -3,12 +3,15 @@ package com.otaliastudios.cameraview.engine.action; 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 java.util.ArrayList; import java.util.List; +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) public abstract class BaseAction implements Action { private final List callbacks = new ArrayList<>(); @@ -16,7 +19,7 @@ public abstract class BaseAction implements Action { private ActionHolder holder; @Override - public int getState() { + public final int getState() { return state; } @@ -58,6 +61,15 @@ public abstract class BaseAction implements Action { } } + public boolean isCompleted() { + return state == STATE_COMPLETED; + } + + @NonNull + protected ActionHolder getHolder() { + return holder; + } + @Override public void addCallback(@NonNull ActionCallback callback) { if (!callbacks.contains(callback)) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/CompletionCallback.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/CompletionCallback.java new file mode 100644 index 00000000..a5ffab12 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/CompletionCallback.java @@ -0,0 +1,20 @@ +package com.otaliastudios.cameraview.engine.action; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public abstract class CompletionCallback implements ActionCallback { + + @Override + public void onActionStateChanged(@NonNull Action action, int state) { + if (state == Action.STATE_COMPLETED) { + onActionCompleted(action); + } + } + + @SuppressWarnings("WeakerAccess") + protected abstract void onActionCompleted(@NonNull Action action); +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoExposureLock.java similarity index 58% rename from cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoExposureLock.java index 12b386fe..a4b7ba56 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoExposureLock.java @@ -1,34 +1,30 @@ -package com.otaliastudios.cameraview.engine.locking; +package com.otaliastudios.cameraview.engine.lock; import android.hardware.camera2.CameraCharacteristics; 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.engine.action.ActionHolder; @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class AutoExposure extends Parameter { +public class AutoExposureLock extends BaseLock { - private static final String TAG = AutoExposure.class.getSimpleName(); - private static final CameraLogger LOG = CameraLogger.create(TAG + "Locking"); - - public AutoExposure(@NonNull LockingChangeCallback callback) { - super(callback); - } + private final static String TAG = AutoExposureLock.class.getSimpleName(); + private final static CameraLogger LOG = CameraLogger.create(TAG); @Override - protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { - boolean isNotLegacy = readCharacteristic(characteristics, - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + protected boolean checkIsSupported(@NonNull ActionHolder holder) { + boolean isNotLegacy = readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) + != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; // Not sure we should check aeMode as well, probably all aeModes support locking, // but this should not be a big issue since we're not even using different AE modes. - Integer aeMode = builder.get(CaptureRequest.CONTROL_AE_MODE); + Integer aeMode = holder.getBuilder(this).get(CaptureRequest.CONTROL_AE_MODE); boolean isAEOn = aeMode != null && (aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH @@ -36,37 +32,37 @@ public class AutoExposure extends Parameter { || aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE || aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */); boolean result = isNotLegacy && isAEOn; - LOG.i("checkSupportsProcessing:", result); + LOG.i("checkIsSupported:", result); return result; } @Override - protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { - Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); + protected boolean checkShouldSkip(@NonNull ActionHolder holder) { + Integer aeState = holder.getLastResult(this).get(CaptureResult.CONTROL_AE_STATE); boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED; LOG.i("checkShouldSkip:", result); return result; } @Override - protected void onLock(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { + protected void onStarted(@NonNull ActionHolder holder) { int cancelTrigger = Build.VERSION.SDK_INT >= 23 ? CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL : CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE; - builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, cancelTrigger); - builder.set(CaptureRequest.CONTROL_AE_LOCK, true); - notifyBuilderChanged(); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, cancelTrigger); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_LOCK, true); + holder.applyBuilder(this); } @Override - public void processCapture(@NonNull CaptureResult result) { + public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + super.onCaptureCompleted(holder, request, result); Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); LOG.i("processCapture:", "aeState:", aeState); if (aeState == null) return; switch (aeState) { case CaptureRequest.CONTROL_AE_STATE_LOCKED: { - notifyLocked(true); + setState(STATE_COMPLETED); break; } case CaptureRequest.CONTROL_AE_STATE_PRECAPTURE: @@ -79,9 +75,4 @@ public class AutoExposure extends Parameter { } } } - - @Override - protected void onLocked(@NonNull CaptureRequest.Builder builder, boolean success) { - // Do nothing - } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoFocusLock.java similarity index 56% rename from cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoFocusLock.java index c2311fa3..02747063 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoFocusLock.java @@ -1,32 +1,28 @@ -package com.otaliastudios.cameraview.engine.locking; +package com.otaliastudios.cameraview.engine.lock; import android.hardware.camera2.CameraCharacteristics; 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.engine.action.ActionHolder; @RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class AutoFocus extends Parameter { +public class AutoFocusLock extends BaseLock { - private static final String TAG = AutoFocus.class.getSimpleName(); - private static final CameraLogger LOG = CameraLogger.create(TAG + "Locking"); - - public AutoFocus(@NonNull LockingChangeCallback callback) { - super(callback); - } + private final static String TAG = AutoFocusLock.class.getSimpleName(); + private final static CameraLogger LOG = CameraLogger.create(TAG); @Override - protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { + protected boolean checkIsSupported(@NonNull ActionHolder holder) { // We'll lock by changing the AF mode to AUTO. // In that mode, AF won't change unless someone starts a trigger operation. - int[] modes = readCharacteristic(characteristics, - CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); + int[] modes = readCharacteristic(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{}); for (int mode : modes) { if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) { return true; @@ -36,14 +32,15 @@ public class AutoFocus extends Parameter { } @Override - protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { + protected boolean checkShouldSkip(@NonNull ActionHolder holder) { + CaptureResult lastResult = holder.getLastResult(this); Integer afState = lastResult.get(CaptureResult.CONTROL_AF_STATE); boolean afStateOk = afState != null && (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED - || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED - || afState == CaptureResult.CONTROL_AF_STATE_INACTIVE - || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED - || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED); + || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED + || afState == CaptureResult.CONTROL_AF_STATE_INACTIVE + || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED + || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED); Integer afMode = lastResult.get(CaptureResult.CONTROL_AF_MODE); boolean afModeOk = afMode != null && afMode == CaptureResult.CONTROL_AF_MODE_AUTO; boolean result = afStateOk && afModeOk; @@ -52,15 +49,15 @@ public class AutoFocus extends Parameter { } @Override - protected void onLock(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { - builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); - builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); - notifyBuilderChanged(); + protected void onStarted(@NonNull ActionHolder holder) { + holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + holder.applyBuilder(this); } @Override - public void processCapture(@NonNull CaptureResult result) { + public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + super.onCaptureCompleted(holder, request, result); Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE); LOG.i("onCapture:", "afState:", afState, "afMode:", afMode); @@ -72,7 +69,7 @@ public class AutoFocus extends Parameter { case CaptureRequest.CONTROL_AF_STATE_INACTIVE: case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED: case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { - notifyLocked(true); + setState(STATE_COMPLETED); break; } case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN: @@ -82,10 +79,4 @@ public class AutoFocus extends Parameter { } } } - - @Override - protected void onLocked(@NonNull CaptureRequest.Builder builder, boolean success) { - // Do nothing. - } - } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoWhiteBalanceLock.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoWhiteBalanceLock.java new file mode 100644 index 00000000..4acd2d92 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoWhiteBalanceLock.java @@ -0,0 +1,64 @@ +package com.otaliastudios.cameraview.engine.lock; + +import android.hardware.camera2.CameraCharacteristics; +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.engine.action.ActionHolder; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class AutoWhiteBalanceLock extends BaseLock { + + private final static String TAG = AutoWhiteBalanceLock.class.getSimpleName(); + private final static CameraLogger LOG = CameraLogger.create(TAG); + + @Override + protected boolean checkIsSupported(@NonNull ActionHolder holder) { + boolean isNotLegacy = readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) + != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + Integer awbMode = holder.getBuilder(this).get(CaptureRequest.CONTROL_AWB_MODE); + boolean result = isNotLegacy && awbMode != null && awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO; + LOG.i("checkIsSupported:", result); + return result; + } + + @Override + protected boolean checkShouldSkip(@NonNull ActionHolder holder) { + Integer awbState = holder.getLastResult(this).get(CaptureResult.CONTROL_AWB_STATE); + boolean result = awbState != null && awbState == CaptureRequest.CONTROL_AWB_STATE_LOCKED; + LOG.i("checkShouldSkip:", result); + return result; + } + + @Override + protected void onStarted(@NonNull ActionHolder holder) { + holder.getBuilder(this).set(CaptureRequest.CONTROL_AWB_LOCK, true); + holder.applyBuilder(this); + } + + @Override + public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + super.onCaptureCompleted(holder, request, result); + Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); + LOG.i("processCapture:", "awbState:", awbState); + if (awbState == null) return; + switch (awbState) { + case CaptureRequest.CONTROL_AWB_STATE_LOCKED: { + setState(STATE_COMPLETED); + break; + } + case CaptureRequest.CONTROL_AWB_STATE_CONVERGED: + case CaptureRequest.CONTROL_AWB_STATE_INACTIVE: + case CaptureRequest.CONTROL_AWB_STATE_SEARCHING: { + // Wait... + break; + } + } + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/BaseLock.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/BaseLock.java new file mode 100644 index 00000000..5790f11e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/BaseLock.java @@ -0,0 +1,40 @@ +package com.otaliastudios.cameraview.engine.lock; + +import android.hardware.camera2.CameraCharacteristics; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.engine.action.ActionHolder; +import com.otaliastudios.cameraview.engine.action.BaseAction; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public abstract class BaseLock extends BaseAction { + + @SuppressWarnings("WeakerAccess") + @NonNull + protected T readCharacteristic(@NonNull CameraCharacteristics.Key key, + @NonNull T fallback) { + T value = getHolder().getCharacteristics(this).get(key); + return value == null ? fallback : value; + } + + @Override + protected final void onStart(@NonNull ActionHolder holder) { + super.onStart(holder); + boolean isSkipped = checkShouldSkip(holder); + boolean isSupported = checkIsSupported(holder); + if (isSupported && !isSkipped) { + onStarted(holder); + } else { + setState(STATE_COMPLETED); + } + } + + protected abstract void onStarted(@NonNull ActionHolder holder); + + protected abstract boolean checkShouldSkip(@NonNull ActionHolder holder); + + protected abstract boolean checkIsSupported(@NonNull ActionHolder holder); +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/LockAction.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/LockAction.java new file mode 100644 index 00000000..dbf35e1f --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/LockAction.java @@ -0,0 +1,26 @@ +package com.otaliastudios.cameraview.engine.lock; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.engine.action.ActionWrapper; +import com.otaliastudios.cameraview.engine.action.Actions; +import com.otaliastudios.cameraview.engine.action.BaseAction; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class LockAction extends ActionWrapper { + + private final BaseAction action = Actions.together( + new AutoExposureLock(), + new AutoFocusLock(), + new AutoWhiteBalanceLock() + ); + + @NonNull + @Override + public BaseAction getAction() { + return action; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/UnlockAction.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/UnlockAction.java new file mode 100644 index 00000000..28941b06 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/UnlockAction.java @@ -0,0 +1,25 @@ +package com.otaliastudios.cameraview.engine.lock; + +import android.hardware.camera2.CaptureRequest; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.otaliastudios.cameraview.engine.action.ActionHolder; +import com.otaliastudios.cameraview.engine.action.BaseAction; + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +public class UnlockAction extends BaseAction { + + @Override + protected void onStart(@NonNull ActionHolder holder) { + super.onStart(holder); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_LOCK, false); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AWB_LOCK, false); + holder.applyBuilder(this); + setState(STATE_COMPLETED); + // TODO focus is managed by the engine + // TODO should wait results? + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java deleted file mode 100644 index 4946b49c..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.otaliastudios.cameraview.engine.locking; - -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.CaptureResult; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.otaliastudios.cameraview.CameraLogger; - -@RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class AutoWhiteBalance extends Parameter { - - private static final String TAG = AutoWhiteBalance.class.getSimpleName(); - private static final CameraLogger LOG = CameraLogger.create(TAG + "Locking"); - - public AutoWhiteBalance(@NonNull LockingChangeCallback callback) { - super(callback); - } - - @Override - protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { - boolean isNotLegacy = readCharacteristic(characteristics, - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) != - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; - Integer awbMode = builder.get(CaptureRequest.CONTROL_AWB_MODE); - boolean result = isNotLegacy && awbMode != null && awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO; - LOG.i("checkSupportsLocking:", result); - return result; - } - - @Override - protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { - Integer awbState = lastResult.get(CaptureResult.CONTROL_AWB_STATE); - boolean result = awbState != null && awbState == CaptureRequest.CONTROL_AWB_STATE_LOCKED; - LOG.i("checkShouldSkip:", result); - return result; - } - - @Override - protected void onLock(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder) { - builder.set(CaptureRequest.CONTROL_AWB_LOCK, true); - notifyBuilderChanged(); - } - - @Override - public void processCapture(@NonNull CaptureResult result) { - Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); - LOG.i("processCapture:", "awbState:", awbState); - if (awbState == null) return; - switch (awbState) { - case CaptureRequest.CONTROL_AWB_STATE_LOCKED: { - notifyLocked(true); - break; - } - case CaptureRequest.CONTROL_AWB_STATE_CONVERGED: - case CaptureRequest.CONTROL_AWB_STATE_INACTIVE: - case CaptureRequest.CONTROL_AWB_STATE_SEARCHING: { - // Wait... - break; - } - } - } - - @Override - protected void onLocked(@NonNull CaptureRequest.Builder builder, boolean success) { - // Do nothing - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java deleted file mode 100644 index a48af4c8..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.otaliastudios.cameraview.engine.locking; - -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.CaptureResult; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -@RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public abstract class Parameter { - - public interface LockingChangeCallback { - void onLockingChange(); - } - - private boolean isSuccessful; - private boolean isLocked; - private LockingChangeCallback callback; - private boolean shouldSkip; - private boolean supportsLocking; - - protected Parameter(@NonNull LockingChangeCallback callback) { - this.callback = callback; - } - - @SuppressWarnings("WeakerAccess") - @NonNull - protected T readCharacteristic(@NonNull CameraCharacteristics characteristics, - @NonNull CameraCharacteristics.Key key, - @NonNull T fallback) { - T value = characteristics.get(key); - return value == null ? fallback : value; - } - - @SuppressWarnings("WeakerAccess") - protected void notifyBuilderChanged() { - callback.onLockingChange(); - } - - @SuppressWarnings({"WeakerAccess", "SameParameterValue"}) - protected void notifyLocked(boolean success) { - isLocked = true; - isSuccessful = success; - } - - public final boolean isLocked() { - // A non supported parameter should always appear as metered - return isLocked || !supportsLocking || shouldSkip; - } - - public final boolean isSuccessful() { - // A non supported parameter should always appear as successful - return isSuccessful || !supportsLocking || shouldSkip; - } - - public final void lock(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder, - @NonNull CaptureResult lastResult) { - isSuccessful = false; - isLocked = false; - shouldSkip = checkShouldSkip(lastResult); - supportsLocking = checkSupportsLocking(characteristics, builder); - if (!shouldSkip && supportsLocking) { - onLock(characteristics, builder); - } - } - - public final void onCapture(@NonNull CaptureRequest.Builder builder, - @NonNull CaptureResult result) { - if (isLocked()) return; - processCapture(result); - if (isLocked()) onLocked(builder, isSuccessful); - } - - protected abstract boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder); - - protected abstract boolean checkShouldSkip(@NonNull CaptureResult lastResult); - - protected abstract void onLock(@NonNull CameraCharacteristics characteristics, - @NonNull CaptureRequest.Builder builder); - - protected abstract void processCapture(@NonNull CaptureResult result); - - protected abstract void onLocked(@NonNull CaptureRequest.Builder builder, boolean success); -} 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 290de7f8..5c4017fd 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java @@ -2,8 +2,6 @@ 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; @@ -15,7 +13,12 @@ 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.engine.action.Action; +import com.otaliastudios.cameraview.engine.action.ActionHolder; +import com.otaliastudios.cameraview.engine.action.Actions; +import com.otaliastudios.cameraview.engine.action.BaseAction; +import com.otaliastudios.cameraview.engine.action.CompletionCallback; +import com.otaliastudios.cameraview.engine.lock.LockAction; import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.size.AspectRatio; @@ -37,48 +40,69 @@ 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 implements Locker.Callback { +public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder { 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_LOCKER = 1; - private final static int STATE_WAITING_TORCH = 2; - private final static int STATE_WAITING_IMAGE = 3; + private static class FlashAction extends BaseAction { - 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; + @Override + protected void onStart(@NonNull ActionHolder holder) { + super.onStart(holder); + LOG.i("FlashAction:", "Parameters locked, opening torch."); + holder.getBuilder(this).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); + holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + holder.applyBuilder(this); + } + + @Override + public void onCaptureCompleted(@NonNull ActionHolder holder, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + super.onCaptureCompleted(holder, request, result); + Integer flashState = result.get(CaptureResult.FLASH_STATE); + if (flashState == null) { + LOG.w("FlashAction:", "Waiting flash, but flashState is null! Taking snapshot."); + setState(STATE_COMPLETED); + } else if (flashState == CaptureResult.FLASH_STATE_FIRED) { + LOG.i("FlashAction:", "Waiting flash and we have FIRED state! Taking snapshot."); + setState(STATE_COMPLETED); + } else { + LOG.i("FlashAction:", "Waiting flash but flashState is", + flashState, ". Waiting..."); + } + } + } - private int mState = STATE_IDLE; + private final Action mAction; + private final ActionHolder mHolder; + private final boolean mActionNeeded; private Integer mOriginalAeMode; private Integer mOriginalFlashMode; - 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, - @NonNull CaptureResult lastResult) { + @NonNull AspectRatio outputRatio) { super(stub, engine, preview, outputRatio); - mSession = session; - mCallback = callback; - mBuilder = builder; - mCharacteristics = characteristics; - mFirstResult = lastResult; + mHolder = engine; + + mAction = Actions.sequence(new LockAction(), new FlashAction()); + mAction.addCallback(new CompletionCallback() { + @Override + protected void onActionCompleted(@NonNull Action action) { + LOG.i("Taking picture with super.take()."); + Snapshot2PictureRecorder.super.take(); + } + }); - Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); + Integer aeState = mHolder.getLastResult(mAction).get(CaptureResult.CONTROL_AE_STATE); mActionNeeded = engine.getPictureSnapshotMetering() && aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED; + mOriginalAeMode = mHolder.getBuilder(mAction).get(CaptureRequest.CONTROL_AE_MODE); + mOriginalFlashMode = mHolder.getBuilder(mAction).get(CaptureRequest.FLASH_MODE); } @Override @@ -87,85 +111,26 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder implemen LOG.i("take:", "Engine does no metering or needs no flash, taking fast snapshot."); super.take(); } else { - 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); - } - } - - private void applyBuilder() { - try { - mSession.setRepeatingRequest(mBuilder.build(), mCallback, null); - } catch (CameraAccessException e) { - mResult = null; - mError = e; - dispatchResult(); - } - } - - @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_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."); - 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:", "Waiting flash but flashState is", - flashState, ". Waiting..."); - } + LOG.i("take:", "Engine needs flash. Starting action"); + mAction.start(mHolder); } } @Override protected void dispatchResult() { - if (mState == STATE_WAITING_IMAGE) { - // Revert our changes. - LOG.i("dispatchResult:", "Reverting the flash changes."); - try { - // See Camera2Engine.setFlash() comments: turning TORCH off has bugs and we must do - // as follows. - mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - mBuilder.set(CaptureRequest.FLASH_MODE, CaptureResult.FLASH_MODE_OFF); - mSession.capture(mBuilder.build(), null, null); - mBuilder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode); - mBuilder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode); - mSession.setRepeatingRequest(mBuilder.build(), mCallback, null); - } catch (CameraAccessException ignore) {} - } - mState = STATE_IDLE; + // Revert our changes. + LOG.i("dispatchResult:", "Reverting the flash changes."); + try { + // See Camera2Engine.setFlash() comments: turning TORCH off has bugs and we must do + // as follows. + CaptureRequest.Builder builder = mHolder.getBuilder(mAction); + builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + builder.set(CaptureRequest.FLASH_MODE, CaptureResult.FLASH_MODE_OFF); + mHolder.applyBuilder(mAction, builder); + builder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode); + builder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode); + mHolder.applyBuilder(mAction); + } catch (CameraAccessException ignore) {} super.dispatchResult(); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java index 96a45277..6c9306a2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java @@ -15,6 +15,7 @@ import com.otaliastudios.cameraview.engine.action.Action; import com.otaliastudios.cameraview.engine.action.ActionCallback; import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.BaseAction; +import com.otaliastudios.cameraview.engine.action.CompletionCallback; import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.size.Size; @@ -61,12 +62,10 @@ public class Full2VideoRecorder extends FullVideoRecorder { } } }; - action.addCallback(new ActionCallback() { + action.addCallback(new CompletionCallback() { @Override - public void onActionStateChanged(@NonNull Action action, int state) { - if (state == Action.STATE_COMPLETED) { - Full2VideoRecorder.super.onStart(); - } + protected void onActionCompleted(@NonNull Action action) { + Full2VideoRecorder.super.onStart(); } }); action.start(mHolder);