Move Snapshot2PictureRecorder using actions, rewrite lock package

pull/580/head
Mattia Iavarone 6 years ago
parent e96008f4b0
commit 53d044e340
  1. 93
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 126
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Action.java
  4. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionCallback.java
  5. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionHolder.java
  6. 49
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/ActionWrapper.java
  7. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/Actions.java
  8. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/BaseAction.java
  9. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/action/CompletionCallback.java
  10. 49
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoExposureLock.java
  11. 51
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoFocusLock.java
  12. 64
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/AutoWhiteBalanceLock.java
  13. 40
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/BaseLock.java
  14. 26
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/LockAction.java
  15. 25
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/lock/UnlockAction.java
  16. 73
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java
  17. 88
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java
  18. 169
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java
  19. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java

@ -44,6 +44,7 @@ import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.engine.action.Action; import com.otaliastudios.cameraview.engine.action.Action;
import com.otaliastudios.cameraview.engine.action.ActionHolder; 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.mappers.Camera2Mapper;
import com.otaliastudios.cameraview.engine.offset.Axis; import com.otaliastudios.cameraview.engine.offset.Axis;
import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.engine.offset.Reference;
@ -70,8 +71,7 @@ import java.util.concurrent.ExecutionException;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAvailableListener, public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAvailableListener,
ActionHolder, ActionHolder,
Meter.Callback, Meter.Callback {
Locker.Callback {
private static final String TAG = Camera2Engine.class.getSimpleName(); private static final String TAG = Camera2Engine.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -110,7 +110,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// 3A metering // 3A metering
private Meter mMeter; private Meter mMeter;
private Gesture mMeteringGesture; private Gesture mMeteringGesture;
private Locker mLocker;
private PictureResult.Stub mDelayedPictureStub; private PictureResult.Stub mDelayedPictureStub;
private AspectRatio mDelayedPictureRatio; 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 @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) { for (Action action : mActions) {
action.onCaptureStarted(Camera2Engine.this, request); action.onCaptureStarted(Camera2Engine.this, request);
} }
} }
@Override @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()) { if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(partialResult); mMeter.onCapture(partialResult);
} }
if (mLocker != null && mLocker.isLocking()) {
mLocker.onCapture(partialResult);
}
for (Action action : mActions) { for (Action action : mActions) {
action.onCaptureProgressed(Camera2Engine.this, request, partialResult); action.onCaptureProgressed(Camera2Engine.this, request, partialResult);
} }
} }
@Override @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; mLastRepeatingResult = result;
if (mPictureRecorder instanceof Snapshot2PictureRecorder) {
((Snapshot2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
}
if (mMeter != null && mMeter.isMetering()) { if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(result); mMeter.onCapture(result);
} }
if (mLocker != null && mLocker.isLocking()) {
mLocker.onCapture(result);
}
for (Action action : mActions) { for (Action action : mActions) {
action.onCaptureProgressed(Camera2Engine.this, request, result); action.onCaptureProgressed(Camera2Engine.this, request, result);
} }
@ -294,10 +292,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
try { try {
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); StreamConfigurationMap streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (streamMap == null) if (streamMap == null) {
throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); 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()); android.util.Size[] sizes = streamMap.getOutputSizes(mPreview.getOutputClass());
List<Size> candidates = new ArrayList<>(sizes.length); List<Size> candidates = new ArrayList<>(sizes.length);
for (android.util.Size size : sizes) { 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. // However, let's launch an unrecoverable exception.
throw createCameraException(e); 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) { for (String cameraId : cameraIds) {
try { try {
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId); CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) { if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) {
mCameraId = cameraId; mCameraId = cameraId;
int sensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0); int sensorOffset = readCharacteristic(characteristics,
CameraCharacteristics.SENSOR_ORIENTATION, 0);
getAngles().setSensorOffset(facing, sensorOffset); getAngles().setSensorOffset(facing, sensorOffset);
return true; 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 // However, using trySetException should address this problem - it will only trigger
// if the task has no result. // if the task has no result.
// //
// Docs say to release this camera instance, however, since we throw an unrecoverable CameraException, // Docs say to release this camera instance, however, since we throw an unrecoverable
// this will trigger a stop() through the exception handler. // CameraException, this will trigger a stop() through the exception handler.
task.trySetException(new CameraException(CameraException.REASON_DISCONNECTED)); task.trySetException(new CameraException(CameraException.REASON_DISCONNECTED));
} }
@ -651,16 +653,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
} else { } else {
LOG.i("onTakePictureSnapshot:", "doMetering is false. Performing."); LOG.i("onTakePictureSnapshot:", "doMetering is false. Performing.");
if (!(mPreview instanceof GlCameraPreview)) { 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, mPictureRecorder = new Snapshot2PictureRecorder(stub, this,
(GlCameraPreview) mPreview, (GlCameraPreview) mPreview, outputRatio);
outputRatio,
mCameraCharacteristics,
mSession,
mRepeatingRequestCallback,
mRepeatingRequestBuilder,
mLastRepeatingResult);
mPictureRecorder.take(); mPictureRecorder.take();
} }
} }
@ -1343,42 +1340,14 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region 3A Locking //region 3A Locking
// TODO this might become public API // TODO this might become public API
// TODO add lockMetering
private void unlockMetering() { private void unlockMetering() {
if (getEngineState() == STATE_STARTED) { if (getEngineState() == STATE_STARTED) {
mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); applyDefaultFocus(mRepeatingRequestBuilder);
mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, false); new UnlockAction().start(this);
// TODO applyDefaultFocus(mRepeatingRequestBuilder);
applyRepeatingRequestBuilder(); // only if preview started
} }
} }
// 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 //endregion
//region Actions //region Actions

