diff --git a/libraries/test_utils/build.gradle b/libraries/test_utils/build.gradle index d3901cf175..a5817b9f92 100644 --- a/libraries/test_utils/build.gradle +++ b/libraries/test_utils/build.gradle @@ -26,6 +26,7 @@ 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/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java similarity index 96% rename from libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java rename to libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java index 547a619a95..a8d180e714 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestMuxer.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package androidx.media3.transformer; +package androidx.media3.test.utils; import androidx.media3.common.Format; -import androidx.media3.test.utils.DumpableFormat; -import androidx.media3.test.utils.Dumper; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.transformer.Muxer; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +28,7 @@ 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/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java new file mode 100644 index 0000000000..82c6ae8ac9 --- /dev/null +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestTransformerBuilderFactory.java @@ -0,0 +1,107 @@ +/* + * 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 aafdce151a..e579a00166 100644 --- a/libraries/test_utils_robolectric/build.gradle +++ b/libraries/test_utils_robolectric/build.gradle @@ -20,6 +20,7 @@ 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 6e85eed2a0..fe5e61e8de 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,10 +16,15 @@ 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; @@ -30,105 +35,247 @@ import org.robolectric.shadows.ShadowMediaCodec; import org.robolectric.shadows.ShadowMediaCodecList; /** - * A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}. + * A JUnit @Rule to configure {@link ShadowMediaCodec} for transcoding or decoding. * - *

Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video - * MIME type known by ExoPlayer. + *

Registers {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} instances for ExoPlayer + * and Transformer tests. */ @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(); + return new ShadowMediaCodecConfig(/* forTranscoding= */ false); } @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); - configureCodec( - /* codecName= */ "exotest.video.avc", + addExoplayerCodec( + 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); - configureCodec( - /* codecName= */ "exotest.video.mpeg2", + addExoplayerCodec( + EXOTEST_VIDEO_MPEG2, MimeTypes.VIDEO_MPEG2, + generateDecodingCodecConfig(MimeTypes.VIDEO_MPEG2), ImmutableList.of(mpeg2ProfileLevel), ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - configureCodec( - /* codecName= */ "exotest.video.vp9", + addExoplayerCodec( + EXOTEST_VIDEO_VP9, MimeTypes.VIDEO_VP9, + generateDecodingCodecConfig(MimeTypes.VIDEO_VP9), ImmutableList.of(), ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); // Audio codecs - 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); + 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)); // 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. - configureCodec("exotest.audio.raw", MimeTypes.AUDIO_RAW); + addExoplayerCodec( + EXOTEST_AUDIO_RAW, MimeTypes.AUDIO_RAW, generateDecodingCodecConfig(MimeTypes.AUDIO_RAW)); } @Override protected void after() { - MediaCodecUtil.clearDecoderInfoCache(); + if (!forTranscoding) { + MediaCodecUtil.clearDecoderInfoCache(); + } else { + EncoderUtil.clearCachedEncoders(); + } ShadowMediaCodecList.reset(); ShadowMediaCodec.clearCodecs(); } - private void configureCodec(String codecName, String mimeType) { - configureCodec( + 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( 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 configureCodec( + private void addExoplayerCodec( 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); + MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder() + .setMediaFormat(mediaFormat) + .setIsEncoder(!isDecoder); 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()); - // 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)); + + if (isDecoder) { + ShadowMediaCodec.addDecoder(codecName, codecConfig); + } else { + ShadowMediaCodec.addEncoder(codecName, codecConfig); + } } 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 680910eb21..e60141e212 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 - /* package */ Builder setClock(Clock clock) { + public Builder setClock(Clock clock) { this.clock = clock; this.listeners = listeners.copy(looper, clock, (listener, flags) -> {}); return this; 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 986ba3b388..c00eefd7a1 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -29,15 +29,11 @@ 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; @@ -53,14 +49,13 @@ 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.FakeClock; +import androidx.media3.test.utils.TestTransformerBuilderFactory; +import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig; 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; @@ -74,16 +69,17 @@ 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"; @@ -100,39 +96,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(); - createEncodersAndDecoders(); + testTransformerBuilderFactory = new TestTransformerBuilderFactory(context); } @After public void tearDown() throws Exception { Files.delete(Paths.get(outputPath)); - removeEncodersAndDecoders(); } @Test public void startTransformation_videoOnlyPassthrough_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_VIDEO_ONLY)); } @Test public void startTransformation_audioOnlyPassthrough_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER); @@ -140,13 +136,16 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); } @Test public void startTransformation_audioOnlyTranscoding_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer @@ -158,24 +157,29 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac")); } @Test public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = new MediaItem.Builder() .setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S) @@ -192,14 +196,15 @@ public final class TransformerEndToEndTest { DumpFileAsserts.assertOutput( context, - testMuxer, + testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped")); } @Test public void startTransformation_withSubtitles_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build()) .build(); @@ -208,13 +213,17 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES)); + DumpFileAsserts.assertOutput( + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_WITH_SUBTITLES)); } @Test public void startTransformation_successiveTransformations_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); // Transform first media item. @@ -226,12 +235,14 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_concurrentTransformations_throwsError() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); @@ -243,33 +254,44 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_removeAudio_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).setRemoveAudio(true).build(); + testTransformerBuilderFactory + .create(/* 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, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio")); } @Test public void startTransformation_removeVideo_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).setRemoveVideo(true).build(); + testTransformerBuilderFactory + .create(/* 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, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); } @Test public void startTransformation_silentAudio_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .experimentalSetForceSilentAudio(true) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -278,7 +300,9 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio")); } @Test @@ -286,7 +310,8 @@ public final class TransformerEndToEndTest { SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); sonicAudioProcessor.setOutputSampleRateHz(48000); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setAudioProcessors(ImmutableList.of(sonicAudioProcessor)) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -295,7 +320,9 @@ public final class TransformerEndToEndTest { TransformerTestRunner.runUntilCompleted(transformer); DumpFileAsserts.assertOutput( - context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz")); } @Test @@ -304,7 +331,8 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -325,7 +353,8 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -352,7 +381,8 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ true) + testTransformerBuilderFactory + .create(/* enableFallback= */ true) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -377,7 +407,8 @@ public final class TransformerEndToEndTest { Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer transformer1 = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .addListener(mockListener1) .addListener(mockListener2) .addListener(mockListener3) @@ -396,7 +427,8 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) .build(); @@ -405,7 +437,10 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION)); + DumpFileAsserts.assertOutput( + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_WITH_SEF_SLOW_MOTION)); } @Test @@ -420,7 +455,10 @@ public final class TransformerEndToEndTest { } }; Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).addListener(listener).build(); + testTransformerBuilderFactory + .create(/* enableFallback= */ false) + .addListener(listener) + .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); @@ -436,7 +474,8 @@ public final class TransformerEndToEndTest { public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType( @@ -457,7 +496,8 @@ public final class TransformerEndToEndTest { public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError() throws Exception { Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .create(/* enableFallback= */ false) .setTransformationRequest( new TransformationRequest.Builder() .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer @@ -475,7 +515,8 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_withIoError_completesWithError() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); transformer.startTransformation(mediaItem, outputPath); @@ -495,14 +536,19 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build(); + testTransformerBuilderFactory + .create(/* 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, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); verify(mockListener) .onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest); } @@ -516,14 +562,19 @@ public final class TransformerEndToEndTest { TransformationRequest fallbackTransformationRequest = new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ true).addListener(mockListener).build(); + testTransformerBuilderFactory + .create(/* 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, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); + context, + testTransformerBuilderFactory.getTestMuxer(), + getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); verify(mockListener) .onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest); } @@ -533,11 +584,11 @@ public final class TransformerEndToEndTest { MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory( context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10)); - Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) + testTransformerBuilderFactory + .setMaxDelayBetweenSamplesMs(1) + .create(/* enableFallback= */ false) .setMediaSourceFactory(mediaSourceFactory) - .setMuxerFactory(muxerFactory) .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); @@ -551,20 +602,24 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_withUnsetMaxDelayBetweenSamples_completesSuccessfully() throws Exception { - Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).setMuxerFactory(muxerFactory).build(); + testTransformerBuilderFactory + .setMaxDelayBetweenSamplesMs(C.TIME_UNSET) + .create(/* enableFallback= */ false) + .build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_afterCancellation_completesSuccessfully() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); transformer.startTransformation(mediaItem, outputPath); @@ -575,7 +630,8 @@ public final class TransformerEndToEndTest { transformer.startTransformation(mediaItem, outputPath); TransformerTestRunner.runUntilCompleted(transformer); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); } @Test @@ -584,7 +640,7 @@ public final class TransformerEndToEndTest { anotherThread.start(); Looper looper = anotherThread.getLooper(); Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false).setLooper(looper).build(); + testTransformerBuilderFactory.create(/* enableFallback= */ false).setLooper(looper).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); AtomicReference exception = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -604,12 +660,14 @@ public final class TransformerEndToEndTest { countDownLatch.await(); assertThat(exception.get()).isNull(); - DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); + DumpFileAsserts.assertOutput( + context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO)); } @Test public void startTransformation_fromWrongThread_throwsError() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); @@ -634,7 +692,8 @@ public final class TransformerEndToEndTest { @Test public void getProgress_knownDuration_returnsConsistentStates() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); AtomicInteger previousProgressState = new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); @@ -680,7 +739,8 @@ public final class TransformerEndToEndTest { @Test public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); List progresses = new ArrayList<>(); Handler progressHandler = @@ -715,7 +775,8 @@ public final class TransformerEndToEndTest { @Test public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); @Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder); @@ -729,7 +790,8 @@ public final class TransformerEndToEndTest { @Test public void getProgress_unknownDuration_returnsConsistentStates() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION); AtomicInteger previousProgressState = new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); @@ -772,7 +834,8 @@ public final class TransformerEndToEndTest { @Test public void getProgress_fromWrongThread_throwsError() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -796,7 +859,8 @@ public final class TransformerEndToEndTest { @Test public void cancel_afterCompletion_doesNotThrow() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); transformer.startTransformation(mediaItem, outputPath); @@ -806,7 +870,8 @@ public final class TransformerEndToEndTest { @Test public void cancel_fromWrongThread_throwsError() throws Exception { - Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); + Transformer transformer = + testTransformerBuilderFactory.create(/* enableFallback= */ false).build(); HandlerThread anotherThread = new HandlerThread("AnotherThread"); AtomicReference illegalStateException = new AtomicReference<>(); CountDownLatch countDownLatch = new CountDownLatch(1); @@ -828,147 +893,10 @@ 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;