mirror of
https://github.com/androidx/media.git
synced 2025-05-18 13:09:56 +08:00
Add test for audio and video from different sources
PiperOrigin-RevId: 515379858
This commit is contained in:
parent
7031d2c6f4
commit
1865e38108
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ import androidx.media3.common.util.SystemClock;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -174,20 +176,20 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the {@link EditedMediaItem}, saving a summary of the export to the application cache.
|
* Exports the {@link Composition}, saving a summary of the export to the application cache.
|
||||||
*
|
*
|
||||||
* @param testId A unique identifier for the transformer test run.
|
* @param testId A unique identifier for the transformer test run.
|
||||||
* @param editedMediaItem The {@link EditedMediaItem} to export.
|
* @param composition The {@link Composition} to export.
|
||||||
* @return The {@link ExportTestResult}.
|
* @return The {@link ExportTestResult}.
|
||||||
* @throws Exception The cause of the export not completing.
|
* @throws Exception The cause of the export not completing.
|
||||||
*/
|
*/
|
||||||
public ExportTestResult run(String testId, EditedMediaItem editedMediaItem) throws Exception {
|
public ExportTestResult run(String testId, Composition composition) throws Exception {
|
||||||
JSONObject resultJson = new JSONObject();
|
JSONObject resultJson = new JSONObject();
|
||||||
if (inputValues != null) {
|
if (inputValues != null) {
|
||||||
resultJson.put("inputValues", JSONObject.wrap(inputValues));
|
resultJson.put("inputValues", JSONObject.wrap(inputValues));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ExportTestResult exportTestResult = runInternal(testId, editedMediaItem);
|
ExportTestResult exportTestResult = runInternal(testId, composition);
|
||||||
resultJson.put("exportResult", exportTestResult.asJsonObject());
|
resultJson.put("exportResult", exportTestResult.asJsonObject());
|
||||||
if (exportTestResult.exportResult.exportException != null) {
|
if (exportTestResult.exportResult.exportException != null) {
|
||||||
throw exportTestResult.exportResult.exportException;
|
throw exportTestResult.exportResult.exportException;
|
||||||
@ -209,6 +211,21 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the {@link EditedMediaItem}, saving a summary of the export to the application cache.
|
||||||
|
*
|
||||||
|
* @param testId A unique identifier for the transformer test run.
|
||||||
|
* @param editedMediaItem The {@link EditedMediaItem} to export.
|
||||||
|
* @return The {@link ExportTestResult}.
|
||||||
|
* @throws Exception The cause of the export not completing.
|
||||||
|
*/
|
||||||
|
public ExportTestResult run(String testId, EditedMediaItem editedMediaItem) throws Exception {
|
||||||
|
EditedMediaItemSequence sequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem));
|
||||||
|
Composition composition = new Composition.Builder(ImmutableList.of(sequence)).build();
|
||||||
|
return run(testId, composition);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the {@link MediaItem}, saving a summary of the export to the application cache.
|
* Exports the {@link MediaItem}, saving a summary of the export to the application cache.
|
||||||
*
|
*
|
||||||
@ -223,33 +240,49 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the {@link EditedMediaItem}.
|
* Exports the {@link Composition}.
|
||||||
*
|
*
|
||||||
* @param testId An identifier for the test.
|
* @param testId An identifier for the test.
|
||||||
* @param editedMediaItem The {@link EditedMediaItem} to export.
|
* @param composition The {@link Composition} to export.
|
||||||
* @return The {@link ExportTestResult}.
|
* @return The {@link ExportTestResult}.
|
||||||
* @throws IllegalStateException See {@link Transformer#start(EditedMediaItem, String)}.
|
* @throws IllegalStateException See {@link Transformer#start(Composition, String)}.
|
||||||
* @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
|
* @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
|
||||||
* complete.
|
* complete.
|
||||||
* @throws IOException If an error occurs opening the output file for writing.
|
* @throws IOException If an error occurs opening the output file for writing.
|
||||||
* @throws TimeoutException If the export has not completed after {@linkplain
|
* @throws TimeoutException If the export has not completed after {@linkplain
|
||||||
* Builder#setTimeoutSeconds(int) the given timeout}.
|
* Builder#setTimeoutSeconds(int) the given timeout}.
|
||||||
*/
|
*/
|
||||||
private ExportTestResult runInternal(String testId, EditedMediaItem editedMediaItem)
|
private ExportTestResult runInternal(String testId, Composition composition)
|
||||||
throws InterruptedException, IOException, TimeoutException {
|
throws InterruptedException, IOException, TimeoutException {
|
||||||
MediaItem mediaItem = editedMediaItem.mediaItem;
|
if (requestCalculateSsim) {
|
||||||
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
|
checkArgument(
|
||||||
&& requestCalculateSsim) {
|
composition.sequences.size() == 1
|
||||||
throw new UnsupportedOperationException(
|
&& composition.sequences.get(0).editedMediaItems.size() == 1,
|
||||||
|
"SSIM is only relevant for single MediaItem compositions");
|
||||||
|
checkArgument(
|
||||||
|
composition
|
||||||
|
.sequences
|
||||||
|
.get(0)
|
||||||
|
.editedMediaItems
|
||||||
|
.get(0)
|
||||||
|
.mediaItem
|
||||||
|
.clippingConfiguration
|
||||||
|
.equals(MediaItem.ClippingConfiguration.UNSET),
|
||||||
"SSIM calculation is not supported for clipped inputs.");
|
"SSIM calculation is not supported for clipped inputs.");
|
||||||
}
|
}
|
||||||
|
if (!hasNetworkConnection(context)) {
|
||||||
Uri mediaItemUri = checkNotNull(mediaItem.localConfiguration).uri;
|
for (EditedMediaItemSequence sequence : composition.sequences) {
|
||||||
String scheme = checkNotNull(mediaItemUri.getScheme());
|
for (EditedMediaItem editedMediaItem : sequence.editedMediaItems) {
|
||||||
if ((scheme.equals("http") || scheme.equals("https")) && !hasNetworkConnection(context)) {
|
Uri mediaItemUri = checkNotNull(editedMediaItem.mediaItem.localConfiguration).uri;
|
||||||
throw new UnsupportedOperationException(
|
String scheme = checkNotNull(mediaItemUri.getScheme());
|
||||||
"Input network file requested on device with no network connection. Input file name: "
|
if ((scheme.equals("http") || scheme.equals("https"))) {
|
||||||
+ mediaItemUri);
|
throw new IllegalArgumentException(
|
||||||
|
"Input network file requested on device with no network connection. Input file"
|
||||||
|
+ " name: "
|
||||||
|
+ mediaItemUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AtomicReference<@NullableType FallbackDetails> fallbackDetailsReference =
|
AtomicReference<@NullableType FallbackDetails> fallbackDetailsReference =
|
||||||
@ -307,7 +340,7 @@ public class TransformerAndroidTestRunner {
|
|||||||
.runOnMainSync(
|
.runOnMainSync(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
testTransformer.start(editedMediaItem, outputVideoFile.getAbsolutePath());
|
testTransformer.start(composition, outputVideoFile.getAbsolutePath());
|
||||||
// Catch all exceptions to report. Exceptions thrown here and not caught will NOT
|
// Catch all exceptions to report. Exceptions thrown here and not caught will NOT
|
||||||
// propagate.
|
// propagate.
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -359,6 +392,7 @@ public class TransformerAndroidTestRunner {
|
|||||||
return testResultBuilder.build();
|
return testResultBuilder.build();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
MediaItem mediaItem = composition.sequences.get(0).editedMediaItems.get(0).mediaItem;
|
||||||
double ssim =
|
double ssim =
|
||||||
SsimHelper.calculate(
|
SsimHelper.calculate(
|
||||||
context,
|
context,
|
||||||
|
@ -27,7 +27,10 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.audio.AudioProcessor;
|
||||||
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.effect.Presentation;
|
import androidx.media3.effect.Presentation;
|
||||||
|
import androidx.media3.effect.RgbFilter;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -185,6 +188,51 @@ public class TransformerEndToEndTest {
|
|||||||
assertThat(exception).hasMessageThat().contains("video");
|
assertThat(exception).hasMessageThat().contains("video");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void start_audioVideoTranscodedFromDifferentSequences_producesExpectedResult()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer = new Transformer.Builder(context).build();
|
||||||
|
String testId = "start_audioVideoTranscodedFromDifferentSequences_producesExpectedResult";
|
||||||
|
ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(new SonicAudioProcessor());
|
||||||
|
ImmutableList<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_URI_STRING));
|
||||||
|
EditedMediaItem editedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem)
|
||||||
|
.setEffects(new Effects(audioProcessors, videoEffects))
|
||||||
|
.build();
|
||||||
|
ExportTestResult expectedResult =
|
||||||
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
|
.build()
|
||||||
|
.run(testId, editedMediaItem);
|
||||||
|
EditedMediaItem audioEditedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem)
|
||||||
|
.setEffects(new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of()))
|
||||||
|
.setRemoveVideo(true)
|
||||||
|
.build();
|
||||||
|
EditedMediaItemSequence audioSequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(audioEditedMediaItem));
|
||||||
|
EditedMediaItem videoEditedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem)
|
||||||
|
.setEffects(new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects))
|
||||||
|
.setRemoveAudio(true)
|
||||||
|
.build();
|
||||||
|
EditedMediaItemSequence videoSequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(videoEditedMediaItem));
|
||||||
|
Composition composition =
|
||||||
|
new Composition.Builder(ImmutableList.of(audioSequence, videoSequence)).build();
|
||||||
|
|
||||||
|
ExportTestResult result =
|
||||||
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
|
.build()
|
||||||
|
.run(testId, composition);
|
||||||
|
|
||||||
|
assertThat(result.exportResult.channelCount)
|
||||||
|
.isEqualTo(expectedResult.exportResult.channelCount);
|
||||||
|
assertThat(result.exportResult.videoFrameCount)
|
||||||
|
.isEqualTo(expectedResult.exportResult.videoFrameCount);
|
||||||
|
assertThat(result.exportResult.durationMs).isEqualTo(expectedResult.exportResult.durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class VideoUnsupportedEncoderFactory implements Codec.EncoderFactory {
|
private static final class VideoUnsupportedEncoderFactory implements Codec.EncoderFactory {
|
||||||
|
|
||||||
private final Codec.EncoderFactory encoderFactory;
|
private final Codec.EncoderFactory encoderFactory;
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
|
||||||
import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED;
|
import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED;
|
||||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||||
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
|
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
|
||||||
@ -54,6 +53,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
private boolean isRunning;
|
private boolean isRunning;
|
||||||
private long streamStartPositionUs;
|
private long streamStartPositionUs;
|
||||||
|
private boolean shouldInitDecoder;
|
||||||
|
|
||||||
public ExoAssetLoaderBaseRenderer(
|
public ExoAssetLoaderBaseRenderer(
|
||||||
@C.TrackType int trackType,
|
@C.TrackType int trackType,
|
||||||
@ -97,7 +97,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
@Override
|
@Override
|
||||||
public void render(long positionUs, long elapsedRealtimeUs) {
|
public void render(long positionUs, long elapsedRealtimeUs) {
|
||||||
try {
|
try {
|
||||||
if (!isRunning || isEnded() || !hasReadInputFormat()) {
|
if (!isRunning || isEnded() || !readInputFormatAndInitDecoderIfNeeded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,35 +195,39 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
* {@linkplain Codec decoder}.
|
* {@linkplain Codec decoder}.
|
||||||
*/
|
*/
|
||||||
@EnsuresNonNullIf(expression = "inputFormat", result = true)
|
@EnsuresNonNullIf(expression = "inputFormat", result = true)
|
||||||
private boolean hasReadInputFormat() throws ExportException {
|
private boolean readInputFormatAndInitDecoderIfNeeded() throws ExportException {
|
||||||
if (inputFormat != null) {
|
if (inputFormat != null && !shouldInitDecoder) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatHolder formatHolder = getFormatHolder();
|
if (inputFormat == null) {
|
||||||
@ReadDataResult
|
FormatHolder formatHolder = getFormatHolder();
|
||||||
int result = readSource(formatHolder, decoderInputBuffer, /* readFlags= */ FLAG_REQUIRE_FORMAT);
|
@ReadDataResult
|
||||||
if (result != C.RESULT_FORMAT_READ) {
|
int result =
|
||||||
return false;
|
readSource(formatHolder, decoderInputBuffer, /* readFlags= */ FLAG_REQUIRE_FORMAT);
|
||||||
|
if (result != C.RESULT_FORMAT_READ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
inputFormat = overrideFormat(checkNotNull(formatHolder.format));
|
||||||
|
onInputFormatRead(inputFormat);
|
||||||
|
shouldInitDecoder =
|
||||||
|
assetLoaderListener.onTrackAdded(
|
||||||
|
inputFormat,
|
||||||
|
SUPPORTED_OUTPUT_TYPE_DECODED | SUPPORTED_OUTPUT_TYPE_ENCODED,
|
||||||
|
streamStartPositionUs,
|
||||||
|
streamOffsetUs);
|
||||||
}
|
}
|
||||||
inputFormat = overrideFormat(checkNotNull(formatHolder.format));
|
|
||||||
onInputFormatRead(inputFormat);
|
|
||||||
|
|
||||||
boolean decodeOutput =
|
if (shouldInitDecoder) {
|
||||||
assetLoaderListener.onTrackAdded(
|
if (getProcessedTrackType(inputFormat.sampleMimeType) == C.TRACK_TYPE_VIDEO) {
|
||||||
inputFormat,
|
|
||||||
SUPPORTED_OUTPUT_TYPE_DECODED | SUPPORTED_OUTPUT_TYPE_ENCODED,
|
|
||||||
streamStartPositionUs,
|
|
||||||
streamOffsetUs);
|
|
||||||
if (decodeOutput) {
|
|
||||||
if (getProcessedTrackType(inputFormat.sampleMimeType) == C.TRACK_TYPE_AUDIO) {
|
|
||||||
initDecoder(inputFormat);
|
|
||||||
} else {
|
|
||||||
// TODO(b/237674316): Move surface creation out of video sampleConsumer. Init decoder and
|
// TODO(b/237674316): Move surface creation out of video sampleConsumer. Init decoder and
|
||||||
// get decoder output Format before init sampleConsumer.
|
// get decoder output Format before init sampleConsumer.
|
||||||
checkState(ensureSampleConsumerInitialized());
|
if (!ensureSampleConsumerInitialized()) {
|
||||||
initDecoder(inputFormat);
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
initDecoder(inputFormat);
|
||||||
|
shouldInitDecoder = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -702,7 +702,6 @@ public final class Transformer {
|
|||||||
* @throws IllegalStateException If an export is already in progress.
|
* @throws IllegalStateException If an export is already in progress.
|
||||||
*/
|
*/
|
||||||
public void start(Composition composition, String path) {
|
public void start(Composition composition, String path) {
|
||||||
checkArgument(composition.sequences.size() == 1);
|
|
||||||
checkArgument(composition.effects.audioProcessors.isEmpty());
|
checkArgument(composition.effects.audioProcessors.isEmpty());
|
||||||
// Only supports Presentation in video effects.
|
// Only supports Presentation in video effects.
|
||||||
ImmutableList<Effect> videoEffects = composition.effects.videoEffects;
|
ImmutableList<Effect> videoEffects = composition.effects.videoEffects;
|
||||||
|
@ -465,7 +465,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
} else {
|
} else {
|
||||||
outputHasVideo.set(true);
|
outputHasVideo.set(true);
|
||||||
}
|
}
|
||||||
if (trackCountsToReport.get() == 0 && tracksToAdd.decrementAndGet() == 0) {
|
if (tracksToAdd.decrementAndGet() == 0 && trackCountsToReport.get() == 0) {
|
||||||
int outputTrackCount = (outputHasAudio.get() ? 1 : 0) + (outputHasVideo.get() ? 1 : 0);
|
int outputTrackCount = (outputHasAudio.get() ? 1 : 0) + (outputHasVideo.get() ? 1 : 0);
|
||||||
muxerWrapper.setTrackCount(outputTrackCount);
|
muxerWrapper.setTrackCount(outputTrackCount);
|
||||||
fallbackListener.setTrackCount(outputTrackCount);
|
fallbackListener.setTrackCount(outputTrackCount);
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.media3.transformer;
|
||||||
|
|
||||||
|
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
|
||||||
|
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO;
|
||||||
|
import static androidx.media3.transformer.TestUtil.createTransformerBuilder;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.transformer.TestUtil.TestMuxerFactory.TestMuxerHolder;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
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 {@link Composition} containing multiple {@link
|
||||||
|
* EditedMediaItemSequence} instances with {@link Transformer}.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class CompositionExportTest {
|
||||||
|
|
||||||
|
private String outputPath;
|
||||||
|
private TestMuxerHolder testMuxerHolder;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
outputPath = Util.createTempFile(context, "TransformerTest").getPath();
|
||||||
|
testMuxerHolder = new TestUtil.TestMuxerFactory.TestMuxerHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
Files.delete(Paths.get(outputPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void start_audioVideoTransmuxedFromDifferentSequences_producesExpectedResult()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||||
|
transformer.start(mediaItem, outputPath);
|
||||||
|
ExportResult expectedExportResult = TransformerTestRunner.runLooper(transformer);
|
||||||
|
EditedMediaItem audioEditedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem).setRemoveVideo(true).build();
|
||||||
|
EditedMediaItemSequence audioSequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(audioEditedMediaItem));
|
||||||
|
EditedMediaItem videoEditedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
|
||||||
|
EditedMediaItemSequence videoSequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(videoEditedMediaItem));
|
||||||
|
Composition composition =
|
||||||
|
new Composition.Builder(ImmutableList.of(audioSequence, videoSequence))
|
||||||
|
.setTransmuxAudio(true)
|
||||||
|
.setTransmuxVideo(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
transformer.start(composition, outputPath);
|
||||||
|
ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
// We can't compare the muxer output against a dump file because the asset loaders in each
|
||||||
|
// sequence load samples from their own thread, independently of each other, which makes the
|
||||||
|
// output non-deterministic.
|
||||||
|
assertThat(exportResult.channelCount).isEqualTo(expectedExportResult.channelCount);
|
||||||
|
assertThat(exportResult.videoFrameCount).isEqualTo(expectedExportResult.videoFrameCount);
|
||||||
|
assertThat(exportResult.durationMs).isEqualTo(expectedExportResult.durationMs);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user