@ -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;
}
}
}

@ -3,12 +3,14 @@ package com.otaliastudios.cameraview.engine.action;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public interface Action { public interface Action {
int STATE_CREATED = Integer.MIN_VALUE;
int STATE_COMPLETED = Integer.MAX_VALUE; int STATE_COMPLETED = Integer.MAX_VALUE;
int getState(); int getState();

@ -1,7 +1,11 @@
package com.otaliastudios.cameraview.engine.action; package com.otaliastudios.cameraview.engine.action;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public interface ActionCallback { public interface ActionCallback {
void onActionStateChanged(@NonNull Action action, int state); void onActionStateChanged(@NonNull Action action, int state);
} }

@ -5,9 +5,12 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public interface ActionHolder { public interface ActionHolder {
void addAction(@NonNull Action action); void addAction(@NonNull Action action);

@ -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);
}
}

@ -3,21 +3,24 @@ package com.otaliastudios.cameraview.engine.action;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Actions { public class Actions {
@NonNull @NonNull
public static Action together(@NonNull BaseAction... actions) { public static BaseAction together(@NonNull BaseAction... actions) {
return new TogetherAction(Arrays.asList(actions)); return new TogetherAction(Arrays.asList(actions));
} }
@NonNull @NonNull
public static Action sequence(@NonNull BaseAction... actions) { public static BaseAction sequence(@NonNull BaseAction... actions) {
return new SequenceAction(Arrays.asList(actions)); return new SequenceAction(Arrays.asList(actions));
} }

@ -3,12 +3,15 @@ package com.otaliastudios.cameraview.engine.action;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class BaseAction implements Action { public abstract class BaseAction implements Action {
private final List<ActionCallback> callbacks = new ArrayList<>(); private final List<ActionCallback> callbacks = new ArrayList<>();
@ -16,7 +19,7 @@ public abstract class BaseAction implements Action {
private ActionHolder holder; private ActionHolder holder;
@Override @Override
public int getState() { public final int getState() {
return state; 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 @Override
public void addCallback(@NonNull ActionCallback callback) { public void addCallback(@NonNull ActionCallback callback) {
if (!callbacks.contains(callback)) { if (!callbacks.contains(callback)) {

@ -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);
}

@ -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.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.engine.action.ActionHolder;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class AutoExposure extends Parameter { public class AutoExposureLock extends BaseLock {
private static final String TAG = AutoExposure.class.getSimpleName(); private final static String TAG = AutoExposureLock.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG + "Locking"); private final static CameraLogger LOG = CameraLogger.create(TAG);
public AutoExposure(@NonNull LockingChangeCallback callback) {
super(callback);
}
@Override @Override
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, protected boolean checkIsSupported(@NonNull ActionHolder holder) {
@NonNull CaptureRequest.Builder builder) { boolean isNotLegacy = readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1)
boolean isNotLegacy = readCharacteristic(characteristics, != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
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, // 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. // 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 && boolean isAEOn = aeMode != null &&
(aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON (aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH || 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 == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
|| aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */); || aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */);
boolean result = isNotLegacy && isAEOn; boolean result = isNotLegacy && isAEOn;
LOG.i("checkSupportsProcessing:", result); LOG.i("checkIsSupported:", result);
return result; return result;
} }
@Override @Override
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) { protected boolean checkShouldSkip(@NonNull ActionHolder holder) {
Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE); Integer aeState = holder.getLastResult(this).get(CaptureResult.CONTROL_AE_STATE);
boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED; boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED;
LOG.i("checkShouldSkip:", result); LOG.i("checkShouldSkip:", result);
return result; return result;
} }
@Override @Override
protected void onLock(@NonNull CameraCharacteristics characteristics, protected void onStarted(@NonNull ActionHolder holder) {
@NonNull CaptureRequest.Builder builder) {
int cancelTrigger = Build.VERSION.SDK_INT >= 23 int cancelTrigger = Build.VERSION.SDK_INT >= 23
? CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL ? CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
: CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE; : CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE;
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, cancelTrigger); holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, cancelTrigger);
builder.set(CaptureRequest.CONTROL_AE_LOCK, true); holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_LOCK, true);
notifyBuilderChanged(); holder.applyBuilder(this);
} }
@Override @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); Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
LOG.i("processCapture:", "aeState:", aeState); LOG.i("processCapture:", "aeState:", aeState);
if (aeState == null) return; if (aeState == null) return;
switch (aeState) { switch (aeState) {
case CaptureRequest.CONTROL_AE_STATE_LOCKED: { case CaptureRequest.CONTROL_AE_STATE_LOCKED: {
notifyLocked(true); setState(STATE_COMPLETED);
break; break;
} }
case CaptureRequest.CONTROL_AE_STATE_PRECAPTURE: 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
}
} }

@ -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.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.engine.action.ActionHolder;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class AutoFocus extends Parameter { public class AutoFocusLock extends BaseLock {
private static final String TAG = AutoFocus.class.getSimpleName(); private final static String TAG = AutoFocusLock.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG + "Locking"); private final static CameraLogger LOG = CameraLogger.create(TAG);
public AutoFocus(@NonNull LockingChangeCallback callback) {
super(callback);
}
@Override @Override
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics, protected boolean checkIsSupported(@NonNull ActionHolder holder) {
@NonNull CaptureRequest.Builder builder) {
// We'll lock by changing the AF mode to AUTO. // We'll lock by changing the AF mode to AUTO.
// In that mode, AF won't change unless someone starts a trigger operation. // In that mode, AF won't change unless someone starts a trigger operation.
int[] modes = readCharacteristic(characteristics, int[] modes = readCharacteristic(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{});
CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[]{});
for (int mode : modes) { for (int mode : modes) {
if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) { if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) {
return true; return true;
@ -36,14 +32,15 @@ public class AutoFocus extends Parameter {
} }
@Override @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); Integer afState = lastResult.get(CaptureResult.CONTROL_AF_STATE);
boolean afStateOk = afState != null && boolean afStateOk = afState != null &&
(afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_INACTIVE || afState == CaptureResult.CONTROL_AF_STATE_INACTIVE
|| afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED
|| afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED); || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED);
Integer afMode = lastResult.get(CaptureResult.CONTROL_AF_MODE); Integer afMode = lastResult.get(CaptureResult.CONTROL_AF_MODE);
boolean afModeOk = afMode != null && afMode == CaptureResult.CONTROL_AF_MODE_AUTO; boolean afModeOk = afMode != null && afMode == CaptureResult.CONTROL_AF_MODE_AUTO;
boolean result = afStateOk && afModeOk; boolean result = afStateOk && afModeOk;
@ -52,15 +49,15 @@ public class AutoFocus extends Parameter {
} }
@Override @Override
protected void onLock(@NonNull CameraCharacteristics characteristics, protected void onStarted(@NonNull ActionHolder holder) {
@NonNull CaptureRequest.Builder builder) { holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); holder.applyBuilder(this);
notifyBuilderChanged();
} }
@Override @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 afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE); Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE);
LOG.i("onCapture:", "afState:", afState, "afMode:", afMode); 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_INACTIVE:
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED: case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED:
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: { case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: {
notifyLocked(true); setState(STATE_COMPLETED);
break; break;
} }
case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN: 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.
}
} }

