mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Add encoder support to ShadowMediaCodecConfig
This is a step towards unifying ShadowMediaCodecConfig structure to accommodate having both ExoPlayer and Transcoding codecs configuration. This includes: * Adding ability to configure encoders by calling `addEncoders(CodecInfo...)` * A new factory method that takes specific encoders and decoders CodecInfos * A new method `addCodecs(boolean, CodecConfig, CodecInfo...) that configures codecs with specified behavior by passing a `CodecConfig`. This CL also includes migrating Transformer tests to ShadowMediaCodecConfig. PiperOrigin-RevId: 747390451
This commit is contained in:
parent
ed56ed22fb
commit
07be60ed93
@ -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`.
|
||||
|
@ -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}.
|
||||
*
|
||||
* <p>All codecs will work as passthrough, regardless of type.
|
||||
*/
|
||||
public static ShadowMediaCodecConfig withCodecs(
|
||||
List<CodecInfo> decoders, List<CodecInfo> encoders) {
|
||||
ImmutableSet.Builder<CodecImpl> codecs = new ImmutableSet.Builder<>();
|
||||
codecs.addAll(createDecoders(decoders, /* forcePassthrough= */ true));
|
||||
codecs.addAll(createEncoders(encoders));
|
||||
return new ShadowMediaCodecConfig(codecs.build());
|
||||
}
|
||||
|
||||
private final ImmutableSet<CodecImpl> defaultCodecs;
|
||||
|
||||
private ShadowMediaCodecConfig(ImmutableSet<CodecImpl> defaultCodecs) {
|
||||
@ -267,6 +284,39 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures and publishes {@linkplain ShadowMediaCodec shadow encoders} based on {@code
|
||||
* encoders}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<CodecImpl> createDecoders(
|
||||
ImmutableList<CodecInfo> decoderInfos, boolean forcePassthrough) {
|
||||
List<CodecInfo> decoderInfos, boolean forcePassthrough) {
|
||||
ImmutableSet.Builder<CodecImpl> 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<CodecImpl> createEncoders(List<CodecInfo> encoderInfos) {
|
||||
ImmutableSet.Builder<CodecImpl> 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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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<Composition> 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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}.
|
||||
*
|
||||
* <p>Input buffers are copied directly to the output.
|
||||
*
|
||||
* <p>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}.
|
||||
*
|
||||
* <p>Input buffers are copied directly to the output.
|
||||
*
|
||||
* <p>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}.
|
||||
*
|
||||
* <p>Input buffers are handled according to the {@link
|
||||
* org.robolectric.shadows.ShadowMediaCodec.CodecConfig} provided.
|
||||
*
|
||||
* <p>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<Integer> 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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user