Create Locker and locking.* parameters

pull/580/head
Mattia Iavarone 6 years ago
parent e1139e4ecb
commit ef879c2bbb
  1. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 154
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Locker.java
  3. 91
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoExposure.java
  4. 101
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoFocus.java
  5. 80
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/AutoWhiteBalance.java
  6. 103
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/locking/Parameter.java

@ -104,6 +104,7 @@ 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;
@ -236,6 +237,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (mMeter != null && mMeter.isMetering()) { if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(partialResult); mMeter.onCapture(partialResult);
} }
if (mLocker != null && mLocker.isLocking()) {
mLocker.onCapture(partialResult);
}
} }
@Override @Override
@ -251,6 +255,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (mMeter != null && mMeter.isMetering()) { if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(result); mMeter.onCapture(result);
} }
if (mLocker != null && mLocker.isLocking()) {
mLocker.onCapture(result);
}
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
Integer aeTriggerState = result.get(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER); Integer aeTriggerState = result.get(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER);
LOG.i("metering:", "aeState:", aeState, "aeTriggerState:", aeTriggerState); LOG.i("metering:", "aeState:", aeState, "aeTriggerState:", aeTriggerState);
@ -553,6 +560,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mMeter.resetMetering(); mMeter.resetMetering();
mMeter = null; mMeter = null;
} }
if (mLocker != null) {
mLocker.unlock();
mLocker = null;
}
if (hasFrameProcessors()) { if (hasFrameProcessors()) {
getFrameManager().release(); getFrameManager().release();
} }
@ -1206,7 +1217,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// refers to "3A metering to a specific point". So if we have one, let's check. // refers to "3A metering to a specific point". So if we have one, let's check.
if (point != null && !mCameraOptions.isAutoFocusSupported()) return; if (point != null && !mCameraOptions.isAutoFocusSupported()) return;
// Reset the old meter if present. // Reset the old meter and locker if present.
if (mLocker != null) {
mLocker.unlock();
mLocker = null;
}
if (mMeter != null) { if (mMeter != null) {
mMeter.resetMetering(); mMeter.resetMetering();
mMeter = null; mMeter = null;

@ -0,0 +1,154 @@
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()} ()}
* - Call {@link #unlock()} to reset the locked parameters if needed.
*/
@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);
/**
* Notifies that locking has been undone. From now on, this locker instance
* is done, although in theory it could be reused by calling
* {@link #lock(CaptureResult)} again.
*/
void onUnlocked();
/**
* 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
*/
@SuppressWarnings("WeakerAccess")
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
*/
@SuppressWarnings("WeakerAccess")
public void lock(@NonNull CaptureResult lastResult) {
mIsLocking = true;
mLockingStartTime = System.currentTimeMillis();
mAutoFocus.lock(mCharacteristics, mCallback.getLockingBuilder(), lastResult);
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
*/
@SuppressWarnings("WeakerAccess")
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
if (!mAutoFocus.isLocked()) mAutoFocus.onCapture(mCallback.getLockingBuilder(), result);
if (!mAutoExposure.isLocked()) mAutoExposure.onCapture(mCallback.getLockingBuilder(), result);
if (!mAutoWhiteBalance.isLocked()) mAutoWhiteBalance.onCapture(mCallback.getLockingBuilder(), result);
if (mAutoFocus.isLocked() && mAutoExposure.isLocked() && mAutoWhiteBalance.isLocked()) {
LOG.i("onCapture:", "all Parameters have converged. Dispatching onMeteringEnd");
boolean success = mAutoFocus.isSuccessful()
&& mAutoExposure.isSuccessful()
&& mAutoWhiteBalance.isSuccessful();
dispatchEnd(success);
} else if (System.currentTimeMillis() - mLockingStartTime >= FORCED_END_DELAY) {
LOG.i("onCapture:", "FORCED_END_DELAY was reached. Some Parameter is stuck. Forcing end.");
dispatchEnd(false);
}
}
private void dispatchEnd(boolean success) {
mCallback.onLocked(success);
mIsLocking = false;
}
/**
* Should be called to unlock.
* Note that {@link Callback#onUnlocked()} will be called immediately,
* we're not waiting for the results.
*/
@SuppressWarnings("WeakerAccess")
public void unlock() {
LOG.i("Unlocking.");
mAutoFocus.unlock(mCharacteristics, mCallback.getLockingBuilder());
mAutoExposure.unlock(mCharacteristics, mCallback.getLockingBuilder());
mAutoWhiteBalance.unlock(mCharacteristics, mCallback.getLockingBuilder());
mCallback.onUnlocked();
}
}

