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