Add TransformerTestBuilderFactory to make transformer testable by apps

PiperOrigin-RevId: 495821660
This commit is contained in:
tofunmi 2022-12-16 10:30:01 +00:00 committed by Tianyi Feng
parent 6376f5f7c7
commit 7e63997459
7 changed files with 429 additions and 244 deletions

View File

@ -26,6 +26,7 @@ dependencies {
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + ':lib-transformer')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}

View File

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.transformer;
package androidx.media3.test.utils;
import androidx.media3.common.Format;
import androidx.media3.test.utils.DumpableFormat;
import androidx.media3.test.utils.Dumper;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.transformer.Muxer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@ -28,6 +28,7 @@ import java.util.List;
* testing purposes) and delegates the actual muxing operations to another {@link Muxer} created
* using the factory provided.
*/
@UnstableApi
public final class TestMuxer implements Muxer, Dumper.Dumpable {
private final Muxer muxer;

View File

@ -0,0 +1,107 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.test.utils;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import androidx.media3.common.C;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.DefaultMuxer;
import androidx.media3.transformer.Muxer;
import androidx.media3.transformer.Transformer;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Creates a {@link Transformer.Builder} setting up some of the common resources needed for testing
* {@link Transformer}.
*/
@UnstableApi
public final class TestTransformerBuilderFactory {
private final Context context;
private static @MonotonicNonNull TestMuxer testMuxer;
private long maxDelayBetweenSamplesMs;
/** Creates a new instance */
public TestTransformerBuilderFactory(Context context) {
this.context = context;
maxDelayBetweenSamplesMs = DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS;
}
/**
* Sets the muxer's {@linkplain Muxer#getMaxDelayBetweenSamplesMs() max delay} between samples.
*/
@CanIgnoreReturnValue
public TestTransformerBuilderFactory setMaxDelayBetweenSamplesMs(long maxDelayBetweenSamplesMs) {
this.maxDelayBetweenSamplesMs = maxDelayBetweenSamplesMs;
return this;
}
/** Returns a {@link Transformer.Builder} using the provided values or their defaults. */
@SuppressLint("VisibleForTests") // Suppresses warning on setting the clock outside of a test file
public Transformer.Builder create(boolean enableFallback) {
Clock clock = new FakeClock(/* isAutoAdvancing= */ true);
Muxer.Factory defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs);
return new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory(defaultMuxerFactory))
.setEncoderFactory(
new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build());
}
/**
* Returns the test muxer used in the {@link Transformer.Builder}.
*
* <p>This method should only be called after the transformation is completed.
*/
public TestMuxer getTestMuxer() {
return checkStateNotNull(testMuxer);
}
private static final class TestMuxerFactory implements Muxer.Factory {
private final Muxer.Factory defaultMuxerFactory;
public TestMuxerFactory(Muxer.Factory defaultMuxerFactory) {
this.defaultMuxerFactory = defaultMuxerFactory;
}
@Override
public Muxer create(String path) throws Muxer.MuxerException {
testMuxer = new TestMuxer(path, defaultMuxerFactory);
return testMuxer;
}
@Override
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException {
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory);
return testMuxer;
}
@Override
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
}
}
}

View File

@ -20,6 +20,7 @@ dependencies {
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'org.robolectric:robolectric:' + robolectricVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + ':lib-transformer')
implementation project(modulePrefix + 'test-utils')
}

View File

