From 8ee8910788de1f3967108742e27484324b8d8af8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 19 Dec 2022 10:08:47 +0000 Subject: [PATCH] Rollback of https://github.com/androidx/media/commit/7e6399745976dee403f24b90ee989f0d51907818 *** Original commit *** Add TransformerTestBuilderFactory to make transformer testable by apps *** PiperOrigin-RevId: 496342997 --- libraries/test_utils/build.gradle | 1 - .../utils/TestTransformerBuilderFactory.java | 107 ------ libraries/test_utils_robolectric/build.gradle | 1 - .../robolectric/ShadowMediaCodecConfig.java | 213 ++--------- .../media3/transformer/Transformer.java | 2 +- .../media3/transformer}/TestMuxer.java | 7 +- .../transformer/TransformerEndToEndTest.java | 342 +++++++++++------- 7 files changed, 244 insertions(+), 429 deletions(-) delete mode 100644 libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java rename libraries/{test_utils/src/main/java/androidx/media3/test/utils => transformer/src/test/java/androidx/media3/transformer}/TestMuxer.java (96%) diff --git a/libraries/test_utils/build.gradle b/libraries/test_utils/build.gradle index a5817b9f92..d3901cf175 100644 --- a/libraries/test_utils/build.gradle +++ b/libraries/test_utils/build.gradle @@ -26,7 +26,6 @@ dependencies { implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion implementation project(modulePrefix + 'lib-exoplayer') - implementation project(modulePrefix + ':lib-transformer') testImplementation 'org.robolectric:robolectric:' + robolectricVersion } diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java deleted file mode 100644 index 82c6ae8ac9..0000000000 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2022 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.test.utils; - -import static androidx.media3.common.util.Assertions.checkStateNotNull; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.ParcelFileDescriptor; -import androidx.media3.common.C; -import androidx.media3.common.util.Clock; -import androidx.media3.common.util.UnstableApi; -import androidx.media3.transformer.DefaultEncoderFactory; -import androidx.media3.transformer.DefaultMuxer; -import androidx.media3.transformer.Muxer; -import androidx.media3.transformer.Transformer; -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - -/** - * Creates a {@link Transformer.Builder} setting up some of the common resources needed for testing - * {@link Transformer}. - */ -@UnstableApi -public final class TestTransformerBuilderFactory { - - private final Context context; - - private static @MonotonicNonNull TestMuxer testMuxer; - private long maxDelayBetweenSamplesMs; - - /** Creates a new instance */ - public TestTransformerBuilderFactory(Context context) { - this.context = context; - maxDelayBetweenSamplesMs = DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS; - } - - /** - * Sets the muxer's {@linkplain Muxer#getMaxDelayBetweenSamplesMs() max delay} between samples. - */ - @CanIgnoreReturnValue - public TestTransformerBuilderFactory setMaxDelayBetweenSamplesMs(long maxDelayBetweenSamplesMs) { - this.maxDelayBetweenSamplesMs = maxDelayBetweenSamplesMs; - return this; - } - - /** Returns a {@link Transformer.Builder} using the provided values or their defaults. */ - @SuppressLint("VisibleForTests") // Suppresses warning on setting the clock outside of a test file - public Transformer.Builder create(boolean enableFallback) { - Clock clock = new FakeClock(/* isAutoAdvancing= */ true); - Muxer.Factory defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs); - return new Transformer.Builder(context) - .setClock(clock) - .setMuxerFactory(new TestMuxerFactory(defaultMuxerFactory)) - .setEncoderFactory( - new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build()); - } - - /** - * Returns the test muxer used in the {@link Transformer.Builder}. - * - *

