From a8944ef2f0c42e6f74f89961065af2914b0f9ffb Mon Sep 17 00:00:00 2001 From: tofunmi Date: Thu, 17 Aug 2023 16:14:23 +0100 Subject: [PATCH] Add BitmapDecoder interface PiperOrigin-RevId: 557827954 --- .../image/DefaultImageDecoderTest.java | 29 +++++++++--- .../exoplayer/image/DefaultImageDecoder.java | 46 ++++++++++++++++--- .../DefaultImageDecoderBufferQueueTest.java | 17 +++---- 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/image/DefaultImageDecoderTest.java b/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/image/DefaultImageDecoderTest.java index 13db2f195a..25cfe4bfc0 100644 --- a/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/image/DefaultImageDecoderTest.java +++ b/libraries/exoplayer/src/androidTest/java/androidx/media3/exoplayer/image/DefaultImageDecoderTest.java @@ -15,15 +15,18 @@ */ package androidx.media3.exoplayer.image; +import static androidx.media3.common.util.Assertions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; +import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.nio.ByteBuffer; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -36,16 +39,20 @@ public class DefaultImageDecoderTest { private static final String PNG_TEST_IMAGE_PATH = "media/png/non-motion-photo-shortened.png"; private static final String JPEG_TEST_IMAGE_PATH = "media/jpeg/non-motion-photo-shortened.jpg"; - private DefaultImageDecoder imageDecoder; + private DefaultImageDecoder decoder; + private DecoderInputBuffer inputBuffer; + private ImageOutputBuffer outputBuffer; @Before public void setUp() { - imageDecoder = new DefaultImageDecoder(); + decoder = new DefaultImageDecoder.Factory().createImageDecoder(); + inputBuffer = decoder.createInputBuffer(); + outputBuffer = decoder.createOutputBuffer(); } @After public void tearDown() { - imageDecoder.release(); + decoder.release(); } @Test @@ -53,7 +60,7 @@ public class DefaultImageDecoderTest { byte[] imageData = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), PNG_TEST_IMAGE_PATH); - Bitmap bitmap = imageDecoder.decode(imageData, imageData.length); + Bitmap bitmap = decode(imageData); assertThat( bitmap.sameAs( @@ -79,14 +86,22 @@ public class DefaultImageDecoderTest { rotationMatrix, /* filter= */ false); - Bitmap actualBitmap = imageDecoder.decode(imageData, imageData.length); + Bitmap actualBitmap = decode(imageData); assertThat(actualBitmap.sameAs(expectedBitmap)).isTrue(); } @Test public void decodeBitmap_withInvalidData_throws() throws ImageDecoderException { - assertThrows( - ImageDecoderException.class, () -> imageDecoder.decode(new byte[1], /* length= */ 1)); + assertThrows(ImageDecoderException.class, () -> decode(new byte[1])); + } + + private Bitmap decode(byte[] data) throws Exception { + inputBuffer.data = ByteBuffer.wrap(data); + Exception e = decoder.decode(inputBuffer, outputBuffer, /* reset= */ false); + if (e != null) { + throw e; + } + return checkNotNull(outputBuffer.bitmap); } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/DefaultImageDecoder.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/DefaultImageDecoder.java index 08a0b1b906..156f2ac670 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/DefaultImageDecoder.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/DefaultImageDecoder.java @@ -45,13 +45,27 @@ import java.nio.ByteBuffer; * alongside one timestamp)). */ @UnstableApi -public class DefaultImageDecoder +public final class DefaultImageDecoder extends SimpleDecoder implements ImageDecoder { + /** A functional interface for turning byte arrays into bitmaps. */ + public interface BitmapDecoder { + + /** + * Decodes data into a {@link Bitmap}. + * + * @param data An array holding the data to be decoded, starting at position 0. + * @param length The length of the input to be decoded. + * @return The decoded {@link Bitmap}. + * @throws ImageDecoderException If a decoding error occurs. + */ + Bitmap decode(byte[] data, int length) throws ImageDecoderException; + } + /** A factory for {@link DefaultImageDecoder} instances. */ public static final class Factory implements ImageDecoder.Factory { - + private final BitmapDecoder bitmapDecoder; private static final ImmutableSet SUPPORTED_IMAGE_TYPES = ImmutableSet.of( MimeTypes.IMAGE_PNG, @@ -60,6 +74,22 @@ public class DefaultImageDecoder MimeTypes.IMAGE_HEIF, MimeTypes.IMAGE_WEBP); + /** + * Creates an instance using a {@link BitmapFactory} implementation of {@link BitmapDecoder}. + */ + public Factory() { + this.bitmapDecoder = DefaultImageDecoder::decode; + } + + /** + * Creates an instance. + * + * @param bitmapDecoder The {@link BitmapDecoder} used to turn a byte arrays into a bitmap. + */ + public Factory(BitmapDecoder bitmapDecoder) { + this.bitmapDecoder = bitmapDecoder; + } + @Override public @RendererCapabilities.Capabilities int supportsFormat(Format format) { if (!MimeTypes.isImage(format.containerMimeType)) { @@ -75,13 +105,15 @@ public class DefaultImageDecoder @Override public DefaultImageDecoder createImageDecoder() { - return new DefaultImageDecoder(); + return new DefaultImageDecoder(bitmapDecoder); } } - /** Creates an instance. */ - public DefaultImageDecoder() { + private final BitmapDecoder bitmapDecoder; + + private DefaultImageDecoder(BitmapDecoder bitmapDecoder) { super(new DecoderInputBuffer[1], new ImageOutputBuffer[1]); + this.bitmapDecoder = bitmapDecoder; } @Override @@ -117,7 +149,7 @@ public class DefaultImageDecoder ByteBuffer inputData = checkNotNull(inputBuffer.data); checkState(inputData.hasArray()); checkArgument(inputData.arrayOffset() == 0); - outputBuffer.bitmap = decode(inputData.array(), inputData.remaining()); + outputBuffer.bitmap = bitmapDecoder.decode(inputData.array(), inputData.remaining()); outputBuffer.timeUs = inputBuffer.timeUs; return null; } catch (ImageDecoderException e) { @@ -133,7 +165,7 @@ public class DefaultImageDecoder * @return The decoded {@link Bitmap}. * @throws ImageDecoderException If a decoding error occurs. */ - /* package */ Bitmap decode(byte[] data, int length) throws ImageDecoderException { + private static Bitmap decode(byte[] data, int length) throws ImageDecoderException { @Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length); if (bitmap == null) { throw new ImageDecoderException( diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderBufferQueueTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderBufferQueueTest.java index a72a5765c9..bbf6998707 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderBufferQueueTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderBufferQueueTest.java @@ -42,22 +42,17 @@ public class DefaultImageDecoderBufferQueueTest { private Bitmap decodedBitmap1; private Bitmap decodedBitmap2; + public int decodeCallCount; + @Before public void setUp() throws Exception { + decodeCallCount = 0; decodedBitmap1 = Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888); decodedBitmap2 = Bitmap.createBitmap(/* width= */ 2, /* height= */ 2, Bitmap.Config.ARGB_8888); fakeImageDecoder = - new DefaultImageDecoder() { - - public int decodeCallCount; - - /** Overrides the decode method to fake it. */ - @Override - protected Bitmap decode(byte[] data, int length) { - decodeCallCount++; - return decodeCallCount == 1 ? decodedBitmap1 : decodedBitmap2; - } - }; + new DefaultImageDecoder.Factory( + (data, length) -> ++decodeCallCount == 1 ? decodedBitmap1 : decodedBitmap2) + .createImageDecoder(); } @After