@ -0,0 +1,91 @@
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 AutoExposure extends Parameter {
private static final String TAG = AutoExposure.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
public AutoExposure(@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;
// 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);
boolean isAEOn = aeMode != null &&
(aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH
|| 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);
return result;
}
@Override
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) {
Integer aeState = lastResult.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) {
builder.set(CaptureRequest.CONTROL_AE_LOCK, true);
notifyBuilderChanged();
}
@Override
public void processCapture(@NonNull CaptureResult 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: {
isLocked = true;
isSuccessful = false;
break;
}
case CaptureRequest.CONTROL_AE_STATE_PRECAPTURE:
case CaptureRequest.CONTROL_AE_STATE_CONVERGED:
case CaptureRequest.CONTROL_AE_STATE_INACTIVE:
case CaptureRequest.CONTROL_AE_STATE_SEARCHING:
case CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED: {
// Wait...
break;
}
}
}
@Override
protected void onLocked(@NonNull CaptureRequest.Builder builder) {
// Do nothing
}
@Override
protected void onUnlock(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_AE_LOCK, false);
notifyBuilderChanged();
}
}

@ -0,0 +1,101 @@
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.Nullable;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import java.util.List;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class AutoFocus extends Parameter {
private static final String TAG = AutoFocus.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
private Integer oldAfMode;
public AutoFocus(@NonNull LockingChangeCallback callback) {
super(callback);
}
@Override
protected boolean checkSupportsLocking(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
// 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[]{});
for (int mode : modes) {
if (mode == CameraCharacteristics.CONTROL_AF_MODE_AUTO) {
return true;
}
}
return false;
}
@Override
protected boolean checkShouldSkip(@NonNull CaptureResult lastResult) {
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);
oldAfMode = lastResult.get(CaptureResult.CONTROL_AF_MODE);
boolean afModeOk = oldAfMode != null && oldAfMode == CaptureResult.CONTROL_AF_MODE_AUTO;
boolean result = afStateOk && afModeOk;
LOG.i("checkShouldSkip:", result);
return result;
}
@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();
}
@Override
public void processCapture(@NonNull CaptureResult result) {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE);
LOG.i("onCapture:", "afState:", afState, "afMode:", afMode);
if (afState == null || afMode == null) return;
if (afMode != CaptureResult.CONTROL_AF_MODE_AUTO) return;
switch (afState) {
case CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED:
case CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
case CaptureRequest.CONTROL_AF_STATE_INACTIVE:
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED:
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_UNFOCUSED: {
isLocked = true;
isSuccessful = true;
break;
}
case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN:
case CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN: {
// Wait...
break;
}
}
}
@Override
protected void onLocked(@NonNull CaptureRequest.Builder builder) {
// Do nothing.
}
@Override
protected void onUnlock(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_AF_MODE, oldAfMode);
notifyBuilderChanged();
}
}

@ -0,0 +1,80 @@
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);
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: {
isLocked = true;
isSuccessful = false;
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) {
// Do nothing
}
@Override
protected void onUnlock(@NonNull CameraCharacteristics characteristics, @NonNull CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_AWB_LOCK, false);
notifyBuilderChanged();
}
}

@ -0,0 +1,103 @@
package com.otaliastudios.cameraview.engine.locking;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.util.List;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class Parameter {
public interface LockingChangeCallback {
void onLockingChange();
}
@SuppressWarnings("WeakerAccess")
protected boolean isSuccessful;
@SuppressWarnings("WeakerAccess")
protected boolean isLocked;
private LockingChangeCallback callback;
private boolean shouldSkip;
private boolean supportsLocking;
@SuppressWarnings("WeakerAccess")
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();
}
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);
}
public final void unlock(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
if (supportsLocking) {
// Not checking shouldSkip. Though we skipped locking, we're
// now asked to unlock, so do it.
onUnlock(characteristics, builder);
}
}
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);
protected abstract void onUnlock(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder);
}
Loading…
Cancel
Save