@ -16,10 +16,15 @@
package androidx.media3.test.utils.robolectric;
import android.media.MediaCodecInfo;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import androidx.media3.transformer.EncoderUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.nio.ByteBuffer;
@ -30,105 +35,247 @@ import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
/**
* A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}.
* A JUnit @Rule to configure {@link ShadowMediaCodec} for transcoding or decoding.
*
* <p>Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video
* MIME type known by ExoPlayer.
* <p>Registers {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} instances for ExoPlayer
* and Transformer tests.
*/
@UnstableApi
public final class ShadowMediaCodecConfig extends ExternalResource {
private static final String EXOTEST_VIDEO_AVC = "exotest.video.avc";
private static final String EXOTEST_VIDEO_MPEG2 = "exotest.video.mpeg2";
private static final String EXOTEST_VIDEO_VP9 = "exotest.video.vp9";
private static final String EXOTEST_AUDIO_AAC = "exotest.audio.aac";
private static final String EXOTEST_AUDIO_AC3 = "exotest.audio.ac3";
private static final String EXOTEST_AUDIO_AC4 = "exotest.audio.ac4";
private static final String EXOTEST_AUDIO_E_AC3 = "exotest.audio.eac3";
private static final String EXOTEST_AUDIO_E_AC3_JOC = "exotest.audio.eac3joc";
private static final String EXOTEST_AUDIO_FLAC = "exotest.audio.flac";
private static final String EXOTEST_AUDIO_MPEG = "exotest.audio.mpeg";
private static final String EXOTEST_AUDIO_MPEG_L2 = "exotest.audio.mpegl2";
private static final String EXOTEST_AUDIO_OPUS = "exotest.audio.opus";
private static final String EXOTEST_AUDIO_VORBIS = "exotest.audio.vorbis";
private static final String EXOTEST_AUDIO_RAW = "exotest.audio.raw";
private final boolean forTranscoding;
private ShadowMediaCodecConfig(boolean forTranscoding) {
this.forTranscoding = forTranscoding;
}
/** Creates an instance that configures {@link ShadowMediaCodec} for Transformer transcoding. */
public static ShadowMediaCodecConfig forTranscoding() {
return new ShadowMediaCodecConfig(/* forTranscoding= */ true);
}
/** Creates an instance that configures {@link ShadowMediaCodec} for Exoplayer decoding. */
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
return new ShadowMediaCodecConfig();
return new ShadowMediaCodecConfig(/* forTranscoding= */ false);
}
@Override
protected void before() throws Throwable {
if (forTranscoding) {
addTranscodingCodecs();
} else {
addDecodingCodecs();
}
}
private void addTranscodingCodecs() {
ShadowMediaCodec.CodecConfig codecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 10_000,
/* outputBufferSize= */ 10_000,
/* codec= */ (in, out) -> out.put(in));
addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ true);
addTransformerCodec(MimeTypes.AUDIO_AC3, codecConfig, /* isDecoder= */ true);
addTransformerCodec(MimeTypes.AUDIO_AMR_NB, codecConfig, /* isDecoder= */ true);
addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ false);
ShadowMediaCodec.CodecConfig throwingCodecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 10_000,
/* outputBufferSize= */ 10_000,
new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer in, ByteBuffer out) {
out.put(in);
}
@Override
public void onConfigured(
MediaFormat format,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,
int flags) {
throw new IllegalArgumentException("Format unsupported");
}
});
addTransformerCodec(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig, /* isDecoder= */ true);
addTransformerCodec(MimeTypes.AUDIO_AMR_NB, throwingCodecConfig, /* isDecoder= */ false);
}
private void addDecodingCodecs() {
// Video codecs
MediaCodecInfo.CodecProfileLevel avcProfileLevel =
createProfileLevel(
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
MediaCodecInfo.CodecProfileLevel.AVCLevel62);
configureCodec(
/* codecName= */ "exotest.video.avc",
addExoplayerCodec(
EXOTEST_VIDEO_AVC,
MimeTypes.VIDEO_H264,
generateDecodingCodecConfig(MimeTypes.VIDEO_H264),
ImmutableList.of(avcProfileLevel),
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel =
createProfileLevel(
MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain,
MediaCodecInfo.CodecProfileLevel.MPEG2LevelML);
configureCodec(
/* codecName= */ "exotest.video.mpeg2",
addExoplayerCodec(
EXOTEST_VIDEO_MPEG2,
MimeTypes.VIDEO_MPEG2,
generateDecodingCodecConfig(MimeTypes.VIDEO_MPEG2),
ImmutableList.of(mpeg2ProfileLevel),
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
configureCodec(
/* codecName= */ "exotest.video.vp9",
addExoplayerCodec(
EXOTEST_VIDEO_VP9,
MimeTypes.VIDEO_VP9,
generateDecodingCodecConfig(MimeTypes.VIDEO_VP9),
ImmutableList.of(),
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
// Audio codecs
configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC);
configureCodec("exotest.audio.ac3", MimeTypes.AUDIO_AC3);
configureCodec("exotest.audio.ac4", MimeTypes.AUDIO_AC4);
configureCodec("exotest.audio.eac3", MimeTypes.AUDIO_E_AC3);
configureCodec("exotest.audio.eac3joc", MimeTypes.AUDIO_E_AC3_JOC);
configureCodec("exotest.audio.flac", MimeTypes.AUDIO_FLAC);
configureCodec("exotest.audio.mpeg", MimeTypes.AUDIO_MPEG);
configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2);
configureCodec("exotest.audio.opus", MimeTypes.AUDIO_OPUS);
configureCodec("exotest.audio.vorbis", MimeTypes.AUDIO_VORBIS);
addExoplayerCodec(
EXOTEST_AUDIO_AAC, MimeTypes.AUDIO_AAC, generateDecodingCodecConfig(MimeTypes.AUDIO_AAC));
addExoplayerCodec(
EXOTEST_AUDIO_AC3, MimeTypes.AUDIO_AC3, generateDecodingCodecConfig(MimeTypes.AUDIO_AC3));
addExoplayerCodec(
EXOTEST_AUDIO_AC4, MimeTypes.AUDIO_AC4, generateDecodingCodecConfig(MimeTypes.AUDIO_AC4));
addExoplayerCodec(
EXOTEST_AUDIO_E_AC3,
MimeTypes.AUDIO_E_AC3,
generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3));
addExoplayerCodec(
EXOTEST_AUDIO_E_AC3_JOC,
MimeTypes.AUDIO_E_AC3_JOC,
generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3_JOC));
addExoplayerCodec(
EXOTEST_AUDIO_FLAC,
MimeTypes.AUDIO_FLAC,
generateDecodingCodecConfig(MimeTypes.AUDIO_FLAC));
addExoplayerCodec(
EXOTEST_AUDIO_MPEG,
MimeTypes.AUDIO_MPEG,
generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG));
addExoplayerCodec(
EXOTEST_AUDIO_MPEG_L2,
MimeTypes.AUDIO_MPEG_L2,
generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG_L2));
addExoplayerCodec(
EXOTEST_AUDIO_OPUS,
MimeTypes.AUDIO_OPUS,
generateDecodingCodecConfig(MimeTypes.AUDIO_OPUS));
addExoplayerCodec(
EXOTEST_AUDIO_VORBIS,
MimeTypes.AUDIO_VORBIS,
generateDecodingCodecConfig(MimeTypes.AUDIO_VORBIS));
// Raw audio should use a bypass mode and never need this codec. However, to easily assert
// failures of the bypass mode we want to detect when the raw audio is decoded by this class and
// thus we need a codec to output samples.
configureCodec("exotest.audio.raw", MimeTypes.AUDIO_RAW);
addExoplayerCodec(
EXOTEST_AUDIO_RAW, MimeTypes.AUDIO_RAW, generateDecodingCodecConfig(MimeTypes.AUDIO_RAW));
}
@Override
protected void after() {
MediaCodecUtil.clearDecoderInfoCache();
if (!forTranscoding) {
MediaCodecUtil.clearDecoderInfoCache();
} else {
EncoderUtil.clearCachedEncoders();
}
ShadowMediaCodecList.reset();
ShadowMediaCodec.clearCodecs();
}
private void configureCodec(String codecName, String mimeType) {
configureCodec(
private ShadowMediaCodec.CodecConfig generateDecodingCodecConfig(String mimeType) {
// TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
// to configure() so we don't have to specify large buffers here.
CodecImpl codec = new CodecImpl(mimeType);
return new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec);
}
private void addTransformerCodec(
String mimeType, ShadowMediaCodec.CodecConfig codecConfig, boolean isDecoder) {
String codecName =
Util.formatInvariant(
isDecoder ? "transformertest.%s.decoder" : "transformertest.%s.encoder",
mimeType.replace('/', '.'));
addCodec(
codecName,
mimeType,
codecConfig,
/* profileLevels= */ ImmutableList.of(),
/* colorFormats= */ ImmutableList.of(),
isDecoder);
}
private void addExoplayerCodec(
String codecName, String mimeType, ShadowMediaCodec.CodecConfig codecConfig) {
addExoplayerCodec(
codecName,
mimeType,
codecConfig,
/* profileLevels= */ ImmutableList.of(),
/* colorFormats= */ ImmutableList.of());
}
private void configureCodec(
private void addExoplayerCodec(
String codecName,
String mimeType,
ShadowMediaCodec.CodecConfig codecConfig,
List<MediaCodecInfo.CodecProfileLevel> profileLevels,
List<Integer> colorFormats) {
addCodec(codecName, mimeType, codecConfig, profileLevels, colorFormats, /* isDecoder= */ true);
}
private void addCodec(
String codecName,
String mimeType,
ShadowMediaCodec.CodecConfig codecConfig,
List<MediaCodecInfo.CodecProfileLevel> profileLevels,
List<Integer> colorFormats,
boolean isDecoder) {
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities =
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat);
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(mediaFormat)
.setIsEncoder(!isDecoder);
if (!profileLevels.isEmpty()) {
capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0]));
}
if (!colorFormats.isEmpty()) {
capabilities.setColorFormats(Ints.toArray(colorFormats));
}
ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder()
.setName(codecName)
.setIsEncoder(!isDecoder)
.setCapabilities(capabilities.build())
.build());
// TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
// to configure() so we don't have to specify large buffers here.
CodecImpl codec = new CodecImpl(mimeType);
ShadowMediaCodec.addDecoder(
codecName,
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec));
if (isDecoder) {
ShadowMediaCodec.addDecoder(codecName, codecConfig);
} else {
ShadowMediaCodec.addEncoder(codecName, codecConfig);
}
}
private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) {

View File

@ -430,7 +430,7 @@ public final class Transformer {
*/
@CanIgnoreReturnValue
@VisibleForTesting
/* package */ Builder setClock(Clock clock) {
public Builder setClock(Clock clock) {
this.clock = clock;
this.listeners = listeners.copy(looper, clock, (listener, flags) -> {});
return this;

View File

@ -29,15 +29,11 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -53,14 +49,13 @@ import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.ExtractorsFactory;
import androidx.media3.extractor.PositionHolder;
import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.TestTransformerBuilderFactory;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
@ -74,16 +69,17 @@ import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
/** End-to-end test for {@link Transformer}. */
@RunWith(AndroidJUnit4.class)
public final class TransformerEndToEndTest {
@Rule
public final ShadowMediaCodecConfig mediaCodecConfig = ShadowMediaCodecConfig.forTranscoding();
private static final String ASSET_URI_PREFIX = "asset:///media/";
private static final String FILE_VIDEO_ONLY = "mp4/sample_18byte_nclx_colr.mp4";
private static final String FILE_AUDIO_VIDEO = "mp4/sample.mp4";
@ -100,39 +96,39 @@ public final class TransformerEndToEndTest {
private Context context;
private String outputPath;
private TestMuxer testMuxer;
private FakeClock clock;
private ProgressHolder progressHolder;
private TestTransformerBuilderFactory testTransformerBuilderFactory;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
outputPath = Util.createTempFile(context, "TransformerTest").getPath();
clock = new FakeClock(/* isAutoAdvancing= */ true);
progressHolder = new ProgressHolder();
createEncodersAndDecoders();
testTransformerBuilderFactory = new TestTransformerBuilderFactory(context);
}
@After
public void tearDown() throws Exception {
Files.delete(Paths.get(outputPath));
removeEncodersAndDecoders();
}
@Test
public void startTransformation_videoOnlyPassthrough_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_VIDEO_ONLY));
}
@Test
public void startTransformation_audioOnlyPassthrough_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
@ -140,13 +136,16 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
}
@Test
public void startTransformation_audioOnlyTranscoding_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
@ -158,24 +157,29 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac"));
}
@Test
public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
public void startTransformation_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully()
throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S)
@ -192,14 +196,15 @@ public final class TransformerEndToEndTest {
DumpFileAsserts.assertOutput(
context,
testMuxer,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped"));
}
@Test
public void startTransformation_withSubtitles_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build())
.build();
@ -208,13 +213,17 @@ public final class TransformerEndToEndTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES));
DumpFileAsserts.assertOutput(
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_WITH_SUBTITLES));
}
@Test
public void startTransformation_successiveTransformations_completesSuccessfully()
throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
// Transform first media item.
@ -226,12 +235,14 @@ public final class TransformerEndToEndTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
public void startTransformation_concurrentTransformations_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
@ -243,33 +254,44 @@ public final class TransformerEndToEndTest {
@Test
public void startTransformation_removeAudio_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setRemoveAudio(true).build();
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setRemoveAudio(true)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
}
@Test
public void startTransformation_removeVideo_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setRemoveVideo(true).build();
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setRemoveVideo(true)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
}
@Test
public void startTransformation_silentAudio_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.experimentalSetForceSilentAudio(true)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
@ -278,7 +300,9 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
}
@Test
@ -286,7 +310,8 @@ public final class TransformerEndToEndTest {
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setOutputSampleRateHz(48000);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setAudioProcessors(ImmutableList.of(sonicAudioProcessor))
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
@ -295,7 +320,9 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
}
@Test
@ -304,7 +331,8 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
@ -325,7 +353,8 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
@ -352,7 +381,8 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ true)
testTransformerBuilderFactory
.create(/* enableFallback= */ true)
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
@ -377,7 +407,8 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer1 =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.addListener(mockListener1)
.addListener(mockListener2)
.addListener(mockListener3)
@ -396,7 +427,8 @@ public final class TransformerEndToEndTest {
@Test
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.build();
@ -405,7 +437,10 @@ public final class TransformerEndToEndTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
DumpFileAsserts.assertOutput(
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
}
@Test
@ -420,7 +455,10 @@ public final class TransformerEndToEndTest {
}
};
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).addListener(listener).build();
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.addListener(listener)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
@ -436,7 +474,8 @@ public final class TransformerEndToEndTest {
public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(
@ -457,7 +496,8 @@ public final class TransformerEndToEndTest {
public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
@ -475,7 +515,8 @@ public final class TransformerEndToEndTest {
@Test
public void startTransformation_withIoError_completesWithError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
transformer.startTransformation(mediaItem, outputPath);
@ -495,14 +536,19 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build();
testTransformerBuilderFactory
.create(/* enableFallback= */ false)
.addListener(mockListener)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
verify(mockListener)
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
}
@ -516,14 +562,19 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ true).addListener(mockListener).build();
testTransformerBuilderFactory
.create(/* enableFallback= */ true)
.addListener(mockListener)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
context,
testTransformerBuilderFactory.getTestMuxer(),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
verify(mockListener)
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
}
@ -533,11 +584,11 @@ public final class TransformerEndToEndTest {
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)
testTransformerBuilderFactory
.setMaxDelayBetweenSamplesMs(1)
.create(/* enableFallback= */ false)
.setMediaSourceFactory(mediaSourceFactory)
.setMuxerFactory(muxerFactory)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
@ -551,20 +602,24 @@ public final class TransformerEndToEndTest {
@Test
public void startTransformation_withUnsetMaxDelayBetweenSamples_completesSuccessfully()
throws Exception {
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setMuxerFactory(muxerFactory).build();
testTransformerBuilderFactory
.setMaxDelayBetweenSamplesMs(C.TIME_UNSET)
.create(/* enableFallback= */ false)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
public void startTransformation_afterCancellation_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.startTransformation(mediaItem, outputPath);
@ -575,7 +630,8 @@ public final class TransformerEndToEndTest {
transformer.startTransformation(mediaItem, outputPath);
TransformerTestRunner.runUntilCompleted(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
@ -584,7 +640,7 @@ public final class TransformerEndToEndTest {
anotherThread.start();
Looper looper = anotherThread.getLooper();
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setLooper(looper).build();
testTransformerBuilderFactory.create(/* enableFallback= */ false).setLooper(looper).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
AtomicReference<Exception> exception = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -604,12 +660,14 @@ public final class TransformerEndToEndTest {
countDownLatch.await();
assertThat(exception.get()).isNull();
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
DumpFileAsserts.assertOutput(
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
}
@Test
public void startTransformation_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
@ -634,7 +692,8 @@ public final class TransformerEndToEndTest {
@Test
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
@ -680,7 +739,8 @@ public final class TransformerEndToEndTest {
@Test
public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
List<Integer> progresses = new ArrayList<>();
Handler progressHandler =
@ -715,7 +775,8 @@ public final class TransformerEndToEndTest {
@Test
public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
@ -729,7 +790,8 @@ public final class TransformerEndToEndTest {
@Test
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION);
AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
@ -772,7 +834,8 @@ public final class TransformerEndToEndTest {
@Test
public void getProgress_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -796,7 +859,8 @@ public final class TransformerEndToEndTest {
@Test
public void cancel_afterCompletion_doesNotThrow() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
@ -806,7 +870,8 @@ public final class TransformerEndToEndTest {
@Test
public void cancel_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
Transformer transformer =
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -828,147 +893,10 @@ public final class TransformerEndToEndTest {
assertThat(illegalStateException.get()).isNotNull();
}
private Transformer.Builder createTransformerBuilder(boolean enableFallback) {
return new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.setEncoderFactory(
new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build());
}
private static void createEncodersAndDecoders() {
ShadowMediaCodec.CodecConfig codecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 10_000,
/* outputBufferSize= */ 10_000,
/* codec= */ (in, out) -> out.put(in));
addCodec(
MimeTypes.AUDIO_AAC,
codecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true);
addCodec(
MimeTypes.AUDIO_AC3,
codecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true);
addCodec(
MimeTypes.AUDIO_AMR_NB,
codecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true);
addCodec(
MimeTypes.AUDIO_AAC,
codecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ false);
ShadowMediaCodec.CodecConfig throwingCodecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 10_000,
/* outputBufferSize= */ 10_000,
new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer in, ByteBuffer out) {
out.put(in);
}
@Override
public void onConfigured(
MediaFormat format,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,
int flags) {
throw new IllegalArgumentException("Format unsupported");
}
});
addCodec(
MimeTypes.AUDIO_AMR_WB,
throwingCodecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true);
addCodec(
MimeTypes.AUDIO_AMR_NB,
throwingCodecConfig,
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ false);
}
private static void addCodec(
String mimeType,
ShadowMediaCodec.CodecConfig codecConfig,
List<Integer> colorFormats,
boolean isDecoder) {
String codecName =
Util.formatInvariant(
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
if (isDecoder) {
ShadowMediaCodec.addDecoder(codecName, codecConfig);
} else {
ShadowMediaCodec.addEncoder(codecName, codecConfig);
}
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
MediaCodecInfoBuilder.CodecCapabilitiesBuilder codecCapabilities =
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(mediaFormat)
.setIsEncoder(!isDecoder);
if (!colorFormats.isEmpty()) {
codecCapabilities.setColorFormats(Ints.toArray(colorFormats));
}
ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder()
.setName(codecName)
.setIsEncoder(!isDecoder)
.setCapabilities(codecCapabilities.build())
.build());
}
private static void removeEncodersAndDecoders() {
ShadowMediaCodec.clearCodecs();
ShadowMediaCodecList.reset();
EncoderUtil.clearCachedEncoders();
}
private static String getDumpFileName(String originalFileName) {
return DUMP_FILE_OUTPUT_DIRECTORY + '/' + originalFileName + '.' + DUMP_FILE_EXTENSION;
}
private final class TestMuxerFactory implements Muxer.Factory {
private final Muxer.Factory defaultMuxerFactory;
public TestMuxerFactory() {
defaultMuxerFactory = new DefaultMuxer.Factory();
}
public TestMuxerFactory(long maxDelayBetweenSamplesMs) {
defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs);
}
@Override
public Muxer create(String path) throws Muxer.MuxerException {
testMuxer = new TestMuxer(path, defaultMuxerFactory);
return testMuxer;
}
@Override
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException {
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory);
return testMuxer;
}
@Override
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
}
}
private static final class SlowExtractorsFactory implements ExtractorsFactory {
private final long delayBetweenReadsMs;