diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d6eba89c0e..8cd53af055 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -69,6 +69,11 @@ * Leanback extension: * Cast extension: * Test Utilities: + * Removed `transformer.TestUtil.addAudioDecoders(String...)`, + `transformer.TestUtil.addAudioEncoders(String...)`, and + `transformer.TestUtil.addAudioEncoders(ShadowMediaCodec.CodecConfig, + String...)`. Use `ShadowMediaCodecConfig` to configure shadow encoders + and decoders instead. * Demo app: * Add `PlaybackSpeedPopUpButton` Composable UI element to be part of `ExtraControls` in `demo-compose`. 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 dd10d5d6b3..f38153060b 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 @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Ints; import java.nio.ByteBuffer; +import java.util.List; import org.junit.rules.ExternalResource; import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.ShadowMediaCodec; @@ -132,6 +133,8 @@ public final class ShadowMediaCodecConfig extends ExternalResource { new CodecInfo(/* codecName= */ "exotest.audio.ac3", MimeTypes.AUDIO_AC3); public static final CodecInfo CODEC_INFO_AC4 = new CodecInfo(/* codecName= */ "exotest.audio.ac4", MimeTypes.AUDIO_AC4); + public static final CodecInfo CODEC_INFO_AMR_NB = + new CodecInfo(/* codecName= */ "exotest.audio.amrnb", MimeTypes.AUDIO_AMR_NB); public static final CodecInfo CODEC_INFO_E_AC3 = new CodecInfo(/* codecName= */ "exotest.audio.eac3", MimeTypes.AUDIO_E_AC3); public static final CodecInfo CODEC_INFO_E_AC3_JOC = @@ -173,7 +176,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource { /** * @deprecated Use {@link ShadowMediaCodecConfig#withAllDefaultSupportedCodecs()} instead. */ - // TODO(b/399861060): Remove in Media3 1.8. + // TODO(b/406437316): Remove in Media3 1.8. @Deprecated public static ShadowMediaCodecConfig forAllSupportedMimeTypes() { return withAllDefaultSupportedCodecs(); @@ -194,7 +197,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource { /** * @deprecated Use {@link ShadowMediaCodecConfig#withNoDefaultSupportedCodecs()} instead. */ - // TODO(b/399861060): Remove in Media3 1.8. + // TODO(b/406437316): Remove in Media3 1.8. @Deprecated public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() { return withNoDefaultSupportedCodecs(); @@ -205,6 +208,20 @@ public final class ShadowMediaCodecConfig extends ExternalResource { return new ShadowMediaCodecConfig(ImmutableSet.of()); } + /** + * Returns a {@link ShadowMediaCodecConfig} instance configured with the provided {@code decoders} + * and {@code encoders}. + * + *

All codecs will work as passthrough, regardless of type. + */ + public static ShadowMediaCodecConfig withCodecs( + List decoders, List encoders) { + ImmutableSet.Builder codecs = new ImmutableSet.Builder<>(); + codecs.addAll(createDecoders(decoders, /* forcePassthrough= */ true)); + codecs.addAll(createEncoders(encoders)); + return new ShadowMediaCodecConfig(codecs.build()); + } + private final ImmutableSet defaultCodecs; private ShadowMediaCodecConfig(ImmutableSet defaultCodecs) { @@ -267,6 +284,39 @@ public final class ShadowMediaCodecConfig extends ExternalResource { } } + /** + * Configures and publishes {@linkplain ShadowMediaCodec shadow encoders} based on {@code + * encoders}. + * + *

This method configures pass-through encoders. + */ + public void addEncoders(CodecInfo... encoders) { + for (CodecInfo encoderInfo : encoders) { + CodecImpl encoder = CodecImpl.createEncoder(encoderInfo); + encoder.configure(); + } + } + + /** + * Configures and publishes a {@link ShadowMediaCodec} codec. + * + *

Input buffers are handled according to the {@link ShadowMediaCodec.CodecConfig} provided. + * + * @param codecInfo Basic codec information. + * @param isEncoder Whether the codecs registered are encoders or decoders. + * @param codecConfig Codec configuration implementation of the shadow. + */ + public void addCodec( + CodecInfo codecInfo, boolean isEncoder, ShadowMediaCodec.CodecConfig codecConfig) { + configureShadowMediaCodec( + codecInfo.codecName, + codecInfo.mimeType, + isEncoder, + codecInfo.profileLevels, + codecInfo.colorFormats, + codecConfig); + } + @Override protected void before() throws Throwable { for (CodecImpl codec : this.defaultCodecs) { @@ -282,7 +332,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource { } private static ImmutableSet createDecoders( - ImmutableList decoderInfos, boolean forcePassthrough) { + List decoderInfos, boolean forcePassthrough) { ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); for (CodecInfo info : decoderInfos) { if (!forcePassthrough && MimeTypes.isAudio(info.mimeType)) { @@ -294,6 +344,14 @@ public final class ShadowMediaCodecConfig extends ExternalResource { return builder.build(); } + private static ImmutableSet createEncoders(List encoderInfos) { + ImmutableSet.Builder builder = new ImmutableSet.Builder<>(); + for (CodecInfo info : encoderInfos) { + builder.add(CodecImpl.createEncoder(info)); + } + return builder.build(); + } + /** * A {@link ShadowMediaCodec.CodecConfig.Codec} that provides pass-through or frame dropping * encoders and decoders. @@ -312,6 +370,10 @@ public final class ShadowMediaCodecConfig extends ExternalResource { return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ false); } + public static CodecImpl createEncoder(CodecInfo codecInfo) { + return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ true); + } + /** * Creates an instance. * diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/CompositionExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/CompositionExportTest.java index 03129879d7..8bb1063882 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/CompositionExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/CompositionExportTest.java @@ -15,6 +15,8 @@ */ package androidx.media3.transformer; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; @@ -23,25 +25,22 @@ import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S; import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; -import static androidx.media3.transformer.TestUtil.addAudioDecoders; -import static androidx.media3.transformer.TestUtil.addAudioEncoders; import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor; import static androidx.media3.transformer.TestUtil.getCompositionDumpFilePath; import static androidx.media3.transformer.TestUtil.getDumpFileName; -import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import androidx.media3.common.MediaItem; -import androidx.media3.common.MimeTypes; import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.TestTransformerBuilder; +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 org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -57,15 +56,16 @@ public class CompositionExportTest { private final Context context = ApplicationProvider.getApplicationContext(); - @Before - public void setUp() { - addAudioDecoders(MimeTypes.AUDIO_RAW); - addAudioEncoders(MimeTypes.AUDIO_AAC); - } + @Rule + public ShadowMediaCodecConfig shadowMediaCodecConfig = + ShadowMediaCodecConfig.withCodecs( + /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), + /* encoders= */ ImmutableList.of(CODEC_INFO_AAC)); @After public void tearDown() { - removeEncodersAndDecoders(); + // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method. + EncoderUtil.clearCachedEncoders(); } @Test diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java index 0efded45dc..bd9ee8f8e0 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java @@ -17,6 +17,9 @@ package androidx.media3.transformer; import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AMR_NB; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED; import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA; @@ -36,12 +39,9 @@ import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ELST_TRIM_IDR_DURA import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION; import static androidx.media3.transformer.TestUtil.FILE_WITH_SUBTITLES; -import static androidx.media3.transformer.TestUtil.addAudioDecoders; -import static androidx.media3.transformer.TestUtil.addAudioEncoders; import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.getDumpFileName; -import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders; import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE; import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED; import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE; @@ -89,6 +89,7 @@ import androidx.media3.extractor.PositionHolder; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.TestTransformerBuilder; +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; @@ -105,7 +106,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -125,24 +125,42 @@ import org.robolectric.shadows.ShadowMediaCodec; public final class MediaItemExportTest { private static final long TEST_TIMEOUT_SECONDS = 10; + private static final String EXPECTED_CODEC_EXCEPTION_MESSAGE = "Unexpected format!"; + private static final ShadowMediaCodec.CodecConfig THROWING_CODEC_CONFIG = + new ShadowMediaCodec.CodecConfig( + /* inputBufferSize= */ 100_000, + /* outputBufferSize= */ 100_000, + new ShadowMediaCodec.CodecConfig.Codec() { + @Override + public void process(ByteBuffer byteBuffer, ByteBuffer byteBuffer1) { + throw new IllegalStateException(); + } + + @Override + public void onConfigured( + MediaFormat format, Surface surface, MediaCrypto crypto, int flags) { + throw new IllegalArgumentException(EXPECTED_CODEC_EXCEPTION_MESSAGE); + } + }); @Rule public final TemporaryFolder outputDir = new TemporaryFolder(); private final Context context = ApplicationProvider.getApplicationContext(); - @Before - public void setUp() { - addAudioDecoders(MimeTypes.AUDIO_RAW); - addAudioEncoders(MimeTypes.AUDIO_AAC); - } + @Rule + public ShadowMediaCodecConfig shadowMediaCodecConfig = + ShadowMediaCodecConfig.withCodecs( + /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), /* encoders= */ ImmutableList.of()); @After public void tearDown() { - removeEncodersAndDecoders(); + // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method. + EncoderUtil.clearCachedEncoders(); } @Test public void start_gapOnlyExport_outputsSilence() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer transformer = new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); @@ -275,6 +293,7 @@ public final class MediaItemExportTest { @Test public void start_trimOptimizationEnabled_fileNotMp4_fallbackToNormalExport() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer transformer = new TestTransformerBuilder(context) @@ -441,6 +460,7 @@ public final class MediaItemExportTest { @Test public void start_forceAudioTrackAndRemoveAudioWithEffects_generatesSilentAudio() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer transformer = new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); @@ -497,6 +517,7 @@ public final class MediaItemExportTest { @Test public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer transformer = new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); @@ -521,6 +542,7 @@ public final class MediaItemExportTest { @Test public void exportAudio_muxerReceivesExpectedNumberOfBytes() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); AtomicInteger bytesSeenByEffect = new AtomicInteger(); Transformer transformer = @@ -540,6 +562,7 @@ public final class MediaItemExportTest { @Test public void start_adjustSampleRate_completesSuccessfully() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); sonicAudioProcessor.setOutputSampleRateHz(48000); @@ -569,6 +592,7 @@ public final class MediaItemExportTest { @Test public void adjustAudioSpeed_toDoubleSpeed_returnsExpectedNumberOfSamples() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); sonicAudioProcessor.setSpeed(2f); @@ -599,6 +623,7 @@ public final class MediaItemExportTest { @Test public void start_withRawBigEndianAudioInput_completesSuccessfully() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); ToInt16PcmAudioProcessor toInt16PcmAudioProcessor = new ToInt16PcmAudioProcessor(); Transformer transformer = @@ -622,6 +647,7 @@ public final class MediaItemExportTest { @Test public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); sonicAudioProcessor.setOutputSampleRateHz(48000); @@ -702,6 +728,7 @@ public final class MediaItemExportTest { @Test public void start_withMultipleListeners_callsEachOnFallback() throws Exception { + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); ArgumentCaptor compositionArgumentCaptor = ArgumentCaptor.forClass(Composition.class); @@ -804,29 +831,11 @@ public final class MediaItemExportTest { @Test public void start_whenCodecFailsToConfigure_completesWithError() throws Exception { - CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); - String expectedFailureMessage = "Format not valid. AMR NB (3gpp)"; - ShadowMediaCodec.CodecConfig throwOnConfigureCodecConfig = - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 100_000, - /* outputBufferSize= */ 100_000, - /* codec= */ new ShadowMediaCodec.CodecConfig.Codec() { - @Override - public void process(ByteBuffer in, ByteBuffer out) { - out.put(in); - } - - @Override - public void onConfigured( - MediaFormat format, Surface surface, MediaCrypto crypto, int flags) { - // MediaCodec#configure documented to throw IAE if format is invalid. - throw new IllegalArgumentException(expectedFailureMessage); - } - }); - + shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC); // Add the AMR_NB encoder that throws when configured. - addAudioEncoders(throwOnConfigureCodecConfig, MimeTypes.AUDIO_AMR_NB); - + shadowMediaCodecConfig.addCodec( + CODEC_INFO_AMR_NB, /* isEncoder= */ true, THROWING_CODEC_CONFIG); + CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); Transformer transformer = new TestTransformerBuilder(context) .setMuxerFactory(muxerFactory) @@ -841,7 +850,10 @@ public final class MediaItemExportTest { assertThat(exception.errorCode) .isEqualTo(ExportException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED); assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); - assertThat(exception).hasCauseThat().hasMessageThat().isEqualTo(expectedFailureMessage); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo(EXPECTED_CODEC_EXCEPTION_MESSAGE); } @Test @@ -866,11 +878,9 @@ public final class MediaItemExportTest { public void start_withAudioFormatUnsupportedByMuxer_ignoresDisabledFallbackAndCompletesSuccessfully() throws Exception { - removeEncodersAndDecoders(); - addAudioDecoders(MimeTypes.AUDIO_RAW); // RAW supported by encoder, unsupported by muxer. // AAC supported by encoder and muxer. - addAudioEncoders(MimeTypes.AUDIO_RAW, MimeTypes.AUDIO_AAC); + shadowMediaCodecConfig.addEncoders(CODEC_INFO_RAW, CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer.Listener mockListener = mock(Transformer.Listener.class); @@ -901,11 +911,9 @@ public final class MediaItemExportTest { @Test public void start_withAudioFormatUnsupportedByMuxer_fallsBackAndCompletesSuccessfully() throws Exception { - removeEncodersAndDecoders(); - addAudioDecoders(MimeTypes.AUDIO_RAW); // RAW supported by encoder, unsupported by muxer. // AAC supported by encoder and muxer. - addAudioEncoders(MimeTypes.AUDIO_RAW, MimeTypes.AUDIO_AAC); + shadowMediaCodecConfig.addEncoders(CODEC_INFO_RAW, CODEC_INFO_AAC); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); Transformer.Listener mockListener = mock(Transformer.Listener.class); @@ -1198,9 +1206,7 @@ public final class MediaItemExportTest { @Test public void analyze_audioOnlyWithItemEffect_completesSuccessfully() throws Exception { - removeEncodersAndDecoders(); - addAudioDecoders(MimeTypes.AUDIO_RAW); - addThrowingAudioEncoder(MimeTypes.AUDIO_AAC); + shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG); Transformer transformer = ExperimentalAnalyzerModeFactory.buildAnalyzer( getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); @@ -1220,9 +1226,7 @@ public final class MediaItemExportTest { @Test public void analyze_audioOnlyWithCompositionEffect_completesSuccessfully() throws Exception { - removeEncodersAndDecoders(); - addAudioDecoders(MimeTypes.AUDIO_RAW); - addThrowingAudioEncoder(MimeTypes.AUDIO_AAC); + shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG); Transformer transformer = ExperimentalAnalyzerModeFactory.buildAnalyzer( getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); @@ -1247,9 +1251,7 @@ public final class MediaItemExportTest { @Test public void analyze_audioOnly_itemAndMixerOutputMatch() throws Exception { - removeEncodersAndDecoders(); - addAudioDecoders(MimeTypes.AUDIO_RAW); - addThrowingAudioEncoder(MimeTypes.AUDIO_AAC); + shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG); Transformer transformer = ExperimentalAnalyzerModeFactory.buildAnalyzer( getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); @@ -1567,27 +1569,6 @@ public final class MediaItemExportTest { /* modifications...= */ "transmuxed")); } - private static void addThrowingAudioEncoder(String mimeType) { - ShadowMediaCodec.CodecConfig.Codec codec = - new ShadowMediaCodec.CodecConfig.Codec() { - @Override - public void process(ByteBuffer byteBuffer, ByteBuffer byteBuffer1) { - throw new IllegalStateException(); - } - - @Override - public void onConfigured( - MediaFormat format, Surface surface, MediaCrypto crypto, int flags) { - throw new IllegalStateException(); - } - }; - - addAudioEncoders( - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec), - mimeType); - } - private static AudioProcessor createByteCountingAudioProcessor(AtomicInteger byteCount) { return new TeeAudioProcessor( new TeeAudioProcessor.AudioBufferSink() { diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedAudioExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedAudioExportTest.java index fbea9bc478..29a84c221d 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedAudioExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedAudioExportTest.java @@ -16,24 +16,23 @@ package androidx.media3.transformer; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; -import static androidx.media3.transformer.TestUtil.addAudioDecoders; -import static androidx.media3.transformer.TestUtil.addAudioEncoders; import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath; -import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders; import static com.google.common.truth.Truth.assertThat; import static java.util.stream.Collectors.toList; import android.content.Context; import androidx.media3.common.MediaItem; -import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.TestTransformerBuilder; +import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig; import androidx.test.core.app.ApplicationProvider; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; @@ -46,7 +45,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -126,6 +124,12 @@ public final class ParameterizedAudioExportTest { @Rule public final TemporaryFolder outputDir = new TemporaryFolder(); + @Rule + public ShadowMediaCodecConfig shadowMediaCodecConfig = + ShadowMediaCodecConfig.withCodecs( + /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), + /* encoders= */ ImmutableList.of(CODEC_INFO_AAC)); + @Parameter public SequenceConfig sequence; private final Context context = ApplicationProvider.getApplicationContext(); @@ -133,15 +137,10 @@ public final class ParameterizedAudioExportTest { private final CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); - @Before - public void setUp() { - addAudioDecoders(MimeTypes.AUDIO_RAW); - addAudioEncoders(MimeTypes.AUDIO_AAC); - } - @After public void tearDown() { - removeEncodersAndDecoders(); + // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method. + EncoderUtil.clearCachedEncoders(); } @Test diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedItemExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedItemExportTest.java index bb6dbd28f7..c5bfacc4fe 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedItemExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/ParameterizedItemExportTest.java @@ -16,6 +16,8 @@ package androidx.media3.transformer; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; @@ -23,24 +25,20 @@ import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_STEREO_48000KH import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; -import static androidx.media3.transformer.TestUtil.addAudioDecoders; -import static androidx.media3.transformer.TestUtil.addAudioEncoders; import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor; import static androidx.media3.transformer.TestUtil.getDumpFileName; -import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders; import static org.junit.Assume.assumeFalse; import android.content.Context; import androidx.media3.common.MediaItem; -import androidx.media3.common.MimeTypes; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.TestTransformerBuilder; +import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig; import androidx.test.core.app.ApplicationProvider; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -93,17 +91,18 @@ public final class ParameterizedItemExportTest { private final Context context = ApplicationProvider.getApplicationContext(); - @Before - public void setUp() { - // Only add RAW decoder, so non-RAW audio has no options for decoding. - addAudioDecoders(MimeTypes.AUDIO_RAW); - // Use an AAC encoder because muxer supports AAC. - addAudioEncoders(MimeTypes.AUDIO_AAC); - } + // Only add RAW decoder, so non-RAW audio has no options for decoding. + // Use an AAC encoder because muxer supports AAC. + @Rule + public ShadowMediaCodecConfig shadowMediaCodecConfig = + ShadowMediaCodecConfig.withCodecs( + /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), + /* encoders= */ ImmutableList.of(CODEC_INFO_AAC)); @After public void tearDown() { - removeEncodersAndDecoders(); + // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method. + EncoderUtil.clearCachedEncoders(); } @Test diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/SequenceExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/SequenceExportTest.java index 5a0473fb9f..f67214cdfd 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/SequenceExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/SequenceExportTest.java @@ -16,36 +16,34 @@ package androidx.media3.transformer; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC; +import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_STEREO_48000KHZ; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S; -import static androidx.media3.transformer.TestUtil.addAudioDecoders; -import static androidx.media3.transformer.TestUtil.addAudioEncoders; import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createChannelCountChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.getDumpFileName; import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath; -import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.content.Context; import androidx.annotation.Nullable; import androidx.media3.common.MediaItem; -import androidx.media3.common.MimeTypes; import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.effect.RgbFilter; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.TestTransformerBuilder; +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 org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -69,15 +67,16 @@ public final class SequenceExportTest { private final Context context = ApplicationProvider.getApplicationContext(); - @Before - public void setUp() { - addAudioDecoders(MimeTypes.AUDIO_RAW); - addAudioEncoders(MimeTypes.AUDIO_AAC); - } + @Rule + public ShadowMediaCodecConfig shadowMediaCodecConfig = + ShadowMediaCodecConfig.withCodecs( + /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), + /* encoders= */ ImmutableList.of(CODEC_INFO_AAC)); @After public void tearDown() { - removeEncodersAndDecoders(); + // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method. + EncoderUtil.clearCachedEncoders(); } @Test diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TestUtil.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TestUtil.java index 3a6f158a88..8fee6f8e17 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TestUtil.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TestUtil.java @@ -15,20 +15,14 @@ */ package androidx.media3.transformer; -import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.configureShadowMediaCodec; - -import androidx.media3.common.MimeTypes; import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.audio.ChannelMixingAudioProcessor; import androidx.media3.common.audio.ChannelMixingMatrix; import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.util.UnstableApi; -import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.StringJoiner; -import org.robolectric.shadows.ShadowMediaCodec; -import org.robolectric.shadows.ShadowMediaCodecList; /** Utility class for {@link Transformer} unit tests */ @UnstableApi @@ -142,83 +136,4 @@ public final class TestUtil { + "." + DUMP_FILE_EXTENSION; } - - /** - * Adds an audio decoder for each {@linkplain MimeTypes mime type}. - * - *

Input buffers are copied directly to the output. - * - *

When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test class - * {@link org.junit.After @After} method. - */ - public static void addAudioDecoders(String... mimeTypes) { - for (String mimeType : mimeTypes) { - addCodec( - mimeType, - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 150_000, - /* outputBufferSize= */ 150_000, - /* codec= */ (in, out) -> out.put(in)), - /* colorFormats= */ ImmutableList.of(), - /* isDecoder= */ true); - } - } - - /** - * Adds an audio encoder for each {@linkplain MimeTypes mime type}. - * - *

Input buffers are copied directly to the output. - * - *

When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test class - * {@link org.junit.After @After} method. - */ - public static void addAudioEncoders(String... mimeTypes) { - addAudioEncoders( - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 150_000, - /* outputBufferSize= */ 150_000, - /* codec= */ (in, out) -> out.put(in)), - mimeTypes); - } - - /** - * Adds an audio encoder for each {@linkplain MimeTypes mime type}. - * - *

Input buffers are handled according to the {@link - * org.robolectric.shadows.ShadowMediaCodec.CodecConfig} provided. - * - *

When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test's - * {@link org.junit.After @After} method. - */ - public static void addAudioEncoders( - ShadowMediaCodec.CodecConfig codecConfig, String... mimeTypes) { - for (String mimeType : mimeTypes) { - addCodec( - mimeType, codecConfig, /* colorFormats= */ ImmutableList.of(), /* isDecoder= */ false); - } - } - - /** Clears all cached codecs. */ - public static void removeEncodersAndDecoders() { - ShadowMediaCodec.clearCodecs(); - ShadowMediaCodecList.reset(); - EncoderUtil.clearCachedEncoders(); - } - - private static void addCodec( - String mimeType, - ShadowMediaCodec.CodecConfig codecConfig, - ImmutableList colorFormats, - boolean isDecoder) { - String codecName = - Util.formatInvariant( - isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-')); - configureShadowMediaCodec( - codecName, - mimeType, - !isDecoder, - /* profileLevels= */ ImmutableList.of(), - colorFormats, - codecConfig); - } }