Lock AE/AWB/AF inside the snapshot recorder

pull/580/head
Mattia Iavarone 6 years ago
parent 43c492e2d6
commit 0d7d0ba9b9
  1. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java
  3. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoExposure.java
  4. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoFocus.java
  5. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoWhiteBalance.java
  6. 15
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/Parameter.java
  7. 126
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java

@ -1216,6 +1216,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// The last one is under our control because the library has no focus API.
// So let's set a good af mode here. This operation is reverted during onMeteringReset().
applyFocusForMetering(mRepeatingRequestBuilder);
applyRepeatingRequestBuilder();
// Create the meter and start.
mMeteringGesture = gesture;
@ -1235,7 +1236,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override
public void onMeteringStarted(@Nullable PointF point) {
LOG.w("onMeteringStarted - point:", point, "gesture:", mMeteringGesture);
applyRepeatingRequestBuilder();
if (point != null) {
mCallback.dispatchOnFocusStart(mMeteringGesture, point);
}
@ -1250,7 +1250,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override
public void onMeteringEnd(@Nullable PointF point, boolean success) {
LOG.w("onMeteringEnd - point:", point, "gesture:", mMeteringGesture, "success:", success);
applyRepeatingRequestBuilder();
if (point != null) {
mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point);
} else {
@ -1290,5 +1289,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
return mRepeatingRequestBuilder;
}
@Override
public void onMeteringChange() {
applyRepeatingRequestBuilder();
}
//endregion
}

