diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 582ef5b8..6bce83a8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.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 } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java index ccb34574..4fc7b4d6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java @@ -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); } /** diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoExposure.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoExposure.java index c066cf20..cf80b521 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoExposure.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoExposure.java @@ -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(); + } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoFocus.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoFocus.java index c6189f08..129321f6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoFocus.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoFocus.java @@ -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 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(); } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoWhiteBalance.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoWhiteBalance.java index 86d5a3c6..75428615 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoWhiteBalance.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/AutoWhiteBalance.java @@ -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(); } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/Parameter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/Parameter.java index b8f957f1..1b81e1ae 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/Parameter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/metering/Parameter.java @@ -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 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; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java index 2036ced9..e4f2c51f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java @@ -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) {} }