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.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<Size> 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

@ -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.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();

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

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

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

@ -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<ActionCallback> 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)) {

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

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

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

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

Loading…
Cancel
Save