parent
e96008f4b0
commit
53d044e340
@ -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; |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
|
@ -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); |
||||
} |
||||
} |
@ -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); |
||||
} |
@ -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); |
||||
} |
Loading…
Reference in new issue