From 2adf88a1c6a3f7db7b5e75f0829f800b0693ad16 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 15 Dec 2019 20:30:12 +0100 Subject: [PATCH] Add setFrameProcessingMaxWidth and setFrameProcessingMaxHeight --- .../cameraview/CameraViewTest.java | 21 ++++- .../engine/CameraIntegrationTest.java | 21 +++++ .../cameraview/engine/MockCameraEngine.java | 6 ++ .../otaliastudios/cameraview/CameraView.java | 36 +++++++++ .../cameraview/engine/Camera1Engine.java | 11 +++ .../cameraview/engine/Camera2Engine.java | 40 ++++++---- .../cameraview/engine/CameraBaseEngine.java | 76 +++++++++++++++++++ .../cameraview/engine/CameraEngine.java | 6 ++ 8 files changed, 200 insertions(+), 17 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index 40e55c99..d1470a4e 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -170,6 +170,8 @@ public class CameraViewTest extends BaseTest { assertEquals(cameraView.getZoom(), 0f, 0f); assertEquals(cameraView.getVideoMaxDuration(), 0, 0); assertEquals(cameraView.getVideoMaxSize(), 0, 0); + assertEquals(cameraView.getSnapshotMaxWidth(), 0, 0); + assertEquals(cameraView.getSnapshotMaxHeight(), 0, 0); // Self managed GestureParser gestures = new GestureParser(empty); @@ -801,6 +803,22 @@ public class CameraViewTest extends BaseTest { assertEquals(cameraView.getPreviewFrameRate(), 60, 0); } + @Test + public void testSnapshotMaxSize() { + cameraView.setSnapshotMaxWidth(500); + assertEquals(cameraView.getSnapshotMaxWidth(), 500); + cameraView.setSnapshotMaxHeight(700); + assertEquals(cameraView.getSnapshotMaxHeight(), 700); + } + + @Test + public void testFrameProcessingMaxSize() { + cameraView.setFrameProcessingMaxWidth(500); + assertEquals(cameraView.getFrameProcessingMaxWidth(), 500); + cameraView.setFrameProcessingMaxHeight(700); + assertEquals(cameraView.getFrameProcessingMaxHeight(), 700); + } + //endregion //region Lists of listeners and processors @@ -975,7 +993,6 @@ public class CameraViewTest extends BaseTest { } //endregion - // TODO: test permissions //region Filter @@ -1002,4 +1019,6 @@ public class CameraViewTest extends BaseTest { //noinspection ResultOfMethodCallIgnored verify(mockPreview, times(1)).getCurrentFilter(); } + + //endregion } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java index 31a4e6a7..5bb245f0 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java @@ -1043,6 +1043,27 @@ public abstract class CameraIntegrationTest extends assert15Frames(processor); } + @Test + @Retry(emulatorOnly = true) + @SdkExclude(maxSdkVersion = 22, emulatorOnly = true) + public void testFrameProcessing_maxSize() { + final int max = 600; + camera.setFrameProcessingMaxWidth(max); + camera.setFrameProcessingMaxHeight(max); + final Op sizeOp = new Op<>(); + camera.addFrameProcessor(new FrameProcessor() { + @Override + public void process(@NonNull Frame frame) { + sizeOp.controller().end(frame.getSize()); + } + }); + openSync(true); + Size size = sizeOp.await(2000); + assertNotNull(size); + assertTrue(size.getWidth() <= max); + assertTrue(size.getHeight() <= max); + } + @Test @Retry(emulatorOnly = true) @SdkExclude(maxSdkVersion = 22, emulatorOnly = true) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java index f7254f15..166bff19 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java @@ -171,6 +171,12 @@ public class MockCameraEngine extends CameraBaseEngine { return new ArrayList<>(); } + @NonNull + @Override + protected List getFrameProcessingAvailableSizes() { + return new ArrayList<>(); + } + @Override public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) { mFocusStarted = true; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 7af2089f..ea896030 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1730,6 +1730,24 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraEngine.setSnapshotMaxHeight(maxHeight); } + /** + * The max width for snapshots. + * @see #setSnapshotMaxWidth(int) + * @return max width + */ + public int getSnapshotMaxWidth() { + return mCameraEngine.getSnapshotMaxWidth(); + } + + /** + * The max height for snapshots. + * @see #setSnapshotMaxHeight(int) + * @return max height + */ + public int getSnapshotMaxHeight() { + return mCameraEngine.getSnapshotMaxHeight(); + } + /** * Returns the size used for snapshots, or null if it hasn't been computed * (for example if the surface is not ready). This is the preview size, rotated to match @@ -2305,6 +2323,24 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraEngine.setFrameProcessingMaxHeight(maxHeight); } + /** + * The max width for frame processing frames. + * @see #setFrameProcessingMaxWidth(int) + * @return max width + */ + public int getFrameProcessingMaxWidth() { + return mCameraEngine.getFrameProcessingMaxWidth(); + } + + /** + * The max height for frame processing frames. + * @see #setFrameProcessingMaxHeight(int) + * @return max height + */ + public int getFrameProcessingMaxHeight() { + return mCameraEngine.getFrameProcessingMaxHeight(); + } + //endregion //region Overlays diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 6714846e..fb9aa9e3 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -47,6 +47,8 @@ import com.otaliastudios.cameraview.video.SnapshotVideoRecorder; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; @@ -104,6 +106,15 @@ public class Camera1Engine extends CameraBaseEngine implements return result; } + @EngineThread + @NonNull + @Override + protected List getFrameProcessingAvailableSizes() { + // We don't choose the frame processing size. + // It comes from the preview stream. + return Collections.singletonList(mPreviewStreamSize); + } + @EngineThread @Override protected void onPreviewStreamSizeChanged() { 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 5be14ffa..22d5a435 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -95,7 +95,6 @@ public class Camera2Engine extends CameraBaseEngine implements private final Camera2Mapper mMapper = Camera2Mapper.get(); // Frame processing - private Size mFrameProcessingSize; private ImageReader mFrameProcessingReader; // need this or the reader surface is collected private final WorkerHandler mFrameConversionHandler; private final Object mFrameProcessingImageLock = new Object(); @@ -326,6 +325,29 @@ public class Camera2Engine extends CameraBaseEngine implements } } + @EngineThread + @NonNull + @Override + protected List getFrameProcessingAvailableSizes() { + try { + CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); + StreamConfigurationMap streamMap = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (streamMap == null) { + throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); + } + android.util.Size[] sizes = streamMap.getOutputSizes(FRAME_PROCESSING_INPUT_FORMAT); + List candidates = new ArrayList<>(sizes.length); + for (android.util.Size size : sizes) { + Size add = new Size(size.getWidth(), size.getHeight()); + if (!candidates.contains(add)) candidates.add(add); + } + return candidates; + } catch (CameraAccessException e) { + throw createCameraException(e); + } + } + @EngineThread @Override protected void onPreviewStreamSizeChanged() { @@ -520,21 +542,7 @@ public class Camera2Engine extends CameraBaseEngine implements // 4. FRAME PROCESSING if (hasFrameProcessors()) { - // Choose the size. - StreamConfigurationMap streamMap = mCameraCharacteristics - .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (streamMap == null) { - throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); - } - android.util.Size[] aSizes = streamMap.getOutputSizes(FRAME_PROCESSING_INPUT_FORMAT); - List sizes = new ArrayList<>(); - for (android.util.Size aSize : aSizes) { - sizes.add(new Size(aSize.getWidth(), aSize.getHeight())); - } - mFrameProcessingSize = SizeSelectors.and( - SizeSelectors.maxWidth(Math.min(640, mPreviewStreamSize.getWidth())), - SizeSelectors.maxHeight(Math.min(640, mPreviewStreamSize.getHeight())), - SizeSelectors.biggest()).select(sizes).get(0); + mFrameProcessingSize = computeFrameProcessingSize(); mFrameProcessingReader = ImageReader.newInstance( mFrameProcessingSize.getWidth(), mFrameProcessingSize.getHeight(), diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java index 0ccbfa7b..90871509 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java @@ -51,6 +51,7 @@ public abstract class CameraBaseEngine extends CameraEngine { @SuppressWarnings("WeakerAccess") protected VideoRecorder mVideoRecorder; @SuppressWarnings("WeakerAccess") protected Size mCaptureSize; @SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize; + @SuppressWarnings("WeakerAccess") protected Size mFrameProcessingSize; @SuppressWarnings("WeakerAccess") protected Flash mFlash; @SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance; @SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec; @@ -80,6 +81,8 @@ public abstract class CameraBaseEngine extends CameraEngine { private long mAutoFocusResetDelayMillis; private int mSnapshotMaxWidth; // in REF_VIEW like SizeSelectors private int mSnapshotMaxHeight; // in REF_VIEW like SizeSelectors + private int mFrameProcessingMaxWidth; // in REF_VIEW like SizeSelectors + private int mFrameProcessingMaxHeight; // in REF_VIEW like SizeSelectors private Overlay mOverlay; // Ops used for testing. @@ -261,6 +264,26 @@ public abstract class CameraBaseEngine extends CameraEngine { return mSnapshotMaxHeight; } + @Override + public final void setFrameProcessingMaxWidth(int maxWidth) { + mFrameProcessingMaxWidth = maxWidth; + } + + @Override + public final int getFrameProcessingMaxWidth() { + return mFrameProcessingMaxWidth; + } + + @Override + public final void setFrameProcessingMaxHeight(int maxHeight) { + mFrameProcessingMaxHeight = maxHeight; + } + + @Override + public final int getFrameProcessingMaxHeight() { + return mFrameProcessingMaxHeight; + } + @Override public final void setAutoFocusResetDelay(long delayMillis) { mAutoFocusResetDelayMillis = delayMillis; @@ -846,5 +869,58 @@ public abstract class CameraBaseEngine extends CameraEngine { return result; } + /** + * This is called anytime {@link #computeFrameProcessingSize()} is called. + * Implementors can return null if frame processor size is not selectable + * @return a list of available sizes for frame processing + */ + @EngineThread + @NonNull + protected abstract List getFrameProcessingAvailableSizes(); + + @EngineThread + @NonNull + @SuppressWarnings("WeakerAccess") + protected final Size computeFrameProcessingSize() { + @NonNull List frameSizes = getFrameProcessingAvailableSizes(); + // These sizes come in REF_SENSOR. Since there is an external selector involved, + // we must convert all of them to REF_VIEW, then flip back when returning. + boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); + List sizes = new ArrayList<>(frameSizes.size()); + for (Size size : frameSizes) { + sizes.add(flip ? size.flip() : size); + } + AspectRatio targetRatio = AspectRatio.of( + mPreviewStreamSize.getWidth(), + mPreviewStreamSize.getHeight()); + if (flip) targetRatio = targetRatio.flip(); + int maxWidth = mFrameProcessingMaxWidth; + int maxHeight = mFrameProcessingMaxHeight; + if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) maxWidth = 640; + if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) maxHeight = 640; + Size targetMaxSize = new Size(maxWidth, maxHeight); + LOG.i("computeFrameProcessingSize:", + "targetRatio:", targetRatio, + "targetMaxSize:", targetMaxSize); + SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); + SizeSelector matchSize = SizeSelectors.and( + SizeSelectors.maxHeight(targetMaxSize.getHeight()), + SizeSelectors.maxWidth(targetMaxSize.getWidth()), + SizeSelectors.biggest()); + SizeSelector matchAll = SizeSelectors.or( + SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints. + matchSize, // If couldn't match aspect ratio, at least respect the size + SizeSelectors.smallest() // If couldn't match any, take the smallest. + ); + Size result = matchAll.select(sizes).get(0); + if (!sizes.contains(result)) { + throw new RuntimeException("SizeSelectors must not return Sizes other than " + + "those in the input list."); + } + if (flip) result = result.flip(); + LOG.i("computeFrameProcessingSize:", "result:", result, "flip:", flip); + return result; + } + //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 41910370..71b41ec4 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -641,6 +641,12 @@ public abstract class CameraEngine implements public abstract void setSnapshotMaxHeight(int maxHeight); public abstract int getSnapshotMaxHeight(); + public abstract void setFrameProcessingMaxWidth(int maxWidth); + public abstract int getFrameProcessingMaxWidth(); + + public abstract void setFrameProcessingMaxHeight(int maxHeight); + public abstract int getFrameProcessingMaxHeight(); + public abstract void setAutoFocusResetDelay(long delayMillis); public abstract long getAutoFocusResetDelay();