From d69a5e286aaeae6ee97db2ff526ad3a33d078b90 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Tue, 13 Aug 2019 14:25:09 +0200 Subject: [PATCH] Scale down based on encoder requirements --- .../internal/DeviceEncodersTest.java | 34 +++++++++---------- .../otaliastudios/cameraview/CameraView.java | 6 ++++ .../cameraview/internal/DeviceEncoders.java | 21 +++++++++++- docs/_posts/2019-02-24-snapshot-size.md | 11 ++++++ 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java index 32e313c2..64dc2a7f 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java @@ -11,6 +11,7 @@ import androidx.test.rule.ActivityTestRule; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.TestActivity; import com.otaliastudios.cameraview.controls.Grid; +import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; import org.junit.After; @@ -122,30 +123,27 @@ public class DeviceEncodersTest extends BaseTest { assertSame(input, output); } - @Test(expected = RuntimeException.class) - public void testGetSupportedVideoSize_hugeWidth() { - DeviceEncoders deviceEncoders = create(); - if (DeviceEncoders.ENABLED) { - Size input = new Size(Integer.MAX_VALUE, GUARANTEED_SIZE.getHeight()); - deviceEncoders.getSupportedVideoSize(input); - } else { - throw new RuntimeException("Test should pass."); - } - } - - @Test(expected = RuntimeException.class) - public void testGetSupportedVideoSize_hugeHeight() { + @Test + public void testGetSupportedVideoSize_scalesDown() { DeviceEncoders deviceEncoders = create(); if (DeviceEncoders.ENABLED) { - Size input = new Size(GUARANTEED_SIZE.getWidth(), Integer.MAX_VALUE); - deviceEncoders.getSupportedVideoSize(input); - } else { - throw new RuntimeException("Test should pass."); + Size input = new Size( + GUARANTEED_SIZE.getWidth() * 1000, + GUARANTEED_SIZE.getHeight() * 1000); + try { + Size output = deviceEncoders.getSupportedVideoSize(input); + assertTrue(AspectRatio.of(input).matches(output, 0.01F)); + } catch (RuntimeException e) { + // The scaled down size happens to be not supported. + // I see no way of testing this easily if we're not sure of supported ranges. + // This depends highly on the alignment since scaling down, while keeping AR, + // can change the alignment and require width / height changes. + } } } @Test - public void testGetSupportedVideoSize_alignsSize() { + public void testGetSupportedVideoSize_aligns() { DeviceEncoders deviceEncoders = create(); if (DeviceEncoders.ENABLED) { Size input = new Size(GUARANTEED_SIZE.getWidth() + 1, diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 08d84c24..e73daa96 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1664,6 +1664,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * (for example if the surface is not ready). This is the preview size, rotated to match * the output orientation, and cropped to the visible part. * + * This also includes the {@link #setSnapshotMaxWidth(int)} and + * {@link #setSnapshotMaxHeight(int)} constraints. + * + * This does NOT include any constraints specific to video encoding, which are + * device specific and depend on the capabilities of the device codec. + * * @return the size of snapshots */ @Nullable diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java index 4fbd6dac..8e7454e5 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java @@ -138,8 +138,25 @@ public class DeviceEncoders { if (!ENABLED) return size; int width = size.getWidth(); int height = size.getHeight(); + double aspect = (double) width / height; + + // If width is too large, scale down, but keep aspect ratio. + if (mVideoCapabilities.getSupportedWidths().getUpper() < width) { + width = mVideoCapabilities.getSupportedWidths().getUpper(); + height = (int) Math.round(width / aspect); + } + + // If height is too large, scale down, but keep aspect ratio. + if (mVideoCapabilities.getSupportedHeights().getUpper() < height) { + height = mVideoCapabilities.getSupportedHeights().getUpper(); + width = (int) Math.round(aspect * height); + } + + // Adjust the alignment. while (width % mVideoCapabilities.getWidthAlignment() != 0) width--; while (height % mVideoCapabilities.getHeightAlignment() != 0) height--; + + // It's still possible that we're BELOW the lower. if (!mVideoCapabilities.getSupportedWidths().contains(width)) { throw new RuntimeException("Width not supported after adjustment." + " Desired:" + width + @@ -150,10 +167,12 @@ public class DeviceEncoders { " Desired:" + height + " Range:" + mVideoCapabilities.getSupportedHeights()); } + + // It's still possible that we're unsupported for other reasons. if (!mVideoCapabilities.isSizeSupported(width, height)) { throw new RuntimeException("Size not supported for unknown reason." + " Might be an aspect ratio issue." + - " Desired size:" + size); + " Desired size:" + new Size(width, height)); } Size adjusted = new Size(width, height); LOG.i("getSupportedVideoSize -", "inputSize:", size, "adjustedSize:", adjusted); diff --git a/docs/_posts/2019-02-24-snapshot-size.md b/docs/_posts/2019-02-24-snapshot-size.md index 8703a232..b6b326fd 100644 --- a/docs/_posts/2019-02-24-snapshot-size.md +++ b/docs/_posts/2019-02-24-snapshot-size.md @@ -42,6 +42,17 @@ This is very useful as it decouples the snapshot size logic from the preview. By you can have a pleasant, good looking preview stream, while still capturing fast, low-res snapshots with no issues. +### Video Codec requirements + +When taking video snapshots, the video codec that the device provides might require extra constraints, +like + +- width / height alignment +- maximum width or height + +CameraView will try to read these requirements and apply them, which can result in video snapshots +that are smaller than you would expect, or with a **very slightly** different aspect ratio. + ### XML Attributes ```xml