Add ImageDecoder.Factory

PiperOrigin-RevId: 557498045
This commit is contained in:
tofunmi 2023-08-16 16:43:20 +01:00 committed by oceanjules
parent 2156f94480
commit a801f8d3f5
3 changed files with 161 additions and 1 deletions

View file

@ -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<DecoderInputBuffer, ImageOutputBuffer, ImageDecoderException>
implements ImageDecoder {
/** A factory for {@link DefaultImageDecoder} instances. */
public static final class Factory implements ImageDecoder.Factory {
private static final ImmutableSet<String> 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(

View file

@ -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<DecoderInputBuffer, ImageOutputBuffer, ImageDecoderException> {
/** 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.
*

View file

@ -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));
}
}