@ -43,7 +43,7 @@ public class Meter {
/**
* The meter callback.
*/
public interface Callback {
public interface Callback extends Parameter.MeteringChangeCallback {
/**
* Notifies that metering has started. At this point implementors should apply
@ -93,9 +93,9 @@ public class Meter {
private boolean mIsMetering;
private long mMeteringStartTime;
private Parameter mAutoFocus = new AutoFocus();
private Parameter mAutoWhiteBalance = new AutoWhiteBalance();
private Parameter mAutoExposure = new AutoExposure();
private Parameter mAutoFocus;
private Parameter mAutoWhiteBalance;
private Parameter mAutoExposure;
/**
* Creates a new meter.
@ -110,6 +110,9 @@ public class Meter {
mEngine = engine;
mCharacteristics = characteristics;
mCallback = callback;
mAutoFocus = new AutoFocus(callback);
mAutoExposure = new AutoExposure(callback);
mAutoWhiteBalance = new AutoWhiteBalance(callback);
}
/**

@ -20,6 +20,10 @@ public class AutoExposure extends Parameter {
private static final String TAG = AutoExposure.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
public AutoExposure(@NonNull MeteringChangeCallback callback) {
super(callback);
}
private boolean isStarted;
@Override
@ -56,10 +60,12 @@ public class AutoExposure extends Parameter {
boolean supportsProcessing) {
isStarted = false;
boolean changed = false;
if (supportsProcessing) {
// Launch the precapture trigger.
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
changed = true;
}
// Even if precapture is not supported, check the regions anyway.
@ -69,6 +75,13 @@ public class AutoExposure extends Parameter {
int max = Math.min(maxRegions, areas.size());
builder.set(CaptureRequest.CONTROL_AE_REGIONS,
areas.subList(0, max).toArray(new MeteringRectangle[]{}));
changed = true;
}
// Notify change and remove any problematic control for future request
if (changed) {
notifyBuilderChanged();
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, null);
}
}
@ -138,17 +151,23 @@ public class AutoExposure extends Parameter {
@NonNull CaptureRequest.Builder builder,
@Nullable MeteringRectangle area,
boolean supportsProcessing) {
boolean changed = false;
int maxRegions = readCharacteristic(characteristics,
CameraCharacteristics.CONTROL_MAX_REGIONS_AE, 0);
if (area != null && maxRegions > 0) {
builder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{area});
changed = true;
}
if (supportsProcessing) {
// Cleanup any precapture sequence.
if (Build.VERSION.SDK_INT >= 23) {
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
changed = true;
}
}
if (changed) {
notifyBuilderChanged();
}
}
}

@ -20,6 +20,10 @@ public class AutoFocus extends Parameter {
private static final String TAG = AutoFocus.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
public AutoFocus(@NonNull MeteringChangeCallback callback) {
super(callback);
}
@Override
protected boolean checkSupportsProcessing(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
@ -49,8 +53,10 @@ public class AutoFocus extends Parameter {
@NonNull CaptureRequest.Builder builder,
@NonNull List<MeteringRectangle> areas,
boolean supportsProcessing) {
boolean changed = false;
if (supportsProcessing) {
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
changed = true;
}
// Even if auto is not supported, change the regions anyway.
@ -60,6 +66,13 @@ public class AutoFocus extends Parameter {
int max = Math.min(maxRegions, areas.size());
builder.set(CaptureRequest.CONTROL_AF_REGIONS,
areas.subList(0, max).toArray(new MeteringRectangle[]{}));
changed = true;
}
// Notify change and remove any problematic control for future request
if (changed) {
notifyBuilderChanged();
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, null);
}
}
@ -95,14 +108,20 @@ public class AutoFocus extends Parameter {
@NonNull CaptureRequest.Builder builder,
@Nullable MeteringRectangle area,
boolean supportsProcessing) {
boolean changed = false;
int maxRegions = readCharacteristic(characteristics,
CameraCharacteristics.CONTROL_MAX_REGIONS_AF, 0);
if (area != null && maxRegions > 0) {
builder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{area});
changed = true;
}
if (supportsProcessing) { // Cleanup any trigger.
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
changed = true;
}
if (changed) {
notifyBuilderChanged();
}
}
}

@ -20,6 +20,10 @@ public class AutoWhiteBalance extends Parameter {
private static final String TAG = AutoWhiteBalance.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG);
public AutoWhiteBalance(@NonNull MeteringChangeCallback callback) {
super(callback);
}
@Override
protected boolean checkSupportsProcessing(@NonNull CameraCharacteristics characteristics,
@NonNull CaptureRequest.Builder builder) {
@ -51,6 +55,7 @@ public class AutoWhiteBalance extends Parameter {
int max = Math.min(maxRegions, areas.size());
builder.set(CaptureRequest.CONTROL_AWB_REGIONS,
areas.subList(0, max).toArray(new MeteringRectangle[]{}));
notifyBuilderChanged();
}
}
@ -94,6 +99,7 @@ public class AutoWhiteBalance extends Parameter {
CameraCharacteristics.CONTROL_MAX_REGIONS_AWB, 0);
if (area != null && maxRegions > 0) {
builder.set(CaptureRequest.CONTROL_AWB_REGIONS, new MeteringRectangle[]{area});
notifyBuilderChanged();
}
}
}

@ -15,15 +15,25 @@ import java.util.List;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class Parameter {
public interface MeteringChangeCallback {
void onMeteringChange();
}
@SuppressWarnings("WeakerAccess")
protected boolean isSuccessful;
@SuppressWarnings("WeakerAccess")
protected boolean isMetered;
private MeteringChangeCallback callback;
private boolean shouldSkip;
private boolean supportsProcessing;
@SuppressWarnings("WeakerAccess")
protected Parameter(@NonNull MeteringChangeCallback callback) {
this.callback = callback;
}
@SuppressWarnings("WeakerAccess")
@NonNull
protected <T> T readCharacteristic(@NonNull CameraCharacteristics characteristics,
@ -33,6 +43,11 @@ public abstract class Parameter {
return value == null ? fallback : value;
}
@SuppressWarnings("WeakerAccess")
protected void notifyBuilderChanged() {
callback.onMeteringChange();
}
public final boolean isMetered() {
// A non supported parameter should always appear as metered
return isMetered || !supportsProcessing || shouldSkip;

@ -42,8 +42,8 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
private final static int STATE_IDLE = 0;
private final static int STATE_WAITING_FIRST_FRAME = 1;
private final static int STATE_WAITING_TORCH = 2;
private final static int STATE_WAITING_TORCH_EXPOSURE = 3;
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;
@ -53,6 +53,8 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
private int mState = STATE_IDLE;
private Integer mOriginalAeMode;
private Integer mOriginalFlashMode;
private Integer mOriginalAfMode;
private boolean mNeedsFlash;
public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub,
@NonNull Camera2Engine engine,
@ -78,6 +80,17 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
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();
}
}
@ -96,81 +109,64 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
if (mState == STATE_WAITING_FIRST_FRAME) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null) {
LOG.w("onCaptureCompleted:", "aeState is null! This should never happen.",
"Taking snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
LOG.i("onCaptureCompleted:", "aeState is optimal. Taking snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else if (aeState == CaptureResult.CONTROL_AE_STATE_LOCKED) {
LOG.i("onCaptureCompleted:", "aeState is locked. There's nothing we can do.",
"Taking snapshot with no flash.");
mState = STATE_WAITING_IMAGE;
super.take();
} else if (aeState != CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
LOG.w("onCaptureCompleted:", "aeState is not CONVERGED, FLASH_REQUIRED or LOCKED.", aeState,
"This seems to say that metering did not complete before. Let's take the snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else {
// Open the torch. AE_MODE_ON is guaranteed to be supported.
LOG.w("onCaptureCompleted:", "aeState is FLASH_REQUIRED. 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);
try {
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException e) {
mResult = null;
mError = e;
dispatchResult();
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;
}
} else if (mState == STATE_WAITING_TORCH) {
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! Waiting exposure.");
mState = STATE_WAITING_TORCH_EXPOSURE;
} else {
LOG.i("onCaptureCompleted:", "aeState is FLASH_REQUIRED but flashState is",
flashState, ". Waiting...");
}
} else if (mState == STATE_WAITING_TORCH_EXPOSURE) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null) {
LOG.w("onCaptureCompleted:", "aeState is null! Taking snasphot.");
LOG.i("onCaptureCompleted:", "Waiting flash and we have FIRED state! Taking snapshot.");
mState = STATE_WAITING_IMAGE;
super.take();
} else {
switch (aeState) {
case CaptureResult.CONTROL_AE_STATE_CONVERGED:
case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: {
LOG.i("onCaptureCompleted:", "We have torch and good torch exposure.", aeState);
mState = STATE_WAITING_IMAGE;
super.take();
break;
}
case CaptureResult.CONTROL_AE_STATE_LOCKED: {
LOG.w("onCaptureCompleted:", "Final aeState is LOCKED. Should not happen.");
mState = STATE_WAITING_IMAGE;
super.take();
break;
}
default: {
LOG.i("onCaptureCompleted:", "Waiting for torch exposure...:", aeState);
break;
}
}
LOG.i("onCaptureCompleted:", "aeState is FLASH_REQUIRED but flashState is",
flashState, ". Waiting...");
}
//noinspection UnnecessaryReturnStatement
return;
}
}
@ -184,6 +180,8 @@ public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
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);
mBuilder.set(CaptureRequest.CONTROL_AF_MODE, mOriginalAfMode);
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException ignore) {}
}

Loading…
Cancel
Save