mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +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:
|
* Leanback extension:
|
||||||
* Cast extension:
|
* Cast extension:
|
||||||
* Test Utilities:
|
* 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:
|
* Demo app:
|
||||||
* Add `PlaybackSpeedPopUpButton` Composable UI element to be part of
|
* Add `PlaybackSpeedPopUpButton` Composable UI element to be part of
|
||||||
`ExtraControls` in `demo-compose`.
|
`ExtraControls` in `demo-compose`.
|
||||||
|
@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.rules.ExternalResource;
|
import org.junit.rules.ExternalResource;
|
||||||
import org.robolectric.shadows.MediaCodecInfoBuilder;
|
import org.robolectric.shadows.MediaCodecInfoBuilder;
|
||||||
import org.robolectric.shadows.ShadowMediaCodec;
|
import org.robolectric.shadows.ShadowMediaCodec;
|
||||||
@ -132,6 +133,8 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
new CodecInfo(/* codecName= */ "exotest.audio.ac3", MimeTypes.AUDIO_AC3);
|
new CodecInfo(/* codecName= */ "exotest.audio.ac3", MimeTypes.AUDIO_AC3);
|
||||||
public static final CodecInfo CODEC_INFO_AC4 =
|
public static final CodecInfo CODEC_INFO_AC4 =
|
||||||
new CodecInfo(/* codecName= */ "exotest.audio.ac4", MimeTypes.AUDIO_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 =
|
public static final CodecInfo CODEC_INFO_E_AC3 =
|
||||||
new CodecInfo(/* codecName= */ "exotest.audio.eac3", MimeTypes.AUDIO_E_AC3);
|
new CodecInfo(/* codecName= */ "exotest.audio.eac3", MimeTypes.AUDIO_E_AC3);
|
||||||
public static final CodecInfo CODEC_INFO_E_AC3_JOC =
|
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.
|
* @deprecated Use {@link ShadowMediaCodecConfig#withAllDefaultSupportedCodecs()} instead.
|
||||||
*/
|
*/
|
||||||
// TODO(b/399861060): Remove in Media3 1.8.
|
// TODO(b/406437316): Remove in Media3 1.8.
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
|
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
|
||||||
return withAllDefaultSupportedCodecs();
|
return withAllDefaultSupportedCodecs();
|
||||||
@ -194,7 +197,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
/**
|
/**
|
||||||
* @deprecated Use {@link ShadowMediaCodecConfig#withNoDefaultSupportedCodecs()} instead.
|
* @deprecated Use {@link ShadowMediaCodecConfig#withNoDefaultSupportedCodecs()} instead.
|
||||||
*/
|
*/
|
||||||
// TODO(b/399861060): Remove in Media3 1.8.
|
// TODO(b/406437316): Remove in Media3 1.8.
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() {
|
public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() {
|
||||||
return withNoDefaultSupportedCodecs();
|
return withNoDefaultSupportedCodecs();
|
||||||
@ -205,6 +208,20 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
return new ShadowMediaCodecConfig(ImmutableSet.of());
|
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 final ImmutableSet<CodecImpl> defaultCodecs;
|
||||||
|
|
||||||
private ShadowMediaCodecConfig(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
|
@Override
|
||||||
protected void before() throws Throwable {
|
protected void before() throws Throwable {
|
||||||
for (CodecImpl codec : this.defaultCodecs) {
|
for (CodecImpl codec : this.defaultCodecs) {
|
||||||
@ -282,7 +332,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableSet<CodecImpl> createDecoders(
|
private static ImmutableSet<CodecImpl> createDecoders(
|
||||||
ImmutableList<CodecInfo> decoderInfos, boolean forcePassthrough) {
|
List<CodecInfo> decoderInfos, boolean forcePassthrough) {
|
||||||
ImmutableSet.Builder<CodecImpl> builder = new ImmutableSet.Builder<>();
|
ImmutableSet.Builder<CodecImpl> builder = new ImmutableSet.Builder<>();
|
||||||
for (CodecInfo info : decoderInfos) {
|
for (CodecInfo info : decoderInfos) {
|
||||||
if (!forcePassthrough && MimeTypes.isAudio(info.mimeType)) {
|
if (!forcePassthrough && MimeTypes.isAudio(info.mimeType)) {
|
||||||
@ -294,6 +344,14 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
return builder.build();
|
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
|
* A {@link ShadowMediaCodec.CodecConfig.Codec} that provides pass-through or frame dropping
|
||||||
* encoders and decoders.
|
* encoders and decoders.
|
||||||
@ -312,6 +370,10 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
|||||||
return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ false);
|
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.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer;
|
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.ASSET_URI_PREFIX;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ONLY;
|
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ONLY;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
|
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;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
|
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.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.createAudioEffects;
|
||||||
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.getCompositionDumpFilePath;
|
import static androidx.media3.transformer.TestUtil.getCompositionDumpFilePath;
|
||||||
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
||||||
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.common.audio.SonicAudioProcessor;
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.TestTransformerBuilder;
|
import androidx.media3.test.utils.TestTransformerBuilder;
|
||||||
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
@ -57,15 +56,16 @@ public class CompositionExportTest {
|
|||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Before
|
@Rule
|
||||||
public void setUp() {
|
public ShadowMediaCodecConfig shadowMediaCodecConfig =
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
ShadowMediaCodecConfig.withCodecs(
|
||||||
addAudioEncoders(MimeTypes.AUDIO_AAC);
|
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
|
||||||
}
|
/* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
removeEncodersAndDecoders();
|
// TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
|
||||||
|
EncoderUtil.clearCachedEncoders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil;
|
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_DECODED;
|
||||||
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
|
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
|
||||||
import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA;
|
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_VIDEO_ONLY;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION;
|
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.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.createAudioEffects;
|
||||||
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
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_AVAILABLE;
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
|
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.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.FakeClock;
|
import androidx.media3.test.utils.FakeClock;
|
||||||
import androidx.media3.test.utils.TestTransformerBuilder;
|
import androidx.media3.test.utils.TestTransformerBuilder;
|
||||||
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
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.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -125,24 +125,42 @@ import org.robolectric.shadows.ShadowMediaCodec;
|
|||||||
public final class MediaItemExportTest {
|
public final class MediaItemExportTest {
|
||||||
|
|
||||||
private static final long TEST_TIMEOUT_SECONDS = 10;
|
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();
|
@Rule public final TemporaryFolder outputDir = new TemporaryFolder();
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Before
|
@Rule
|
||||||
public void setUp() {
|
public ShadowMediaCodecConfig shadowMediaCodecConfig =
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
ShadowMediaCodecConfig.withCodecs(
|
||||||
addAudioEncoders(MimeTypes.AUDIO_AAC);
|
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW), /* encoders= */ ImmutableList.of());
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
removeEncodersAndDecoders();
|
// TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
|
||||||
|
EncoderUtil.clearCachedEncoders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_gapOnlyExport_outputsSilence() throws Exception {
|
public void start_gapOnlyExport_outputsSilence() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
||||||
@ -275,6 +293,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_trimOptimizationEnabled_fileNotMp4_fallbackToNormalExport() throws Exception {
|
public void start_trimOptimizationEnabled_fileNotMp4_fallbackToNormalExport() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context)
|
new TestTransformerBuilder(context)
|
||||||
@ -441,6 +460,7 @@ public final class MediaItemExportTest {
|
|||||||
@Test
|
@Test
|
||||||
public void start_forceAudioTrackAndRemoveAudioWithEffects_generatesSilentAudio()
|
public void start_forceAudioTrackAndRemoveAudioWithEffects_generatesSilentAudio()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
||||||
@ -497,6 +517,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception {
|
public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
|
||||||
@ -521,6 +542,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void exportAudio_muxerReceivesExpectedNumberOfBytes() throws Exception {
|
public void exportAudio_muxerReceivesExpectedNumberOfBytes() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
AtomicInteger bytesSeenByEffect = new AtomicInteger();
|
AtomicInteger bytesSeenByEffect = new AtomicInteger();
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
@ -540,6 +562,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_adjustSampleRate_completesSuccessfully() throws Exception {
|
public void start_adjustSampleRate_completesSuccessfully() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
||||||
@ -569,6 +592,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void adjustAudioSpeed_toDoubleSpeed_returnsExpectedNumberOfSamples() throws Exception {
|
public void adjustAudioSpeed_toDoubleSpeed_returnsExpectedNumberOfSamples() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
sonicAudioProcessor.setSpeed(2f);
|
sonicAudioProcessor.setSpeed(2f);
|
||||||
@ -599,6 +623,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_withRawBigEndianAudioInput_completesSuccessfully() throws Exception {
|
public void start_withRawBigEndianAudioInput_completesSuccessfully() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
ToInt16PcmAudioProcessor toInt16PcmAudioProcessor = new ToInt16PcmAudioProcessor();
|
ToInt16PcmAudioProcessor toInt16PcmAudioProcessor = new ToInt16PcmAudioProcessor();
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
@ -622,6 +647,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception {
|
public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
||||||
@ -702,6 +728,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_withMultipleListeners_callsEachOnFallback() throws Exception {
|
public void start_withMultipleListeners_callsEachOnFallback() throws Exception {
|
||||||
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false);
|
||||||
ArgumentCaptor<Composition> compositionArgumentCaptor =
|
ArgumentCaptor<Composition> compositionArgumentCaptor =
|
||||||
ArgumentCaptor.forClass(Composition.class);
|
ArgumentCaptor.forClass(Composition.class);
|
||||||
@ -804,29 +831,11 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void start_whenCodecFailsToConfigure_completesWithError() throws Exception {
|
public void start_whenCodecFailsToConfigure_completesWithError() throws Exception {
|
||||||
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false);
|
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the AMR_NB encoder that throws when configured.
|
// 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 =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context)
|
new TestTransformerBuilder(context)
|
||||||
.setMuxerFactory(muxerFactory)
|
.setMuxerFactory(muxerFactory)
|
||||||
@ -841,7 +850,10 @@ public final class MediaItemExportTest {
|
|||||||
assertThat(exception.errorCode)
|
assertThat(exception.errorCode)
|
||||||
.isEqualTo(ExportException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
|
.isEqualTo(ExportException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
|
||||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
assertThat(exception).hasCauseThat().hasMessageThat().isEqualTo(expectedFailureMessage);
|
assertThat(exception)
|
||||||
|
.hasCauseThat()
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(EXPECTED_CODEC_EXCEPTION_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -866,11 +878,9 @@ public final class MediaItemExportTest {
|
|||||||
public void
|
public void
|
||||||
start_withAudioFormatUnsupportedByMuxer_ignoresDisabledFallbackAndCompletesSuccessfully()
|
start_withAudioFormatUnsupportedByMuxer_ignoresDisabledFallbackAndCompletesSuccessfully()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
removeEncodersAndDecoders();
|
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
// RAW supported by encoder, unsupported by muxer.
|
// RAW supported by encoder, unsupported by muxer.
|
||||||
// AAC supported by encoder and 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);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
||||||
@ -901,11 +911,9 @@ public final class MediaItemExportTest {
|
|||||||
@Test
|
@Test
|
||||||
public void start_withAudioFormatUnsupportedByMuxer_fallsBackAndCompletesSuccessfully()
|
public void start_withAudioFormatUnsupportedByMuxer_fallsBackAndCompletesSuccessfully()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
removeEncodersAndDecoders();
|
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
// RAW supported by encoder, unsupported by muxer.
|
// RAW supported by encoder, unsupported by muxer.
|
||||||
// AAC supported by encoder and 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);
|
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
||||||
@ -1198,9 +1206,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void analyze_audioOnlyWithItemEffect_completesSuccessfully() throws Exception {
|
public void analyze_audioOnlyWithItemEffect_completesSuccessfully() throws Exception {
|
||||||
removeEncodersAndDecoders();
|
shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
||||||
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
||||||
@ -1220,9 +1226,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void analyze_audioOnlyWithCompositionEffect_completesSuccessfully() throws Exception {
|
public void analyze_audioOnlyWithCompositionEffect_completesSuccessfully() throws Exception {
|
||||||
removeEncodersAndDecoders();
|
shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
||||||
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
||||||
@ -1247,9 +1251,7 @@ public final class MediaItemExportTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void analyze_audioOnly_itemAndMixerOutputMatch() throws Exception {
|
public void analyze_audioOnly_itemAndMixerOutputMatch() throws Exception {
|
||||||
removeEncodersAndDecoders();
|
shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
ExperimentalAnalyzerModeFactory.buildAnalyzer(
|
||||||
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
|
||||||
@ -1567,27 +1569,6 @@ public final class MediaItemExportTest {
|
|||||||
/* modifications...= */ "transmuxed"));
|
/* 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) {
|
private static AudioProcessor createByteCountingAudioProcessor(AtomicInteger byteCount) {
|
||||||
return new TeeAudioProcessor(
|
return new TeeAudioProcessor(
|
||||||
new TeeAudioProcessor.AudioBufferSink() {
|
new TeeAudioProcessor.AudioBufferSink() {
|
||||||
|
@ -16,24 +16,23 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
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.ASSET_URI_PREFIX;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
|
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.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.createAudioEffects;
|
||||||
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
|
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
|
||||||
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.TestTransformerBuilder;
|
import androidx.media3.test.utils.TestTransformerBuilder;
|
||||||
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -46,7 +45,6 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
@ -126,6 +124,12 @@ public final class ParameterizedAudioExportTest {
|
|||||||
|
|
||||||
@Rule public final TemporaryFolder outputDir = new TemporaryFolder();
|
@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;
|
@Parameter public SequenceConfig sequence;
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
@ -133,15 +137,10 @@ public final class ParameterizedAudioExportTest {
|
|||||||
private final CapturingMuxer.Factory muxerFactory =
|
private final CapturingMuxer.Factory muxerFactory =
|
||||||
new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
|
||||||
addAudioEncoders(MimeTypes.AUDIO_AAC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
removeEncodersAndDecoders();
|
// TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
|
||||||
|
EncoderUtil.clearCachedEncoders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
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.ASSET_URI_PREFIX;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB;
|
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
|
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_RAW_VIDEO;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_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.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.createAudioEffects;
|
||||||
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
||||||
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
|
|
||||||
import static org.junit.Assume.assumeFalse;
|
import static org.junit.Assume.assumeFalse;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.TestTransformerBuilder;
|
import androidx.media3.test.utils.TestTransformerBuilder;
|
||||||
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
@ -93,17 +91,18 @@ public final class ParameterizedItemExportTest {
|
|||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Before
|
// Only add RAW decoder, so non-RAW audio has no options for decoding.
|
||||||
public void setUp() {
|
// Use an AAC encoder because muxer supports AAC.
|
||||||
// Only add RAW decoder, so non-RAW audio has no options for decoding.
|
@Rule
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
public ShadowMediaCodecConfig shadowMediaCodecConfig =
|
||||||
// Use an AAC encoder because muxer supports AAC.
|
ShadowMediaCodecConfig.withCodecs(
|
||||||
addAudioEncoders(MimeTypes.AUDIO_AAC);
|
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
|
||||||
}
|
/* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
removeEncodersAndDecoders();
|
// TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
|
||||||
|
EncoderUtil.clearCachedEncoders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -16,36 +16,34 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
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.ASSET_URI_PREFIX;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
|
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_STEREO_48000KHZ;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO;
|
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;
|
||||||
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
|
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.createAudioEffects;
|
||||||
import static androidx.media3.transformer.TestUtil.createChannelCountChangingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createChannelCountChangingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
|
||||||
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
import static androidx.media3.transformer.TestUtil.getDumpFileName;
|
||||||
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
|
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
|
||||||
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.common.audio.SonicAudioProcessor;
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.effect.RgbFilter;
|
import androidx.media3.effect.RgbFilter;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.TestTransformerBuilder;
|
import androidx.media3.test.utils.TestTransformerBuilder;
|
||||||
|
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -69,15 +67,16 @@ public final class SequenceExportTest {
|
|||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Before
|
@Rule
|
||||||
public void setUp() {
|
public ShadowMediaCodecConfig shadowMediaCodecConfig =
|
||||||
addAudioDecoders(MimeTypes.AUDIO_RAW);
|
ShadowMediaCodecConfig.withCodecs(
|
||||||
addAudioEncoders(MimeTypes.AUDIO_AAC);
|
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
|
||||||
}
|
/* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
removeEncodersAndDecoders();
|
// TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
|
||||||
|
EncoderUtil.clearCachedEncoders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -15,20 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer;
|
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.AudioProcessor;
|
||||||
import androidx.media3.common.audio.ChannelMixingAudioProcessor;
|
import androidx.media3.common.audio.ChannelMixingAudioProcessor;
|
||||||
import androidx.media3.common.audio.ChannelMixingMatrix;
|
import androidx.media3.common.audio.ChannelMixingMatrix;
|
||||||
import androidx.media3.common.audio.SonicAudioProcessor;
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
import org.robolectric.shadows.ShadowMediaCodec;
|
|
||||||
import org.robolectric.shadows.ShadowMediaCodecList;
|
|
||||||
|
|
||||||
/** Utility class for {@link Transformer} unit tests */
|
/** Utility class for {@link Transformer} unit tests */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -142,83 +136,4 @@ public final class TestUtil {
|
|||||||
+ "."
|
+ "."
|
||||||
+ DUMP_FILE_EXTENSION;
|
+ 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