From a801f8d3f579ca371e27eab430e573f67456141b Mon Sep 17 00:00:00 2001 From: tofunmi Date: Wed, 16 Aug 2023 16:43:20 +0100 Subject: [PATCH] Add ImageDecoder.Factory PiperOrigin-RevId: 557498045 --- .../exoplayer/image/DefaultImageDecoder.java | 37 ++++++- .../media3/exoplayer/image/ImageDecoder.java | 22 ++++ .../image/DefaultImageDecoderFactoryTest.java | 103 ++++++++++++++++++ 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderFactoryTest.java 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 2df31081f5..08a0b1b906 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 @@ -25,9 +25,14 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.SimpleDecoder; +import androidx.media3.exoplayer.RendererCapabilities; +import com.google.common.collect.ImmutableSet; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -44,6 +49,36 @@ public class DefaultImageDecoder extends SimpleDecoder implements ImageDecoder { + /** A factory for {@link DefaultImageDecoder} instances. */ + public static final class Factory implements ImageDecoder.Factory { + + private static final ImmutableSet SUPPORTED_IMAGE_TYPES = + ImmutableSet.of( + MimeTypes.IMAGE_PNG, + MimeTypes.IMAGE_JPEG, + MimeTypes.IMAGE_BMP, + MimeTypes.IMAGE_HEIF, + MimeTypes.IMAGE_WEBP); + + @Override + public @RendererCapabilities.Capabilities int supportsFormat(Format format) { + if (!MimeTypes.isImage(format.containerMimeType)) { + return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); + } + if (format.tileCountHorizontal != 1 || format.tileCountVertical != 1) { + return RendererCapabilities.create(C.FORMAT_EXCEEDS_CAPABILITIES); + } + return SUPPORTED_IMAGE_TYPES.contains(format.containerMimeType) + ? RendererCapabilities.create(C.FORMAT_HANDLED) + : RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); + } + + @Override + public DefaultImageDecoder createImageDecoder() { + return new DefaultImageDecoder(); + } + } + /** Creates an instance. */ public DefaultImageDecoder() { super(new DecoderInputBuffer[1], new ImageOutputBuffer[1]); @@ -98,7 +133,7 @@ public class DefaultImageDecoder * @return The decoded {@link Bitmap}. * @throws ImageDecoderException If a decoding error occurs. */ - protected Bitmap decode(byte[] data, int length) throws ImageDecoderException { + /* package */ 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/main/java/androidx/media3/exoplayer/image/ImageDecoder.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageDecoder.java index 4b0feffec0..357a55f233 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageDecoder.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageDecoder.java @@ -17,15 +17,37 @@ package androidx.media3.exoplayer.image; import android.graphics.Bitmap; import androidx.annotation.Nullable; +import androidx.media3.common.Format; import androidx.media3.common.util.UnstableApi; import androidx.media3.decoder.Decoder; import androidx.media3.decoder.DecoderInputBuffer; +import androidx.media3.exoplayer.RendererCapabilities.Capabilities; /** A {@link Decoder} implementation for images. */ @UnstableApi public interface ImageDecoder extends Decoder { + /** A factory for image decoders. */ + interface Factory { + + /** Default implementation of an image decoder factory. */ + ImageDecoder.Factory DEFAULT = new DefaultImageDecoder.Factory(); + + /** + * Returns the highest {@link Capabilities} of the factory's decoders for the given {@link + * Format}. + * + * @param format The {@link Format}. + * @return The {@link Capabilities} of the decoders the factory can instantiate for this format. + */ + @Capabilities + int supportsFormat(Format format); + + /** Creates a new {@link ImageDecoder}. */ + ImageDecoder createImageDecoder(); + } + /** * Queues an {@link DecoderInputBuffer} to the decoder. * diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderFactoryTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderFactoryTest.java new file mode 100644 index 0000000000..f910a369ea --- /dev/null +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/image/DefaultImageDecoderFactoryTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.media3.exoplayer.image; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; +import androidx.media3.exoplayer.RendererCapabilities; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link DefaultImageDecoder.Factory}. */ +@RunWith(AndroidJUnit4.class) +public class DefaultImageDecoderFactoryTest { + + private final DefaultImageDecoder.Factory imageDecoderFactory = new DefaultImageDecoder.Factory(); + + @Test + public void supportsFormat_validFormat_returnsFormatSupported() throws Exception { + Format.Builder format = + new Format.Builder() + .setContainerMimeType(MimeTypes.IMAGE_JPEG) + .setTileCountVertical(1) + .setTileCountHorizontal(1); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_HANDLED)); + } + + @Test + public void supportsFormat_unsetTileCounts_returnsExceedsCapabilities() throws Exception { + Format.Builder format = new Format.Builder().setContainerMimeType(MimeTypes.IMAGE_JPEG); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_EXCEEDS_CAPABILITIES)); + } + + @Test + public void supportsFormat_unsetTileCountVertical_returnsExceedsCapabilities() throws Exception { + Format.Builder format = new Format.Builder().setContainerMimeType(MimeTypes.IMAGE_JPEG); + + format.setTileCountVertical(1); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_EXCEEDS_CAPABILITIES)); + } + + @Test + public void supportsFormat_unsetTileCountHorizontal_returnsExceedsCapabilities() + throws Exception { + Format.Builder format = new Format.Builder().setContainerMimeType(MimeTypes.IMAGE_JPEG); + + format.setTileCountHorizontal(1); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_EXCEEDS_CAPABILITIES)); + } + + @Test + public void supportsFormat_noContainerMimeType_returnsUnsupportedType() throws Exception { + Format.Builder format = new Format.Builder().setTileCountHorizontal(1).setTileCountVertical(1); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE)); + } + + @Test + public void supportsFormat_nonImageMimeType_returnsUnsupportedType() throws Exception { + Format.Builder format = new Format.Builder().setTileCountHorizontal(1).setTileCountVertical(1); + + format.setContainerMimeType(MimeTypes.VIDEO_AV1); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE)); + } + + @Test + public void supportsFormat_unsupportedImageMimeType_returnsUnsupportedSubType() throws Exception { + Format.Builder format = new Format.Builder().setTileCountHorizontal(1).setTileCountVertical(1); + + format.setContainerMimeType("image/custom"); + + assertThat(imageDecoderFactory.supportsFormat(format.build())) + .isEqualTo(RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE)); + } +}