Simplify logic

pull/580/head
Mattia Iavarone 6 years ago
parent fcc35056c7
commit 226b9bbaca
  1. 38
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java
  3. 65
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java
  4. 198
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorderOld.java

@ -105,7 +105,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// 3A metering
private Meter mMeter;
private boolean mMeteringNeedsFlash;
private Gesture mMeteringGesture;
private Locker mLocker;
private PictureResult.Stub mDelayedPictureStub;
@ -644,10 +643,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mPictureRecorder = new Snapshot2PictureRecorder(stub, this,
(GlCameraPreview) mPreview,
outputRatio,
mCameraCharacteristics,
mSession,
mRepeatingRequestCallback,
mRepeatingRequestBuilder,
getPictureSnapshotMetering() && mMeteringNeedsFlash);
mLastRepeatingResult);
mPictureRecorder.take();
}
}
@ -1273,11 +1273,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
*/
@Override
public void onMeteringEnd(@Nullable PointF point, boolean success) {
Integer aeState = mLastRepeatingResult.get(CaptureResult.CONTROL_AE_STATE);
mMeteringNeedsFlash = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
LOG.w("onMeteringEnd - point:", point,
"gesture:", mMeteringGesture,
"needsFlash:", mMeteringNeedsFlash,
"success:", success);
if (point != null) {
mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point);
@ -1286,8 +1283,14 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mHandler.post(getAutoFocusResetDelay(), mMeteringResetRunnable);
}
} else {
// Continue with picture capturing.
lockMetering(null);
LOG.w("onMeteringEnd - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot);
if (mDelayedPictureStub.isSnapshot) {
onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false);
} else {
onTakePicture(mDelayedPictureStub, false);
}
mDelayedPictureStub = null;
mDelayedPictureRatio = null;
}
}
@ -1321,6 +1324,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
//region 3A Locking
// TODO this might become public API
private void unlockMetering() {
if (getEngineState() == STATE_STARTED) {
mRepeatingRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
@ -1330,27 +1334,19 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
}
}
private void lockMetering(@Nullable PointF point) {
// TODO this might become public API
@SuppressWarnings("unused")
private void lockMetering() {
if (getPreviewState() == STATE_STARTED) {
mLocker = new Locker(mCameraCharacteristics, this);
mLocker.lock(mLastRepeatingResult, point);
mLocker.lock(mLastRepeatingResult);
}
}
@Override
public void onLocked(@Nullable PointF point, boolean success) {
LOG.w("onLocked - point:", point, "gesture:", mMeteringGesture, "success:", success);
public void onLocked(boolean success) {
LOG.w("onLocked - success:", success);
mLocker = null;
if (point == null) {
LOG.w("onLocked - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot);
if (mDelayedPictureStub.isSnapshot) {
onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false);
} else {
onTakePicture(mDelayedPictureStub, false);
}
mDelayedPictureStub = null;
mDelayedPictureRatio = null;
}
}
@NonNull

@ -1,6 +1,5 @@
package com.otaliastudios.cameraview.engine;
import android.graphics.PointF;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
@ -8,7 +7,6 @@ import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
@ -21,7 +19,7 @@ import com.otaliastudios.cameraview.engine.locking.Parameter;
* Helps Camera2-based engines to perform 3A locking and unlocking.
* Users are required to:
*
* - Call {@link #lock(CaptureResult, PointF)} to start
* - Call {@link #lock(CaptureResult)} to start
* - Call {@link #onCapture(CaptureResult)} when they have partial or total results, as long as the
* locker is still in a locking operation, which can be checked through {@link #isLocking()}
*/
@ -36,10 +34,9 @@ public class Locker {
/**
* Notifies that locking has ended. No action is required for implementors.
* From now on, {@link #isLocking()} will return false.
* @param point a point
* @param success success
*/
void onLocked(@Nullable PointF point, boolean success);
void onLocked(boolean success);
/**
* Returns the currently used builder. This can change while a locking
@ -59,7 +56,6 @@ public class Locker {
private final CameraCharacteristics mCharacteristics;
private final Callback mCallback;
private PointF mPoint;
private boolean mIsLocking;
private Parameter mAutoFocus;
private Parameter mAutoWhiteBalance;
@ -71,7 +67,6 @@ public class Locker {
* @param characteristics the camera characteristics
* @param callback the callback
*/
@SuppressWarnings("WeakerAccess")
public Locker(@NonNull CameraCharacteristics characteristics,
@NonNull Callback callback) {
mCharacteristics = characteristics;
@ -84,11 +79,9 @@ public class Locker {
/**
* Locks 3A values.
* @param lastResult the last result
* @param point a point
*/
public void lock(@NonNull CaptureResult lastResult, @Nullable PointF point) {
public void lock(@NonNull CaptureResult lastResult) {
mIsLocking = true;
mPoint = point;
mLockingStartTime = System.currentTimeMillis();
mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult);
mAutoWhiteBalance.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult);
@ -97,7 +90,7 @@ public class Locker {
/**
* True if we're locking. False if we're not, for example
* if {@link #lock(CaptureResult, PointF)} was never called.
* if {@link #lock(CaptureResult)} was never called.
* @return true if locking
*/
@SuppressWarnings("WeakerAccess")
@ -123,11 +116,11 @@ public class Locker {
boolean success = mAutoFocus.isSuccessful()
&& mAutoExposure.isSuccessful()
&& mAutoWhiteBalance.isSuccessful();
mCallback.onLocked(mPoint, success);
mCallback.onLocked(success);
mIsLocking = false;
} else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) {
LOG.i("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end.");
mCallback.onLocked(mPoint, false);
mCallback.onLocked(false);
mIsLocking = false;
}
}

@ -3,6 +3,7 @@ package com.otaliastudios.cameraview.picture;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
@ -14,6 +15,7 @@ import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.engine.Locker;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio;
@ -35,49 +37,62 @@ import com.otaliastudios.cameraview.size.AspectRatio;
* I'd still love to use the capture intent instead of this, but was not able yet.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder implements Locker.Callback {
private final static String TAG = Snapshot2PictureRecorder.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static int STATE_IDLE = 0;
private final static int STATE_WAITING_FIRST_FRAME = 1;
private final static int STATE_WAITING_LOCKER = 1;
private final static int STATE_WAITING_TORCH = 2;
private final static int STATE_WAITING_IMAGE = 3;
private final CameraCaptureSession mSession;
private final CameraCaptureSession.CaptureCallback mCallback;
private final CaptureRequest.Builder mBuilder;
private final CameraCharacteristics mCharacteristics;
private final CaptureResult mFirstResult;
private final boolean mActionNeeded;
private int mState = STATE_IDLE;
private Integer mOriginalAeMode;
private Integer mOriginalFlashMode;
private boolean mNeedsFlash;
private Locker mLocker;
public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub,
@NonNull Camera2Engine engine,
@NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio,
@NonNull CameraCharacteristics characteristics,
@NonNull CameraCaptureSession session,
@NonNull CameraCaptureSession.CaptureCallback callback,
@NonNull CaptureRequest.Builder builder,
boolean needsFlash) {
@NonNull CaptureResult lastResult) {
super(stub, engine, preview, outputRatio);
mSession = session;
mCallback = callback;
mBuilder = builder;
mNeedsFlash = needsFlash;
mCharacteristics = characteristics;
mFirstResult = lastResult;
Integer aeState = lastResult.get(CaptureResult.CONTROL_AE_STATE);
mActionNeeded = engine.getPictureSnapshotMetering()
&& aeState != null
&& aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
}
@Override
public void take() {
if (!mNeedsFlash) {
if (!mActionNeeded) {
LOG.i("take:", "Engine does no metering or needs no flash, taking fast snapshot.");
super.take();
} else {
LOG.i("take:", "Engine needs flash.");
mState = STATE_WAITING_FIRST_FRAME;
LOG.i("take:", "Engine needs flash. Locking parameters");
mState = STATE_WAITING_LOCKER;
mOriginalAeMode = mBuilder.get(CaptureRequest.CONTROL_AE_MODE);
mOriginalFlashMode = mBuilder.get(CaptureRequest.FLASH_MODE);
mLocker = new Locker(mCharacteristics, this);
mLocker.lock(mFirstResult);
}
}
@ -91,15 +106,33 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
}
}
@NonNull
@Override
public CaptureRequest.Builder getLockingBuilder() {
return mBuilder;
}
@Override
public void onLockingChange() {
applyBuilder();
}
@Override
public void onLocked(boolean success) {
LOG.i("onLocked:", "Parameters locked, opening torch.");
mState = STATE_WAITING_TORCH;
mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
applyBuilder();
}
public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
if (mState == STATE_WAITING_FIRST_FRAME) {
LOG.i("onCaptureCompleted:", "Flash needed, opening torch.");
mState = STATE_WAITING_TORCH;
mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
applyBuilder();
} else if (mState == STATE_WAITING_TORCH) {
if (mState == STATE_WAITING_LOCKER) {
mLocker.onCapture(result);
return;
}
if (mState == STATE_WAITING_TORCH) {
Integer flashState = result.get(CaptureResult.FLASH_STATE);
if (flashState == null) {
LOG.w("onCaptureCompleted:", "Waiting flash, but flashState is null! Taking snapshot.");

@ -1,198 +0,0 @@
package com.otaliastudios.cameraview.picture;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio;
/**
* Wraps {@link SnapshotGlPictureRecorder} for Camera2.
*
* Camera2 engine supports metering for snapshots and we expect for them to correctly fire flash as well.
* The first idea, and in theory, the most correct one, was to set {@link CaptureRequest#CONTROL_CAPTURE_INTENT}
* to {@link CaptureRequest#CONTROL_CAPTURE_INTENT_STILL_CAPTURE}.
*
* According to documentation, this will automatically trigger the flash if parameters says so.
* In fact this is what happens, but it is a very fast flash that only lasts for 1 or 2 frames.
* It's not easy to call super.take() at the exact time so that we capture the frame that was lit.
* I have tried by comparing {@link SurfaceTexture#getTimestamp()} and {@link CaptureResult#SENSOR_TIMESTAMP}
* to identify the correct frame. These timestamps match, but the frame is not the correct one.
*
* So what we do here is ignore the {@link CaptureRequest#CONTROL_CAPTURE_INTENT} and instead open the
* torch, if requested to do so. Then wait for exposure to settle again and finally take a snapshot.
* I'd still love to use the capture intent instead of this, but was not able yet.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Snapshot2PictureRecorderOld extends SnapshotGlPictureRecorder {
private final static String TAG = Snapshot2PictureRecorderOld.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static int STATE_IDLE = 0;
private final static int STATE_WAITING_FIRST_FRAME = 1;
private final static int STATE_WAITING_LOCK = 2;
private final static int STATE_WAITING_TORCH = 3;
private final static int STATE_WAITING_IMAGE = 4;
private final Camera2Engine mEngine;
private final CameraCaptureSession mSession;
private final CameraCaptureSession.CaptureCallback mCallback;
private final CaptureRequest.Builder mBuilder;
private int mState = STATE_IDLE;
private Integer mOriginalAeMode;
private Integer mOriginalFlashMode;
private Integer mOriginalAfMode;
private boolean mNeedsFlash;
public Snapshot2PictureRecorderOld(@NonNull PictureResult.Stub stub,
@NonNull Camera2Engine engine,
@NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio,
@NonNull CameraCaptureSession session,
@NonNull CameraCaptureSession.CaptureCallback callback,
@NonNull CaptureRequest.Builder builder) {
super(stub, engine, preview, outputRatio);
mEngine = engine;
mSession = session;
mCallback = callback;
mBuilder = builder;
}
@Override
public void take() {
if (!mEngine.getPictureSnapshotMetering()) {
LOG.i("take:", "Engine does no metering, taking fast snapshot.");
super.take();
} else {
LOG.i("take:", "Engine does metering, caring about flash.");
mState = STATE_WAITING_FIRST_FRAME;
mOriginalAeMode = mBuilder.get(CaptureRequest.CONTROL_AE_MODE);
mOriginalFlashMode = mBuilder.get(CaptureRequest.FLASH_MODE);
mOriginalAfMode = mBuilder.get(CaptureRequest.CONTROL_AF_MODE);
}
}
private void applyBuilder() {
try {
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException e) {
mResult = null;
mError = e;
dispatchResult();
}
}
/**
* This works by inspecting {@link CaptureResult#CONTROL_AE_STATE}, and specifically
* the {@link CaptureRequest#CONTROL_AE_STATE_FLASH_REQUIRED} value.
* The AE state will have this value iff:
* 1. flash is required
* 2. device has a flash
* 3. we are not in Flash.OFF
* So when we see this value we just have to open the torch and wait a bit more,
* just to have a correct exposure.
*
* @param result a total result
*/
public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
if (mState == STATE_WAITING_FIRST_FRAME) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
mNeedsFlash = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
LOG.i("onCaptureCompleted:", "aeState:", aeState, "needsFlash:", mNeedsFlash);
mState = STATE_WAITING_LOCK;
// Removing any ongoing precapture or trigger. This should not be needed.
if (Build.VERSION.SDK_INT >= 23) {
mBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
}
mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
// Lock AE, AWB, AF to their current values
mBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
mBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
mBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
applyBuilder();
return;
}
if (mState == STATE_WAITING_LOCK) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
boolean aeLocked = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_LOCKED;
boolean awbLocked = awbState != null && awbState == CaptureResult.CONTROL_AWB_STATE_LOCKED;
if (aeLocked && awbLocked) {
if (mNeedsFlash) {
LOG.i("onCaptureCompleted:", "AE/AWB locked!", "Flash needed, opening torch.");
mState = STATE_WAITING_TORCH;
mBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
applyBuilder();
} else {
LOG.i("onCaptureCompleted:", "AE/AWB locked!", "No flash needed, taking picture.");
mState = STATE_WAITING_IMAGE;
super.take();
}
} else {
LOG.i("onCaptureCompleted:", "Waiting for AE/AWB locks...",
"aeState:", aeState, "aeLocked:", aeLocked,
"awbState:", awbState, "awbLocked:", awbLocked);
}
return;
}
if (mState == STATE_WAITING_TORCH) {
Integer flashState = result.get(CaptureResult.FLASH_STATE);
if (flashState == null) {
LOG.w("onCaptureCompleted:", "Waiting flash, but flashState is null! Taking snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else if (flashState == CaptureResult.FLASH_STATE_FIRED) {
LOG.i("onCaptureCompleted:", "Waiting flash and we have FIRED state! Taking snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else {
LOG.i("onCaptureCompleted:", "aeState is FLASH_REQUIRED but flashState is",
flashState, ". Waiting...");
}
//noinspection UnnecessaryReturnStatement
return;
}
}
@Override
protected void dispatchResult() {
if (mState == STATE_WAITING_IMAGE) {
// Revert our changes.
LOG.i("dispatchResult:", "Reverting the capture intent changes.");
try {
// See Camera2Engine.setFlash(). It's better to change these one by one.
mBuilder.set(CaptureRequest.CONTROL_AE_MODE, mOriginalAeMode);
mSession.capture(mBuilder.build(), mCallback, null);
mBuilder.set(CaptureRequest.FLASH_MODE, mOriginalFlashMode);
mSession.capture(mBuilder.build(), mCallback, null);
// Revert locks.
mBuilder.set(CaptureRequest.CONTROL_AF_MODE, mOriginalAfMode);
mBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
mBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, false);
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException ignore) {}
}
mState = STATE_IDLE;
super.dispatchResult();
}
}
Loading…
Cancel
Save