@ -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;
}
}
}
}

@ -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> T readCharacteristic(@NonNull CameraCharacteristics.Key<T> 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);
}

@ -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;
}
}

@ -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?
}
}

@ -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
}
}

@ -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> T readCharacteristic(@NonNull CameraCharacteristics characteristics,
@NonNull CameraCharacteristics.Key<T> 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);
}

@ -2,8 +2,6 @@ package com.otaliastudios.cameraview.picture;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.TotalCaptureResult;
@ -15,7 +13,12 @@ import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera2Engine; 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.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio; 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. * I'd still love to use the capture intent instead of this, but was not able yet.
*/ */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @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 String TAG = Snapshot2PictureRecorder.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG); private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static int STATE_IDLE = 0; private static class FlashAction extends BaseAction {
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; @Override
private final CameraCaptureSession.CaptureCallback mCallback; protected void onStart(@NonNull ActionHolder holder) {
private final CaptureRequest.Builder mBuilder; super.onStart(holder);
private final CameraCharacteristics mCharacteristics; LOG.i("FlashAction:", "Parameters locked, opening torch.");
private final CaptureResult mFirstResult; holder.getBuilder(this).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
private final boolean mActionNeeded; 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 mOriginalAeMode;
private Integer mOriginalFlashMode; private Integer mOriginalFlashMode;
private Locker mLocker;
public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub, public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub,
@NonNull Camera2Engine engine, @NonNull Camera2Engine engine,
@NonNull GlCameraPreview preview, @NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio, @NonNull AspectRatio outputRatio) {
@NonNull CameraCharacteristics characteristics,
@NonNull CameraCaptureSession session,
@NonNull CameraCaptureSession.CaptureCallback callback,
@NonNull CaptureRequest.Builder builder,
@NonNull CaptureResult lastResult) {
super(stub, engine, preview, outputRatio); super(stub, engine, preview, outputRatio);
mSession = session; mHolder = engine;
mCallback = callback;
mBuilder = builder; mAction = Actions.sequence(new LockAction(), new FlashAction());
mCharacteristics = characteristics; mAction.addCallback(new CompletionCallback() {
mFirstResult = lastResult; @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() mActionNeeded = engine.getPictureSnapshotMetering()
&& aeState != null && aeState != null
&& aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED; && 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 @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."); LOG.i("take:", "Engine does no metering or needs no flash, taking fast snapshot.");
super.take(); super.take();
} else { } else {
LOG.i("take:", "Engine needs flash. Locking parameters"); LOG.i("take:", "Engine needs flash. Starting action");
mState = STATE_WAITING_LOCKER; mAction.start(mHolder);
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...");
}
} }
} }
@Override @Override
protected void dispatchResult() { protected void dispatchResult() {
if (mState == STATE_WAITING_IMAGE) { // Revert our changes.
// Revert our changes. LOG.i("dispatchResult:", "Reverting the flash changes.");
LOG.i("dispatchResult:", "Reverting the flash changes."); try {
try { // See Camera2Engine.setFlash() comments: turning TORCH off has bugs and we must do
// See Camera2Engine.setFlash() comments: turning TORCH off has bugs and we must do // as follows.
// as follows. CaptureRequest.Builder builder = mHolder.getBuilder(mAction);
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
mBuilder.set(CaptureRequest.FLASH_MODE, CaptureResult.FLASH_MODE_OFF); builder.set(CaptureRequest.FLASH_MODE, CaptureResult.FLASH_MODE_OFF);
mSession.capture(mBuilder.build(), null, null); mHolder.applyBuilder(mAction, builder);
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode); builder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode);
mBuilder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode); builder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode);
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null); mHolder.applyBuilder(mAction);
} catch (CameraAccessException ignore) {} } catch (CameraAccessException ignore) {}
}
mState = STATE_IDLE;
super.dispatchResult(); super.dispatchResult();
} }
} }

@ -15,6 +15,7 @@ import com.otaliastudios.cameraview.engine.action.Action;
import com.otaliastudios.cameraview.engine.action.ActionCallback; import com.otaliastudios.cameraview.engine.action.ActionCallback;
import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.ActionHolder;
import com.otaliastudios.cameraview.engine.action.BaseAction; 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.internal.utils.CamcorderProfiles;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
@ -61,12 +62,10 @@ public class Full2VideoRecorder extends FullVideoRecorder {
} }
} }
}; };
action.addCallback(new ActionCallback() { action.addCallback(new CompletionCallback() {
@Override @Override
public void onActionStateChanged(@NonNull Action action, int state) { protected void onActionCompleted(@NonNull Action action) {
if (state == Action.STATE_COMPLETED) { Full2VideoRecorder.super.onStart();
Full2VideoRecorder.super.onStart();
}
} }
}); });
action.start(mHolder); action.start(mHolder);

Loading…
Cancel
Save