This method should only be called after the transformation is completed. - */ - public TestMuxer getTestMuxer() { - return checkStateNotNull(testMuxer); - } - - private static final class TestMuxerFactory implements Muxer.Factory { - - private final Muxer.Factory defaultMuxerFactory; - - public TestMuxerFactory(Muxer.Factory defaultMuxerFactory) { - this.defaultMuxerFactory = defaultMuxerFactory; - } - - @Override - public Muxer create(String path) throws Muxer.MuxerException { - testMuxer = new TestMuxer(path, defaultMuxerFactory); - return testMuxer; - } - - @Override - public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException { - testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory); - return testMuxer; - } - - @Override - public ImmutableList getSupportedSampleMimeTypes(@C.TrackType int trackType) { - return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType); - } - } -} diff --git a/libraries/test_utils_robolectric/build.gradle b/libraries/test_utils_robolectric/build.gradle index e579a00166..aafdce151a 100644 --- a/libraries/test_utils_robolectric/build.gradle +++ b/libraries/test_utils_robolectric/build.gradle @@ -20,7 +20,6 @@ dependencies { implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'org.robolectric:robolectric:' + robolectricVersion implementation project(modulePrefix + 'lib-exoplayer') - implementation project(modulePrefix + ':lib-transformer') implementation project(modulePrefix + 'test-utils') } diff --git a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java index fe5e61e8de..6e85eed2a0 100644 --- a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java +++ b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java @@ -16,15 +16,10 @@ package androidx.media3.test.utils.robolectric; import android.media.MediaCodecInfo; -import android.media.MediaCrypto; import android.media.MediaFormat; -import android.view.Surface; -import androidx.annotation.Nullable; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; -import androidx.media3.common.util.Util; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; -import androidx.media3.transformer.EncoderUtil; import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; import java.nio.ByteBuffer; @@ -35,247 +30,105 @@ import org.robolectric.shadows.ShadowMediaCodec; import org.robolectric.shadows.ShadowMediaCodecList; /** - * A JUnit @Rule to configure {@link ShadowMediaCodec} for transcoding or decoding. + * A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}. * - *

Registers {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} instances for ExoPlayer - * and Transformer tests. + *

Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video + * MIME type known by ExoPlayer. */ @UnstableApi public final class ShadowMediaCodecConfig extends ExternalResource { - private static final String EXOTEST_VIDEO_AVC = "exotest.video.avc"; - private static final String EXOTEST_VIDEO_MPEG2 = "exotest.video.mpeg2"; - private static final String EXOTEST_VIDEO_VP9 = "exotest.video.vp9"; - private static final String EXOTEST_AUDIO_AAC = "exotest.audio.aac"; - private static final String EXOTEST_AUDIO_AC3 = "exotest.audio.ac3"; - private static final String EXOTEST_AUDIO_AC4 = "exotest.audio.ac4"; - private static final String EXOTEST_AUDIO_E_AC3 = "exotest.audio.eac3"; - private static final String EXOTEST_AUDIO_E_AC3_JOC = "exotest.audio.eac3joc"; - private static final String EXOTEST_AUDIO_FLAC = "exotest.audio.flac"; - private static final String EXOTEST_AUDIO_MPEG = "exotest.audio.mpeg"; - private static final String EXOTEST_AUDIO_MPEG_L2 = "exotest.audio.mpegl2"; - private static final String EXOTEST_AUDIO_OPUS = "exotest.audio.opus"; - private static final String EXOTEST_AUDIO_VORBIS = "exotest.audio.vorbis"; - private static final String EXOTEST_AUDIO_RAW = "exotest.audio.raw"; - - private final boolean forTranscoding; - - private ShadowMediaCodecConfig(boolean forTranscoding) { - this.forTranscoding = forTranscoding; - } - - /** Creates an instance that configures {@link ShadowMediaCodec} for Transformer transcoding. */ - public static ShadowMediaCodecConfig forTranscoding() { - return new ShadowMediaCodecConfig(/* forTranscoding= */ true); - } - - /** Creates an instance that configures {@link ShadowMediaCodec} for Exoplayer decoding. */ public static ShadowMediaCodecConfig forAllSupportedMimeTypes() { - return new ShadowMediaCodecConfig(/* forTranscoding= */ false); + return new ShadowMediaCodecConfig(); } @Override protected void before() throws Throwable { - if (forTranscoding) { - addTranscodingCodecs(); - } else { - addDecodingCodecs(); - } - } - - private void addTranscodingCodecs() { - ShadowMediaCodec.CodecConfig codecConfig = - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 10_000, - /* outputBufferSize= */ 10_000, - /* codec= */ (in, out) -> out.put(in)); - addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ true); - addTransformerCodec(MimeTypes.AUDIO_AC3, codecConfig, /* isDecoder= */ true); - addTransformerCodec(MimeTypes.AUDIO_AMR_NB, codecConfig, /* isDecoder= */ true); - addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ false); - - ShadowMediaCodec.CodecConfig throwingCodecConfig = - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 10_000, - /* outputBufferSize= */ 10_000, - new ShadowMediaCodec.CodecConfig.Codec() { - - @Override - public void process(ByteBuffer in, ByteBuffer out) { - out.put(in); - } - - @Override - public void onConfigured( - MediaFormat format, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags) { - throw new IllegalArgumentException("Format unsupported"); - } - }); - addTransformerCodec(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig, /* isDecoder= */ true); - addTransformerCodec(MimeTypes.AUDIO_AMR_NB, throwingCodecConfig, /* isDecoder= */ false); - } - - private void addDecodingCodecs() { // Video codecs MediaCodecInfo.CodecProfileLevel avcProfileLevel = createProfileLevel( MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, MediaCodecInfo.CodecProfileLevel.AVCLevel62); - addExoplayerCodec( - EXOTEST_VIDEO_AVC, + configureCodec( + /* codecName= */ "exotest.video.avc", MimeTypes.VIDEO_H264, - generateDecodingCodecConfig(MimeTypes.VIDEO_H264), ImmutableList.of(avcProfileLevel), ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel = createProfileLevel( MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain, MediaCodecInfo.CodecProfileLevel.MPEG2LevelML); - addExoplayerCodec( - EXOTEST_VIDEO_MPEG2, + configureCodec( + /* codecName= */ "exotest.video.mpeg2", MimeTypes.VIDEO_MPEG2, - generateDecodingCodecConfig(MimeTypes.VIDEO_MPEG2), ImmutableList.of(mpeg2ProfileLevel), ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - addExoplayerCodec( - EXOTEST_VIDEO_VP9, + configureCodec( + /* codecName= */ "exotest.video.vp9", MimeTypes.VIDEO_VP9, - generateDecodingCodecConfig(MimeTypes.VIDEO_VP9), ImmutableList.of(), ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); // Audio codecs - addExoplayerCodec( - EXOTEST_AUDIO_AAC, MimeTypes.AUDIO_AAC, generateDecodingCodecConfig(MimeTypes.AUDIO_AAC)); - addExoplayerCodec( - EXOTEST_AUDIO_AC3, MimeTypes.AUDIO_AC3, generateDecodingCodecConfig(MimeTypes.AUDIO_AC3)); - addExoplayerCodec( - EXOTEST_AUDIO_AC4, MimeTypes.AUDIO_AC4, generateDecodingCodecConfig(MimeTypes.AUDIO_AC4)); - addExoplayerCodec( - EXOTEST_AUDIO_E_AC3, - MimeTypes.AUDIO_E_AC3, - generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3)); - addExoplayerCodec( - EXOTEST_AUDIO_E_AC3_JOC, - MimeTypes.AUDIO_E_AC3_JOC, - generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3_JOC)); - addExoplayerCodec( - EXOTEST_AUDIO_FLAC, - MimeTypes.AUDIO_FLAC, - generateDecodingCodecConfig(MimeTypes.AUDIO_FLAC)); - addExoplayerCodec( - EXOTEST_AUDIO_MPEG, - MimeTypes.AUDIO_MPEG, - generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG)); - addExoplayerCodec( - EXOTEST_AUDIO_MPEG_L2, - MimeTypes.AUDIO_MPEG_L2, - generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG_L2)); - addExoplayerCodec( - EXOTEST_AUDIO_OPUS, - MimeTypes.AUDIO_OPUS, - generateDecodingCodecConfig(MimeTypes.AUDIO_OPUS)); - addExoplayerCodec( - EXOTEST_AUDIO_VORBIS, - MimeTypes.AUDIO_VORBIS, - generateDecodingCodecConfig(MimeTypes.AUDIO_VORBIS)); + configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC); + configureCodec("exotest.audio.ac3", MimeTypes.AUDIO_AC3); + configureCodec("exotest.audio.ac4", MimeTypes.AUDIO_AC4); + configureCodec("exotest.audio.eac3", MimeTypes.AUDIO_E_AC3); + configureCodec("exotest.audio.eac3joc", MimeTypes.AUDIO_E_AC3_JOC); + configureCodec("exotest.audio.flac", MimeTypes.AUDIO_FLAC); + configureCodec("exotest.audio.mpeg", MimeTypes.AUDIO_MPEG); + configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2); + configureCodec("exotest.audio.opus", MimeTypes.AUDIO_OPUS); + configureCodec("exotest.audio.vorbis", MimeTypes.AUDIO_VORBIS); // Raw audio should use a bypass mode and never need this codec. However, to easily assert // failures of the bypass mode we want to detect when the raw audio is decoded by this class and // thus we need a codec to output samples. - addExoplayerCodec( - EXOTEST_AUDIO_RAW, MimeTypes.AUDIO_RAW, generateDecodingCodecConfig(MimeTypes.AUDIO_RAW)); + configureCodec("exotest.audio.raw", MimeTypes.AUDIO_RAW); } @Override protected void after() { - if (!forTranscoding) { - MediaCodecUtil.clearDecoderInfoCache(); - } else { - EncoderUtil.clearCachedEncoders(); - } + MediaCodecUtil.clearDecoderInfoCache(); ShadowMediaCodecList.reset(); ShadowMediaCodec.clearCodecs(); } - private ShadowMediaCodec.CodecConfig generateDecodingCodecConfig(String mimeType) { - // TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed - // to configure() so we don't have to specify large buffers here. - CodecImpl codec = new CodecImpl(mimeType); - return new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec); - } - - private void addTransformerCodec( - String mimeType, ShadowMediaCodec.CodecConfig codecConfig, boolean isDecoder) { - String codecName = - Util.formatInvariant( - isDecoder ? "transformertest.%s.decoder" : "transformertest.%s.encoder", - mimeType.replace('/', '.')); - addCodec( + private void configureCodec(String codecName, String mimeType) { + configureCodec( codecName, mimeType, - codecConfig, - /* profileLevels= */ ImmutableList.of(), - /* colorFormats= */ ImmutableList.of(), - isDecoder); - } - - private void addExoplayerCodec( - String codecName, String mimeType, ShadowMediaCodec.CodecConfig codecConfig) { - addExoplayerCodec( - codecName, - mimeType, - codecConfig, /* profileLevels= */ ImmutableList.of(), /* colorFormats= */ ImmutableList.of()); } - private void addExoplayerCodec( + private void configureCodec( String codecName, String mimeType, - ShadowMediaCodec.CodecConfig codecConfig, List profileLevels, List colorFormats) { - addCodec(codecName, mimeType, codecConfig, profileLevels, colorFormats, /* isDecoder= */ true); - } - - private void addCodec( - String codecName, - String mimeType, - ShadowMediaCodec.CodecConfig codecConfig, - List profileLevels, - List colorFormats, - boolean isDecoder) { MediaFormat mediaFormat = new MediaFormat(); mediaFormat.setString(MediaFormat.KEY_MIME, mimeType); MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities = - MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder() - .setMediaFormat(mediaFormat) - .setIsEncoder(!isDecoder); + MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat); if (!profileLevels.isEmpty()) { capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0])); } if (!colorFormats.isEmpty()) { capabilities.setColorFormats(Ints.toArray(colorFormats)); } - ShadowMediaCodecList.addCodec( MediaCodecInfoBuilder.newBuilder() .setName(codecName) - .setIsEncoder(!isDecoder) .setCapabilities(capabilities.build()) .build()); - - if (isDecoder) { - ShadowMediaCodec.addDecoder(codecName, codecConfig); - } else { - ShadowMediaCodec.addEncoder(codecName, codecConfig); - } + // TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed + // to configure() so we don't have to specify large buffers here. + CodecImpl codec = new CodecImpl(mimeType); + ShadowMediaCodec.addDecoder( + codecName, + new ShadowMediaCodec.CodecConfig( + /* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec)); } private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index e60141e212..680910eb21 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -430,7 +430,7 @@ public final class Transformer { */ @CanIgnoreReturnValue @VisibleForTesting - public Builder setClock(Clock clock) { + /* package */ Builder setClock(Clock clock) { this.clock = clock; this.listeners = listeners.copy(looper, clock, (listener, flags) -> {}); return this; diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java similarity index 96% rename from libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java rename to libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java index a8d180e714..547a619a95 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package androidx.media3.test.utils; +package androidx.media3.transformer; import androidx.media3.common.Format; -import androidx.media3.common.util.UnstableApi; -import androidx.media3.transformer.Muxer; +import androidx.media3.test.utils.DumpableFormat; +import androidx.media3.test.utils.Dumper; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -28,7 +28,6 @@ import java.util.List; * testing purposes) and delegates the actual muxing operations to another {@link Muxer} created * using the factory provided. */ -@UnstableApi public final class TestMuxer implements Muxer, Dumper.Dumpable { private final Muxer muxer; diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java index c00eefd7a1..986ba3b388 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -29,11 +29,15 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; +import android.media.MediaCrypto; +import android.media.MediaFormat; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.view.Surface; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.MediaItem; @@ -49,13 +53,14 @@ import androidx.media3.extractor.ExtractorOutput; import androidx.media3.extractor.ExtractorsFactory; import androidx.media3.extractor.PositionHolder; import androidx.media3.test.utils.DumpFileAsserts; -import androidx.media3.test.utils.TestTransformerBuilderFactory; -import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig; +import androidx.media3.test.utils.FakeClock; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.primitives.Ints; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -69,17 +74,16 @@ import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.compatqual.NullableType; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.shadows.MediaCodecInfoBuilder; +import org.robolectric.shadows.ShadowMediaCodec; +import org.robolectric.shadows.ShadowMediaCodecList; /** End-to-end test for {@link Transformer}. */ @RunWith(AndroidJUnit4.class) public final class TransformerEndToEndTest { - @Rule - public final ShadowMediaCodecConfig mediaCodecConfig = ShadowMediaCodecConfig.forTranscoding(); - private static final String ASSET_URI_PREFIX = "asset:///media/"; private static final String FILE_VIDEO_ONLY = "mp4/sample_18byte_nclx_colr.mp4"; private static final String FILE_AUDIO_VIDEO = "mp4/sample.mp4"; @@ -96,39 +100,39 @@ public final class TransformerEndToEndTest { private Context context; private String outputPath; + private TestMuxer testMuxer; + private FakeClock clock; private ProgressHolder progressHolder; - private TestTransformerBuilderFactory testTransformerBuilderFactory; @Before public void setUp() throws Exception { context = ApplicationProvider.getApplicationContext(); outputPath = Util.createTempFile(context, "TransformerTest").getPath(); + clock = new FakeClock(/* isAutoAdvancing= */ true); progressHolder = new ProgressHolder(); - testTransformerBuilderFactory = new TestTransformerBuilderFactory(context); + createEncodersAndDecoders(); } @After public void tearDown() throws Exception { Files.delete(Paths.get(outputPath)); + removeEncodersAndDecoders(); } @Test public void startTransformation_videoOnlyPassthrough_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_VIDEO_ONLY)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY)); } @Test public void startTransformation_audioOnlyPassthrough_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER); @@ -136,16 +140,13 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); + context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); } @Test public void startTransformation_audioOnlyTranscoding_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer @@ -157,29 +158,24 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac")); + context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac")); } @Test public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = new MediaItem.Builder() .setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S) @@ -196,15 +192,14 @@ public final class TransformerEndToEndTest { DumpFileAsserts.assertOutput( context, - testTransformerBuilderFactory.getTestMuxer(), + testMuxer, getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped")); } @Test public void startTransformation_withSubtitles_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build()) .build(); @@ -213,17 +208,13 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_WITH_SUBTITLES)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES)); } @Test public void startTransformation_successiveTransformations_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); // Transform first media item. @@ -235,14 +226,12 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_concurrentTransformations_throwsError() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); @@ -254,44 +243,33 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_removeAudio_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) - .setRemoveAudio(true) - .build(); + createTransformerBuilder(/* enableFallback= */ false).setRemoveAudio(true).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio")); + context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio")); } @Test public void startTransformation_removeVideo_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) - .setRemoveVideo(true) - .build(); + createTransformerBuilder(/* enableFallback= */ false).setRemoveVideo(true).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); + context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); } @Test public void startTransformation_silentAudio_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .experimentalSetForceSilentAudio(true) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -300,9 +278,7 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio")); + context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio")); } @Test @@ -310,8 +286,7 @@ public final class TransformerEndToEndTest { SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); sonicAudioProcessor.setOutputSampleRateHz(48000); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setAudioProcessors(ImmutableList.of(sonicAudioProcessor)) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -320,9 +295,7 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz")); + context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz")); } @Test @@ -331,8 +304,7 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -353,8 +325,7 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -381,8 +352,7 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ true) + createTransformerBuilder(/* enableFallback= */ true) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -407,8 +377,7 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer1 = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -427,8 +396,7 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) .build(); @@ -437,10 +405,7 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_WITH_SEF_SLOW_MOTION)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION)); } @Test @@ -455,10 +420,7 @@ public final class TransformerEndToEndTest { } }; Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) - .addListener(listener) - .build(); + createTransformerBuilder(/* enableFallback= */ false).addListener(listener).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); @@ -474,8 +436,7 @@ public final class TransformerEndToEndTest { public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType( @@ -496,8 +457,7 @@ public final class TransformerEndToEndTest { public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError() throws Exception { Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer @@ -515,8 +475,7 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_withIoError_completesWithError() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); transformer.startTransformation(mediaItem, outputPath); @@ -536,19 +495,14 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ false) - .addListener(mockListener) - .build(); + createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); + context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); verify(mockListener) .onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest); } @@ -562,19 +516,14 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - testTransformerBuilderFactory - .create(/* enableFallback= */ true) - .addListener(mockListener) - .build(); + createTransformerBuilder(/* enableFallback= */ true).addListener(mockListener).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, - testTransformerBuilderFactory.getTestMuxer(), - getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); + context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); verify(mockListener) .onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest); } @@ -584,11 +533,11 @@ public final class TransformerEndToEndTest { MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory( context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10)); + Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1); Transformer transformer = - testTransformerBuilderFactory - .setMaxDelayBetweenSamplesMs(1) - .create(/* enableFallback= */ false) + createTransformerBuilder(/* enableFallback= */ false) .setMediaSourceFactory(mediaSourceFactory) + .setMuxerFactory(muxerFactory) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -602,24 +551,20 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_withUnsetMaxDelayBetweenSamples_completesSuccessfully() throws Exception { + Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET); Transformer transformer = - testTransformerBuilderFactory - .setMaxDelayBetweenSamplesMs(C.TIME_UNSET) - .create(/* enableFallback= */ false) - .build(); + createTransformerBuilder(/* enableFallback= */ false).setMuxerFactory(muxerFactory).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_afterCancellation_completesSuccessfully() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); @@ -630,8 +575,7 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); } @Test @@ -640,7 +584,7 @@ public final class TransformerEndToEndTest { anotherThread.start(); Looper looper = anotherThread.getLooper(); Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).setLooper(looper).build(); + createTransformerBuilder(/* enableFallback= */ false).setLooper(looper).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); AtomicReference exception = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -660,14 +604,12 @@ public final class TransformerEndToEndTest { countDownLatch.await(); assertThat(exception.get()).isNull(); - DumpFileAsserts.assertOutput( - context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_fromWrongThread_throwsError() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); @@ -692,8 +634,7 @@ public final class TransformerEndToEndTest { @Test public void getProgress_knownDuration_returnsConsistentStates() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); AtomicInteger previousProgressState = new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); @@ -739,8 +680,7 @@ public final class TransformerEndToEndTest { @Test public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); List progresses = new ArrayList<>(); Handler progressHandler = @@ -775,8 +715,7 @@ public final class TransformerEndToEndTest { @Test public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); @Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder); @@ -790,8 +729,7 @@ public final class TransformerEndToEndTest { @Test public void getProgress_unknownDuration_returnsConsistentStates() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION); AtomicInteger previousProgressState = new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); @@ -834,8 +772,7 @@ public final class TransformerEndToEndTest { @Test public void getProgress_fromWrongThread_throwsError() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -859,8 +796,7 @@ public final class TransformerEndToEndTest { @Test public void cancel_afterCompletion_doesNotThrow() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); @@ -870,8 +806,7 @@ public final class TransformerEndToEndTest { @Test public void cancel_fromWrongThread_throwsError() throws Exception { - Transformer transformer = - testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -893,10 +828,147 @@ public final class TransformerEndToEndTest { assertThat(illegalStateException.get()).isNotNull(); } + private Transformer.Builder createTransformerBuilder(boolean enableFallback) { + return new Transformer.Builder(context) + .setClock(clock) + .setMuxerFactory(new TestMuxerFactory()) + .setEncoderFactory( + new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build()); + } + + private static void createEncodersAndDecoders() { + ShadowMediaCodec.CodecConfig codecConfig = + new ShadowMediaCodec.CodecConfig( + /* inputBufferSize= */ 10_000, + /* outputBufferSize= */ 10_000, + /* codec= */ (in, out) -> out.put(in)); + addCodec( + MimeTypes.AUDIO_AAC, + codecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ true); + addCodec( + MimeTypes.AUDIO_AC3, + codecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ true); + addCodec( + MimeTypes.AUDIO_AMR_NB, + codecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ true); + addCodec( + MimeTypes.AUDIO_AAC, + codecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ false); + + ShadowMediaCodec.CodecConfig throwingCodecConfig = + new ShadowMediaCodec.CodecConfig( + /* inputBufferSize= */ 10_000, + /* outputBufferSize= */ 10_000, + new ShadowMediaCodec.CodecConfig.Codec() { + + @Override + public void process(ByteBuffer in, ByteBuffer out) { + out.put(in); + } + + @Override + public void onConfigured( + MediaFormat format, + @Nullable Surface surface, + @Nullable MediaCrypto crypto, + int flags) { + throw new IllegalArgumentException("Format unsupported"); + } + }); + + addCodec( + MimeTypes.AUDIO_AMR_WB, + throwingCodecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ true); + addCodec( + MimeTypes.AUDIO_AMR_NB, + throwingCodecConfig, + /* colorFormats= */ ImmutableList.of(), + /* isDecoder= */ false); + } + + private static void addCodec( + String mimeType, + ShadowMediaCodec.CodecConfig codecConfig, + List colorFormats, + boolean isDecoder) { + String codecName = + Util.formatInvariant( + isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-')); + if (isDecoder) { + ShadowMediaCodec.addDecoder(codecName, codecConfig); + } else { + ShadowMediaCodec.addEncoder(codecName, codecConfig); + } + + MediaFormat mediaFormat = new MediaFormat(); + mediaFormat.setString(MediaFormat.KEY_MIME, mimeType); + MediaCodecInfoBuilder.CodecCapabilitiesBuilder codecCapabilities = + MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder() + .setMediaFormat(mediaFormat) + .setIsEncoder(!isDecoder); + + if (!colorFormats.isEmpty()) { + codecCapabilities.setColorFormats(Ints.toArray(colorFormats)); + } + + ShadowMediaCodecList.addCodec( + MediaCodecInfoBuilder.newBuilder() + .setName(codecName) + .setIsEncoder(!isDecoder) + .setCapabilities(codecCapabilities.build()) + .build()); + } + + private static void removeEncodersAndDecoders() { + ShadowMediaCodec.clearCodecs(); + ShadowMediaCodecList.reset(); + EncoderUtil.clearCachedEncoders(); + } + private static String getDumpFileName(String originalFileName) { return DUMP_FILE_OUTPUT_DIRECTORY + '/' + originalFileName + '.' + DUMP_FILE_EXTENSION; } + private final class TestMuxerFactory implements Muxer.Factory { + + private final Muxer.Factory defaultMuxerFactory; + + public TestMuxerFactory() { + defaultMuxerFactory = new DefaultMuxer.Factory(); + } + + public TestMuxerFactory(long maxDelayBetweenSamplesMs) { + defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs); + } + + @Override + public Muxer create(String path) throws Muxer.MuxerException { + testMuxer = new TestMuxer(path, defaultMuxerFactory); + return testMuxer; + } + + @Override + public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException { + testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory); + return testMuxer; + } + + @Override + public ImmutableList getSupportedSampleMimeTypes(@C.TrackType int trackType) { + return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType); + } + } + private static final class SlowExtractorsFactory implements ExtractorsFactory { private final long delayBetweenReadsMs;