Commit 3a11c638 by kimvde Committed by tonihei

Split TransformerEndToEndTest

Split test/ TransformerEndToEndTest into SingleMediaItemEndToEndTest and
SingleSequenceEndToEndTest to reduce the file size and split the tests
by category.

PiperOrigin-RevId: 515039502
parent 2d97d3a5
...@@ -19,10 +19,25 @@ package com.google.android.exoplayer2.transformer; ...@@ -19,10 +19,25 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runLooperUntil; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runLooperUntil;
import static com.google.android.exoplayer2.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED; import static com.google.android.exoplayer2.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
import static com.google.android.exoplayer2.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED; import static com.google.android.exoplayer2.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
import static com.google.android.exoplayer2.transformer.TestUtil.ASSET_URI_PREFIX;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_UNSUPPORTED_BY_DECODER;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_UNSUPPORTED_BY_ENCODER;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_UNSUPPORTED_BY_MUXER;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_UNKNOWN_DURATION;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_VIDEO_ONLY;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_WITH_SUBTITLES;
import static com.google.android.exoplayer2.transformer.TestUtil.createEncodersAndDecoders;
import static com.google.android.exoplayer2.transformer.TestUtil.createTransformerBuilder;
import static com.google.android.exoplayer2.transformer.TestUtil.getDumpFileName;
import static com.google.android.exoplayer2.transformer.TestUtil.removeEncodersAndDecoders;
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_AVAILABLE; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NOT_STARTED; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
...@@ -32,24 +47,18 @@ import static org.mockito.Mockito.never; ...@@ -32,24 +47,18 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.view.Surface;
import androidx.annotation.Nullable;
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.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.SonicAudioProcessor; import com.google.android.exoplayer2.audio.SonicAudioProcessor;
import com.google.android.exoplayer2.effect.Presentation; import com.google.android.exoplayer2.effect.Presentation;
import com.google.android.exoplayer2.effect.RgbFilter;
import com.google.android.exoplayer2.effect.ScaleAndRotateTransformation; import com.google.android.exoplayer2.effect.ScaleAndRotateTransformation;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -61,15 +70,15 @@ import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; ...@@ -61,15 +70,15 @@ import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.transformer.TestUtil.FakeAssetLoader;
import com.google.android.exoplayer2.transformer.TestUtil.TestMuxerFactory;
import com.google.android.exoplayer2.transformer.TestUtil.TestMuxerFactory.TestMuxerHolder;
import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.Effect;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -85,32 +94,17 @@ import org.junit.Before; ...@@ -85,32 +94,17 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
/** End-to-end test for {@link Transformer}. */ /**
* End-to-end test for exporting a single {@link MediaItem} or {@link EditedMediaItem} with {@link
* Transformer}.
*/
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class TransformerEndToEndTest { public final class MediaItemExportTest {
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";
private static final String FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S =
"mp4/sample_with_increasing_timestamps_320w_240h.mp4";
private static final String FILE_WITH_SUBTITLES = "mkv/sample_with_srt.mkv";
private static final String FILE_WITH_SEF_SLOW_MOTION = "mp4/sample_sef_slow_motion.mp4";
private static final String FILE_AUDIO_UNSUPPORTED_BY_DECODER = "amr/sample_wb.amr";
private static final String FILE_AUDIO_UNSUPPORTED_BY_ENCODER = "amr/sample_nb.amr";
private static final String FILE_AUDIO_UNSUPPORTED_BY_MUXER = "mp4/sample_ac3.mp4";
private static final String FILE_UNKNOWN_DURATION = "mp4/sample_fragmented.mp4";
private static final String DUMP_FILE_OUTPUT_DIRECTORY = "transformerdumps";
private static final String DUMP_FILE_EXTENSION = "dump";
private Context context; private Context context;
private String outputPath; private String outputPath;
private TestMuxer testMuxer; private TestMuxerHolder testMuxerHolder;
private FakeClock clock;
private ProgressHolder progressHolder; private ProgressHolder progressHolder;
private ArgumentCaptor<Composition> compositionArgumentCaptor; private ArgumentCaptor<Composition> compositionArgumentCaptor;
...@@ -118,7 +112,7 @@ public final class TransformerEndToEndTest { ...@@ -118,7 +112,7 @@ public final class TransformerEndToEndTest {
public void setUp() throws Exception { public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext(); context = ApplicationProvider.getApplicationContext();
outputPath = Util.createTempFile(context, "TransformerTest").getPath(); outputPath = Util.createTempFile(context, "TransformerTest").getPath();
clock = new FakeClock(/* isAutoAdvancing= */ true); testMuxerHolder = new TestMuxerHolder();
progressHolder = new ProgressHolder(); progressHolder = new ProgressHolder();
compositionArgumentCaptor = ArgumentCaptor.forClass(Composition.class); compositionArgumentCaptor = ArgumentCaptor.forClass(Composition.class);
createEncodersAndDecoders(); createEncodersAndDecoders();
...@@ -132,31 +126,36 @@ public final class TransformerEndToEndTest { ...@@ -132,31 +126,36 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_videoOnlyPassthrough_completesSuccessfully() throws Exception { public void start_videoOnlyPassthrough_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_VIDEO_ONLY));
} }
@Test @Test
public void start_audioOnlyPassthrough_completesSuccessfully() throws Exception { public void start_audioOnlyPassthrough_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
} }
@Test @Test
public void start_audioOnlyTranscoding_completesSuccessfully() throws Exception { public void start_audioOnlyTranscoding_completesSuccessfully() throws Exception {
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder() new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
...@@ -168,24 +167,29 @@ public final class TransformerEndToEndTest { ...@@ -168,24 +167,29 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac"));
} }
@Test @Test
public void start_audioAndVideo_completesSuccessfully() throws Exception { public void start_audioAndVideo_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_AUDIO_VIDEO));
} }
@Test @Test
public void start_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully() public void start_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully()
throws Exception { throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder() new MediaItem.Builder()
.setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S) .setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S)
...@@ -202,14 +206,14 @@ public final class TransformerEndToEndTest { ...@@ -202,14 +206,14 @@ public final class TransformerEndToEndTest {
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, context,
testMuxer, checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped")); getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped"));
} }
@Test @Test
public void start_withSubtitles_completesSuccessfully() throws Exception { public void start_withSubtitles_completesSuccessfully() throws Exception {
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build()) new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build())
.build(); .build();
...@@ -218,12 +222,14 @@ public final class TransformerEndToEndTest { ...@@ -218,12 +222,14 @@ public final class TransformerEndToEndTest {
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_WITH_SUBTITLES));
} }
@Test @Test
public void start_successiveExports_completesSuccessfully() throws Exception { public void start_successiveExports_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
// Transform first media item. // Transform first media item.
...@@ -235,12 +241,14 @@ public final class TransformerEndToEndTest { ...@@ -235,12 +241,14 @@ public final class TransformerEndToEndTest {
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_AUDIO_VIDEO));
} }
@Test @Test
public void start_concurrentExports_throwsError() { public void start_concurrentExports_throwsError() {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
...@@ -250,7 +258,8 @@ public final class TransformerEndToEndTest { ...@@ -250,7 +258,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_removeAudio_completesSuccessfully() throws Exception { public void start_removeAudio_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO)) new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO))
.setRemoveAudio(true) .setRemoveAudio(true)
...@@ -260,12 +269,15 @@ public final class TransformerEndToEndTest { ...@@ -260,12 +269,15 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
} }
@Test @Test
public void start_removeVideo_completesSuccessfully() throws Exception { public void start_removeVideo_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO)) new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO))
.setRemoveVideo(true) .setRemoveVideo(true)
...@@ -275,12 +287,15 @@ public final class TransformerEndToEndTest { ...@@ -275,12 +287,15 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
} }
@Test @Test
public void start_forceAudioTrackOnAudioOnly_isIgnored() throws Exception { public void start_forceAudioTrackOnAudioOnly_isIgnored() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build(); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence sequence = EditedMediaItemSequence sequence =
...@@ -294,12 +309,15 @@ public final class TransformerEndToEndTest { ...@@ -294,12 +309,15 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER)); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
} }
@Test @Test
public void start_forceAudioTrackOnAudioVideo_isIgnored() throws Exception { public void start_forceAudioTrackOnAudioVideo_isIgnored() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build(); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence sequence = EditedMediaItemSequence sequence =
...@@ -312,12 +330,14 @@ public final class TransformerEndToEndTest { ...@@ -312,12 +330,14 @@ public final class TransformerEndToEndTest {
transformer.start(composition, outputPath); transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_AUDIO_VIDEO));
} }
@Test @Test
public void start_forceAudioTrackAndRemoveAudio_generatesSilentAudio() throws Exception { public void start_forceAudioTrackAndRemoveAudio_generatesSilentAudio() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO)) new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO))
.setRemoveAudio(true) .setRemoveAudio(true)
...@@ -333,12 +353,15 @@ public final class TransformerEndToEndTest { ...@@ -333,12 +353,15 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
} }
@Test @Test
public void start_forceAudioTrackAndRemoveVideo_isIgnored() throws Exception { public void start_forceAudioTrackAndRemoveVideo_isIgnored() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO)) new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO))
.setRemoveVideo(true) .setRemoveVideo(true)
...@@ -353,12 +376,15 @@ public final class TransformerEndToEndTest { ...@@ -353,12 +376,15 @@ public final class TransformerEndToEndTest {
transformer.start(composition, outputPath); transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
} }
@Test @Test
public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception { public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build(); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence sequence = EditedMediaItemSequence sequence =
...@@ -372,14 +398,17 @@ public final class TransformerEndToEndTest { ...@@ -372,14 +398,17 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY + ".silentaudio")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_VIDEO_ONLY + ".silentaudio"));
} }
@Test @Test
public void start_adjustSampleRate_completesSuccessfully() throws Exception { public void start_adjustSampleRate_completesSuccessfully() throws Exception {
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setOutputSampleRateHz(48000); sonicAudioProcessor.setOutputSampleRateHz(48000);
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(sonicAudioProcessor); ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(sonicAudioProcessor);
Effects effects = new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of()); Effects effects = new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of());
...@@ -390,113 +419,17 @@ public final class TransformerEndToEndTest { ...@@ -390,113 +419,17 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
}
@Test
public void start_concatenateMediaItemsWithSameFormat_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence))
.setTransmuxAudio(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated"));
}
@Test
public void start_concatenateMediaItemsWithSameFormatAndEffects_completesSuccessfully()
throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), /* videoEffects= */ ImmutableList.of());
// The video track must be removed in order for the export to end. Indeed, the
// Robolectric decoder just copies the input buffers to the output and the audio timestamps are
// therefore computed based on the encoded samples (see [internal: b/178685617]). As a result,
// the audio timestamps are much smaller than they should be and the muxer waits for more audio
// samples before writing video samples.
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).setRemoveVideo(true).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence)).build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context, context,
testMuxer, checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated_with_high_pitch_and_no_video")); getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
}
@Test
public void start_concatenateSilenceAndAudio_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
EditedMediaItem noAudioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
EditedMediaItem audioEditedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence sequence =
new EditedMediaItemSequence(ImmutableList.of(noAudioEditedMediaItem, audioEditedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(sequence))
.experimentalSetForceAudioTrack(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silence_then_audio"));
}
@Test
public void start_concatenateSilenceAndAudioWithEffects_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), /* videoEffects= */ ImmutableList.of());
EditedMediaItem noAudioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).setEffects(effects).build();
EditedMediaItem audioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
EditedMediaItemSequence sequence =
new EditedMediaItemSequence(ImmutableList.of(noAudioEditedMediaItem, audioEditedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(sequence))
.experimentalSetForceAudioTrack(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silence_then_audio_with_effects"));
} }
@Test @Test
public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception { public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception {
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setOutputSampleRateHz(48000); sonicAudioProcessor.setOutputSampleRateHz(48000);
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(sonicAudioProcessor); ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(sonicAudioProcessor);
Effects effects = new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of()); Effects effects = new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of());
...@@ -513,33 +446,9 @@ public final class TransformerEndToEndTest { ...@@ -513,33 +446,9 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz")); context,
} checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
@Test
public void start_multipleMediaItemsAndTransmux_transmux() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effect videoEffect = RgbFilter.createGrayscaleFilter();
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), ImmutableList.of(videoEffect));
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence))
.setTransmuxAudio(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated"));
} }
@Test @Test
...@@ -548,7 +457,7 @@ public final class TransformerEndToEndTest { ...@@ -548,7 +457,7 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener(mockListener1) .addListener(mockListener1)
.addListener(mockListener2) .addListener(mockListener2)
.addListener(mockListener3) .addListener(mockListener3)
...@@ -570,7 +479,7 @@ public final class TransformerEndToEndTest { ...@@ -570,7 +479,7 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener(mockListener1) .addListener(mockListener1)
.addListener(mockListener2) .addListener(mockListener2)
.addListener(mockListener3) .addListener(mockListener3)
...@@ -599,7 +508,7 @@ public final class TransformerEndToEndTest { ...@@ -599,7 +508,7 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest = TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ true) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ true)
.addListener(mockListener1) .addListener(mockListener1)
.addListener(mockListener2) .addListener(mockListener2)
.addListener(mockListener3) .addListener(mockListener3)
...@@ -628,7 +537,7 @@ public final class TransformerEndToEndTest { ...@@ -628,7 +537,7 @@ public final class TransformerEndToEndTest {
AtomicBoolean deprecatedFallbackCalled1 = new AtomicBoolean(); AtomicBoolean deprecatedFallbackCalled1 = new AtomicBoolean();
AtomicBoolean deprecatedFallbackCalled2 = new AtomicBoolean(); AtomicBoolean deprecatedFallbackCalled2 = new AtomicBoolean();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
...@@ -660,7 +569,7 @@ public final class TransformerEndToEndTest { ...@@ -660,7 +569,7 @@ public final class TransformerEndToEndTest {
AtomicBoolean deprecatedFallbackCalled2 = new AtomicBoolean(); AtomicBoolean deprecatedFallbackCalled2 = new AtomicBoolean();
AtomicBoolean deprecatedFallbackCalled3 = new AtomicBoolean(); AtomicBoolean deprecatedFallbackCalled3 = new AtomicBoolean();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
...@@ -705,7 +614,7 @@ public final class TransformerEndToEndTest { ...@@ -705,7 +614,7 @@ public final class TransformerEndToEndTest {
public void start_withFallback_callsDeprecatedFallbackCallbacks() throws Exception { public void start_withFallback_callsDeprecatedFallbackCallbacks() throws Exception {
AtomicBoolean deprecatedFallbackCalled = new AtomicBoolean(); AtomicBoolean deprecatedFallbackCalled = new AtomicBoolean();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ true) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ true)
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
...@@ -732,7 +641,7 @@ public final class TransformerEndToEndTest { ...@@ -732,7 +641,7 @@ public final class TransformerEndToEndTest {
Transformer.Listener mockListener2 = mock(Transformer.Listener.class); Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
Transformer.Listener mockListener3 = mock(Transformer.Listener.class); Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
Transformer transformer1 = Transformer transformer1 =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener(mockListener1) .addListener(mockListener1)
.addListener(mockListener2) .addListener(mockListener2)
.addListener(mockListener3) .addListener(mockListener3)
...@@ -750,7 +659,8 @@ public final class TransformerEndToEndTest { ...@@ -750,7 +659,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_flattenForSlowMotion_completesSuccessfully() throws Exception { public void start_flattenForSlowMotion_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION)) new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION))
.setFlattenForSlowMotion(true) .setFlattenForSlowMotion(true)
...@@ -759,12 +669,16 @@ public final class TransformerEndToEndTest { ...@@ -759,12 +669,16 @@ public final class TransformerEndToEndTest {
transformer.start(editedMediaItem, outputPath); transformer.start(editedMediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION)); DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
} }
@Test @Test
public void start_completesWithValidBitrate() throws Exception { public void start_completesWithValidBitrate() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
...@@ -777,7 +691,7 @@ public final class TransformerEndToEndTest { ...@@ -777,7 +691,7 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withAudioEncoderFormatUnsupported_completesWithError() { public void start_withAudioEncoderFormatUnsupported_completesWithError() {
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder() new TransformationRequest.Builder()
.setAudioMimeType( .setAudioMimeType(
...@@ -797,7 +711,7 @@ public final class TransformerEndToEndTest { ...@@ -797,7 +711,7 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withAudioDecoderFormatUnsupported_completesWithError() { public void start_withAudioDecoderFormatUnsupported_completesWithError() {
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder() new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
...@@ -815,7 +729,8 @@ public final class TransformerEndToEndTest { ...@@ -815,7 +729,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withIoError_completesWithError() { public void start_withIoError_completesWithError() {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
...@@ -834,14 +749,18 @@ public final class TransformerEndToEndTest { ...@@ -834,14 +749,18 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest = TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build(); createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.addListener(mockListener)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
verify(mockListener) verify(mockListener)
.onFallbackApplied( .onFallbackApplied(
any(Composition.class), any(Composition.class),
...@@ -857,14 +776,18 @@ public final class TransformerEndToEndTest { ...@@ -857,14 +776,18 @@ public final class TransformerEndToEndTest {
TransformationRequest fallbackTransformationRequest = TransformationRequest fallbackTransformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build(); new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ true).addListener(mockListener).build(); createTransformerBuilder(testMuxerHolder, /* enableFallback= */ true)
.addListener(mockListener)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback")); context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
verify(mockListener) verify(mockListener)
.onFallbackApplied( .onFallbackApplied(
any(Composition.class), any(Composition.class),
...@@ -883,11 +806,12 @@ public final class TransformerEndToEndTest { ...@@ -883,11 +806,12 @@ public final class TransformerEndToEndTest {
context, context,
decoderFactory, decoderFactory,
/* forceInterpretHdrAsSdr= */ false, /* forceInterpretHdrAsSdr= */ false,
clock, new FakeClock(/* isAutoAdvancing= */ true),
mediaSourceFactory); mediaSourceFactory);
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1); Muxer.Factory muxerFactory =
new TestMuxerFactory(testMuxerHolder, /* maxDelayBetweenSamplesMs= */ 1);
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setAssetLoaderFactory(assetLoaderFactory) .setAssetLoaderFactory(assetLoaderFactory)
.setMuxerFactory(muxerFactory) .setMuxerFactory(muxerFactory)
.build(); .build();
...@@ -902,20 +826,25 @@ public final class TransformerEndToEndTest { ...@@ -902,20 +826,25 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withUnsetMaxDelayBetweenSamples_completesSuccessfully() throws Exception { public void start_withUnsetMaxDelayBetweenSamples_completesSuccessfully() throws Exception {
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET); Muxer.Factory muxerFactory =
new TestMuxerFactory(testMuxerHolder, /* maxDelayBetweenSamplesMs= */ C.TIME_UNSET);
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setMuxerFactory(muxerFactory).build(); createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setMuxerFactory(muxerFactory)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_AUDIO_VIDEO));
} }
@Test @Test
public void start_afterCancellation_completesSuccessfully() throws Exception { public void start_afterCancellation_completesSuccessfully() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
...@@ -936,7 +865,9 @@ public final class TransformerEndToEndTest { ...@@ -936,7 +865,9 @@ public final class TransformerEndToEndTest {
anotherThread.start(); anotherThread.start();
Looper looper = anotherThread.getLooper(); Looper looper = anotherThread.getLooper();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false).setLooper(looper).build(); createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setLooper(looper)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
AtomicReference<Exception> exception = new AtomicReference<>(); AtomicReference<Exception> exception = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1); CountDownLatch countDownLatch = new CountDownLatch(1);
...@@ -956,12 +887,14 @@ public final class TransformerEndToEndTest { ...@@ -956,12 +887,14 @@ public final class TransformerEndToEndTest {
countDownLatch.await(); countDownLatch.await();
assertThat(exception.get()).isNull(); assertThat(exception.get()).isNull();
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_AUDIO_VIDEO));
} }
@Test @Test
public void start_fromWrongThread_throwsError() throws Exception { public void start_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
HandlerThread anotherThread = new HandlerThread("AnotherThread"); HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>(); AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
...@@ -988,7 +921,7 @@ public final class TransformerEndToEndTest { ...@@ -988,7 +921,7 @@ public final class TransformerEndToEndTest {
public void start_withAssetLoaderAlwaysDecoding_pipelineExpectsDecoded() throws Exception { public void start_withAssetLoaderAlwaysDecoding_pipelineExpectsDecoded() throws Exception {
AtomicReference<SampleConsumer> sampleConsumerRef = new AtomicReference<>(); AtomicReference<SampleConsumer> sampleConsumerRef = new AtomicReference<>();
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setAssetLoaderFactory( .setAssetLoaderFactory(
new FakeAssetLoader.Factory(SUPPORTED_OUTPUT_TYPE_DECODED, sampleConsumerRef)) new FakeAssetLoader.Factory(SUPPORTED_OUTPUT_TYPE_DECODED, sampleConsumerRef))
.build(); .build();
...@@ -1003,7 +936,7 @@ public final class TransformerEndToEndTest { ...@@ -1003,7 +936,7 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withAssetLoaderNotDecodingAndDecodingNeeded_completesWithError() { public void start_withAssetLoaderNotDecodingAndDecodingNeeded_completesWithError() {
Transformer transformer = Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false) createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false)
.setAssetLoaderFactory( .setAssetLoaderFactory(
new FakeAssetLoader.Factory( new FakeAssetLoader.Factory(
SUPPORTED_OUTPUT_TYPE_ENCODED, /* sampleConsumerRef= */ null)) SUPPORTED_OUTPUT_TYPE_ENCODED, /* sampleConsumerRef= */ null))
...@@ -1023,7 +956,8 @@ public final class TransformerEndToEndTest { ...@@ -1023,7 +956,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void start_withNoOpEffects_transmuxes() throws Exception { public void start_withNoOpEffects_transmuxes() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
int mediaItemHeightPixels = 720; int mediaItemHeightPixels = 720;
ImmutableList<Effect> videoEffects = ImmutableList<Effect> videoEffects =
...@@ -1038,12 +972,14 @@ public final class TransformerEndToEndTest { ...@@ -1038,12 +972,14 @@ public final class TransformerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
// Video transcoding in unit tests is not supported. // Video transcoding in unit tests is not supported.
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY)); DumpFileAsserts.assertOutput(
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_VIDEO_ONLY));
} }
@Test @Test
public void getProgress_knownDuration_returnsConsistentStates() throws Exception { public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
AtomicInteger previousProgressState = AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
...@@ -1089,7 +1025,8 @@ public final class TransformerEndToEndTest { ...@@ -1089,7 +1025,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception { public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
List<Integer> progresses = new ArrayList<>(); List<Integer> progresses = new ArrayList<>();
Handler progressHandler = Handler progressHandler =
...@@ -1123,7 +1060,8 @@ public final class TransformerEndToEndTest { ...@@ -1123,7 +1060,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void getProgress_noCurrentExport_returnsNotStarted() throws Exception { public void getProgress_noCurrentExport_returnsNotStarted() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder); @Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
...@@ -1137,7 +1075,8 @@ public final class TransformerEndToEndTest { ...@@ -1137,7 +1075,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception { public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION);
AtomicInteger previousProgressState = AtomicInteger previousProgressState =
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY); new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
...@@ -1180,7 +1119,8 @@ public final class TransformerEndToEndTest { ...@@ -1180,7 +1119,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void getProgress_fromWrongThread_throwsError() throws Exception { public void getProgress_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
HandlerThread anotherThread = new HandlerThread("AnotherThread"); HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>(); AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1); CountDownLatch countDownLatch = new CountDownLatch(1);
...@@ -1204,7 +1144,8 @@ public final class TransformerEndToEndTest { ...@@ -1204,7 +1144,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void cancel_afterCompletion_doesNotThrow() throws Exception { public void cancel_afterCompletion_doesNotThrow() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
transformer.start(mediaItem, outputPath); transformer.start(mediaItem, outputPath);
...@@ -1214,7 +1155,8 @@ public final class TransformerEndToEndTest { ...@@ -1214,7 +1155,8 @@ public final class TransformerEndToEndTest {
@Test @Test
public void cancel_fromWrongThread_throwsError() throws Exception { public void cancel_fromWrongThread_throwsError() throws Exception {
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
HandlerThread anotherThread = new HandlerThread("AnotherThread"); HandlerThread anotherThread = new HandlerThread("AnotherThread");
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>(); AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1); CountDownLatch countDownLatch = new CountDownLatch(1);
...@@ -1236,141 +1178,6 @@ public final class TransformerEndToEndTest { ...@@ -1236,141 +1178,6 @@ public final class TransformerEndToEndTest {
assertThat(illegalStateException.get()).isNotNull(); 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 ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
}
}
private static final class SlowExtractorsFactory implements ExtractorsFactory { private static final class SlowExtractorsFactory implements ExtractorsFactory {
private final long delayBetweenReadsMs; private final long delayBetweenReadsMs;
...@@ -1439,75 +1246,4 @@ public final class TransformerEndToEndTest { ...@@ -1439,75 +1246,4 @@ public final class TransformerEndToEndTest {
} }
} }
} }
private static final class FakeAssetLoader implements AssetLoader {
public static final class Factory implements AssetLoader.Factory {
private final @SupportedOutputTypes int supportedOutputTypes;
@Nullable private final AtomicReference<SampleConsumer> sampleConsumerRef;
public Factory(
@SupportedOutputTypes int supportedOutputTypes,
@Nullable AtomicReference<SampleConsumer> sampleConsumerRef) {
this.supportedOutputTypes = supportedOutputTypes;
this.sampleConsumerRef = sampleConsumerRef;
}
@Override
public AssetLoader createAssetLoader(
EditedMediaItem editedMediaItem, Looper looper, Listener listener) {
return new FakeAssetLoader(listener, supportedOutputTypes, sampleConsumerRef);
}
}
private final AssetLoader.Listener listener;
private final @SupportedOutputTypes int supportedOutputTypes;
@Nullable private final AtomicReference<SampleConsumer> sampleConsumerRef;
public FakeAssetLoader(
Listener listener,
@SupportedOutputTypes int supportedOutputTypes,
@Nullable AtomicReference<SampleConsumer> sampleConsumerRef) {
this.listener = listener;
this.supportedOutputTypes = supportedOutputTypes;
this.sampleConsumerRef = sampleConsumerRef;
}
@Override
public void start() {
listener.onDurationUs(10_000_000);
listener.onTrackCount(1);
Format format =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_AAC)
.setSampleRate(44100)
.setChannelCount(2)
.build();
try {
listener.onTrackAdded(
format, supportedOutputTypes, /* streamStartPositionUs= */ 0, /* streamOffsetUs= */ 0);
SampleConsumer sampleConsumer = listener.onOutputFormat(format);
if (sampleConsumerRef != null) {
sampleConsumerRef.set(sampleConsumer);
}
} catch (ExportException e) {
throw new IllegalStateException(e);
}
}
@Override
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
return 0;
}
@Override
public ImmutableMap<Integer, String> getDecoderNames() {
return ImmutableMap.of();
}
@Override
public void release() {}
}
} }
/*
* Copyright 2023 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 com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.TestUtil.ASSET_URI_PREFIX;
import static com.google.android.exoplayer2.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static com.google.android.exoplayer2.transformer.TestUtil.createEncodersAndDecoders;
import static com.google.android.exoplayer2.transformer.TestUtil.createTransformerBuilder;
import static com.google.android.exoplayer2.transformer.TestUtil.getDumpFileName;
import static com.google.android.exoplayer2.transformer.TestUtil.removeEncodersAndDecoders;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.audio.SonicAudioProcessor;
import com.google.android.exoplayer2.effect.RgbFilter;
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
import com.google.android.exoplayer2.transformer.TestUtil.TestMuxerFactory.TestMuxerHolder;
import com.google.android.exoplayer2.util.Effect;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* End-to-end test for exporting a single {@link EditedMediaItemSequence} containing multiple {@link
* EditedMediaItem} instances with {@link Transformer}.
*/
@RunWith(AndroidJUnit4.class)
public final class SequenceExportTest {
private Context context;
private String outputPath;
private TestMuxerHolder testMuxerHolder;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
outputPath = Util.createTempFile(context, "TransformerTest").getPath();
testMuxerHolder = new TestMuxerHolder();
createEncodersAndDecoders();
}
@After
public void tearDown() throws Exception {
Files.delete(Paths.get(outputPath));
removeEncodersAndDecoders();
}
@Test
public void start_concatenateMediaItemsWithSameFormat_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence))
.setTransmuxAudio(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated"));
}
@Test
public void start_concatenateMediaItemsWithSameFormatAndEffects_completesSuccessfully()
throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), /* videoEffects= */ ImmutableList.of());
// The video track must be removed in order for the export to end. Indeed, the
// Robolectric decoder just copies the input buffers to the output and the audio timestamps are
// therefore computed based on the encoded samples (see [internal: b/178685617]). As a result,
// the audio timestamps are much smaller than they should be and the muxer waits for more audio
// samples before writing video samples.
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).setRemoveVideo(true).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence)).build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated_with_high_pitch_and_no_video"));
}
@Test
public void start_concatenateSilenceAndAudio_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
EditedMediaItem noAudioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
EditedMediaItem audioEditedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
EditedMediaItemSequence sequence =
new EditedMediaItemSequence(ImmutableList.of(noAudioEditedMediaItem, audioEditedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(sequence))
.experimentalSetForceAudioTrack(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".silence_then_audio"));
}
@Test
public void start_concatenateSilenceAndAudioWithEffects_completesSuccessfully() throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), /* videoEffects= */ ImmutableList.of());
EditedMediaItem noAudioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).setEffects(effects).build();
EditedMediaItem audioEditedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
EditedMediaItemSequence sequence =
new EditedMediaItemSequence(ImmutableList.of(noAudioEditedMediaItem, audioEditedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(sequence))
.experimentalSetForceAudioTrack(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".silence_then_audio_with_effects"));
}
@Test
public void start_multipleMediaItemsAndTransmux_transmux() throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setPitch(2f);
Effect videoEffect = RgbFilter.createGrayscaleFilter();
Effects effects =
new Effects(ImmutableList.of(sonicAudioProcessor), ImmutableList.of(videoEffect));
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
EditedMediaItemSequence editedMediaItemSequence =
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
Composition composition =
new Composition.Builder(ImmutableList.of(editedMediaItemSequence))
.setTransmuxAudio(true)
.setTransmuxVideo(true)
.build();
transformer.start(composition, outputPath);
TransformerTestRunner.runLooper(transformer);
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".concatenated"));
}
}
/*
* Copyright 2023 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 com.google.android.exoplayer2.transformer;
import android.content.Context;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Looper;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
/** Utility class for {@link Transformer} unit tests */
public final class TestUtil {
public static final class TestMuxerFactory implements Muxer.Factory {
public static final class TestMuxerHolder {
@Nullable public TestMuxer testMuxer;
}
private final TestMuxerHolder testMuxerHolder;
private final Muxer.Factory defaultMuxerFactory;
public TestMuxerFactory(TestMuxerHolder testMuxerHolder) {
this(testMuxerHolder, /* maxDelayBetweenSamplesMs= */ C.TIME_UNSET);
}
public TestMuxerFactory(TestMuxerHolder testMuxerHolder, long maxDelayBetweenSamplesMs) {
this.testMuxerHolder = testMuxerHolder;
defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs);
}
@Override
public Muxer create(String path) throws Muxer.MuxerException {
testMuxerHolder.testMuxer = new TestMuxer(path, defaultMuxerFactory);
return testMuxerHolder.testMuxer;
}
@Override
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
}
}
public static final class FakeAssetLoader implements AssetLoader {
public static final class Factory implements AssetLoader.Factory {
private final @SupportedOutputTypes int supportedOutputTypes;
@Nullable private final AtomicReference<SampleConsumer> sampleConsumerRef;
public Factory(
@SupportedOutputTypes int supportedOutputTypes,
@Nullable AtomicReference<SampleConsumer> sampleConsumerRef) {
this.supportedOutputTypes = supportedOutputTypes;
this.sampleConsumerRef = sampleConsumerRef;
}
@Override
public AssetLoader createAssetLoader(
EditedMediaItem editedMediaItem, Looper looper, Listener listener) {
return new FakeAssetLoader(listener, supportedOutputTypes, sampleConsumerRef);
}
}
private final AssetLoader.Listener listener;
private final @SupportedOutputTypes int supportedOutputTypes;
@Nullable private final AtomicReference<SampleConsumer> sampleConsumerRef;
public FakeAssetLoader(
Listener listener,
@SupportedOutputTypes int supportedOutputTypes,
@Nullable AtomicReference<SampleConsumer> sampleConsumerRef) {
this.listener = listener;
this.supportedOutputTypes = supportedOutputTypes;
this.sampleConsumerRef = sampleConsumerRef;
}
@Override
public void start() {
listener.onDurationUs(10_000_000);
listener.onTrackCount(1);
Format format =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_AAC)
.setSampleRate(44100)
.setChannelCount(2)
.build();
try {
listener.onTrackAdded(
format, supportedOutputTypes, /* streamStartPositionUs= */ 0, /* streamOffsetUs= */ 0);
SampleConsumer sampleConsumer = listener.onOutputFormat(format);
if (sampleConsumerRef != null) {
sampleConsumerRef.set(sampleConsumer);
}
} catch (ExportException e) {
throw new IllegalStateException(e);
}
}
@Override
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
return 0;
}
@Override
public ImmutableMap<Integer, String> getDecoderNames() {
return ImmutableMap.of();
}
@Override
public void release() {}
}
public static final String ASSET_URI_PREFIX = "asset:///media/";
public static final String FILE_VIDEO_ONLY = "mp4/sample_18byte_nclx_colr.mp4";
public static final String FILE_AUDIO_VIDEO = "mp4/sample.mp4";
public static final String FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S =
"mp4/sample_with_increasing_timestamps_320w_240h.mp4";
public static final String FILE_WITH_SUBTITLES = "mkv/sample_with_srt.mkv";
public static final String FILE_WITH_SEF_SLOW_MOTION = "mp4/sample_sef_slow_motion.mp4";
public static final String FILE_AUDIO_UNSUPPORTED_BY_DECODER = "amr/sample_wb.amr";
public static final String FILE_AUDIO_UNSUPPORTED_BY_ENCODER = "amr/sample_nb.amr";
public static final String FILE_AUDIO_UNSUPPORTED_BY_MUXER = "mp4/sample_ac3.mp4";
public static final String FILE_UNKNOWN_DURATION = "mp4/sample_fragmented.mp4";
private static final String DUMP_FILE_OUTPUT_DIRECTORY = "transformerdumps";
private static final String DUMP_FILE_EXTENSION = "dump";
private TestUtil() {}
public 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);
}
public static void removeEncodersAndDecoders() {
ShadowMediaCodec.clearCodecs();
ShadowMediaCodecList.reset();
EncoderUtil.clearCachedEncoders();
}
public static Transformer.Builder createTransformerBuilder(
TestMuxerFactory.TestMuxerHolder testMuxerHolder, boolean enableFallback) {
Context context = ApplicationProvider.getApplicationContext();
return new Transformer.Builder(context)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.setMuxerFactory(new TestMuxerFactory(testMuxerHolder))
.setEncoderFactory(
new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build());
}
public static String getDumpFileName(String originalFileName) {
return DUMP_FILE_OUTPUT_DIRECTORY + '/' + originalFileName + '.' + DUMP_FILE_EXTENSION;
}
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());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment