Rollback of 7e63997459
*** Original commit *** Add TransformerTestBuilderFactory to make transformer testable by apps *** PiperOrigin-RevId: 496342997
This commit is contained in:
parent
a974e60379
commit
8ee8910788
@ -26,7 +26,6 @@ dependencies {
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'com.squareup.okhttp3:mockwebserver:' + okhttpVersion
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + ':lib-transformer')
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
}
|
||||
|
||||
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.test.utils;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.transformer.DefaultEncoderFactory;
|
||||
import androidx.media3.transformer.DefaultMuxer;
|
||||
import androidx.media3.transformer.Muxer;
|
||||
import androidx.media3.transformer.Transformer;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Creates a {@link Transformer.Builder} setting up some of the common resources needed for testing
|
||||
* {@link Transformer}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class TestTransformerBuilderFactory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
private static @MonotonicNonNull TestMuxer testMuxer;
|
||||
private long maxDelayBetweenSamplesMs;
|
||||
|
||||
/** Creates a new instance */
|
||||
public TestTransformerBuilderFactory(Context context) {
|
||||
this.context = context;
|
||||
maxDelayBetweenSamplesMs = DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the muxer's {@linkplain Muxer#getMaxDelayBetweenSamplesMs() max delay} between samples.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public TestTransformerBuilderFactory setMaxDelayBetweenSamplesMs(long maxDelayBetweenSamplesMs) {
|
||||
this.maxDelayBetweenSamplesMs = maxDelayBetweenSamplesMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a {@link Transformer.Builder} using the provided values or their defaults. */
|
||||
@SuppressLint("VisibleForTests") // Suppresses warning on setting the clock outside of a test file
|
||||
public Transformer.Builder create(boolean enableFallback) {
|
||||
Clock clock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
Muxer.Factory defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs);
|
||||
return new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory(defaultMuxerFactory))
|
||||
.setEncoderFactory(
|
||||
new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test muxer used in the {@link Transformer.Builder}.
|
||||
*
|
||||
* <p>This method should only be called after the transformation is completed.
|
||||
*/
|
||||
public TestMuxer getTestMuxer() {
|
||||
return checkStateNotNull(testMuxer);
|
||||
}
|
||||
|
||||
private static final class TestMuxerFactory implements Muxer.Factory {
|
||||
|
||||
private final Muxer.Factory defaultMuxerFactory;
|
||||
|
||||
public TestMuxerFactory(Muxer.Factory defaultMuxerFactory) {
|
||||
this.defaultMuxerFactory = defaultMuxerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Muxer create(String path) throws Muxer.MuxerException {
|
||||
testMuxer = new TestMuxer(path, defaultMuxerFactory);
|
||||
return testMuxer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException {
|
||||
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory);
|
||||
return testMuxer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
|
||||
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ dependencies {
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + ':lib-transformer')
|
||||
implementation project(modulePrefix + 'test-utils')
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,10 @@
|
||||
package androidx.media3.test.utils.robolectric;
|
||||
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
||||
import androidx.media3.transformer.EncoderUtil;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -35,247 +30,105 @@ import org.robolectric.shadows.ShadowMediaCodec;
|
||||
import org.robolectric.shadows.ShadowMediaCodecList;
|
||||
|
||||
/**
|
||||
* A JUnit @Rule to configure {@link ShadowMediaCodec} for transcoding or decoding.
|
||||
* A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}.
|
||||
*
|
||||
* <p>Registers {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} instances for ExoPlayer
|
||||
* and Transformer tests.
|
||||
* <p>Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video
|
||||
* MIME type known by ExoPlayer.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class ShadowMediaCodecConfig extends ExternalResource {
|
||||
|
||||
private static final String EXOTEST_VIDEO_AVC = "exotest.video.avc";
|
||||
private static final String EXOTEST_VIDEO_MPEG2 = "exotest.video.mpeg2";
|
||||
private static final String EXOTEST_VIDEO_VP9 = "exotest.video.vp9";
|
||||
private static final String EXOTEST_AUDIO_AAC = "exotest.audio.aac";
|
||||
private static final String EXOTEST_AUDIO_AC3 = "exotest.audio.ac3";
|
||||
private static final String EXOTEST_AUDIO_AC4 = "exotest.audio.ac4";
|
||||
private static final String EXOTEST_AUDIO_E_AC3 = "exotest.audio.eac3";
|
||||
private static final String EXOTEST_AUDIO_E_AC3_JOC = "exotest.audio.eac3joc";
|
||||
private static final String EXOTEST_AUDIO_FLAC = "exotest.audio.flac";
|
||||
private static final String EXOTEST_AUDIO_MPEG = "exotest.audio.mpeg";
|
||||
private static final String EXOTEST_AUDIO_MPEG_L2 = "exotest.audio.mpegl2";
|
||||
private static final String EXOTEST_AUDIO_OPUS = "exotest.audio.opus";
|
||||
private static final String EXOTEST_AUDIO_VORBIS = "exotest.audio.vorbis";
|
||||
private static final String EXOTEST_AUDIO_RAW = "exotest.audio.raw";
|
||||
|
||||
private final boolean forTranscoding;
|
||||
|
||||
private ShadowMediaCodecConfig(boolean forTranscoding) {
|
||||
this.forTranscoding = forTranscoding;
|
||||
}
|
||||
|
||||
/** Creates an instance that configures {@link ShadowMediaCodec} for Transformer transcoding. */
|
||||
public static ShadowMediaCodecConfig forTranscoding() {
|
||||
return new ShadowMediaCodecConfig(/* forTranscoding= */ true);
|
||||
}
|
||||
|
||||
/** Creates an instance that configures {@link ShadowMediaCodec} for Exoplayer decoding. */
|
||||
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
|
||||
return new ShadowMediaCodecConfig(/* forTranscoding= */ false);
|
||||
return new ShadowMediaCodecConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
if (forTranscoding) {
|
||||
addTranscodingCodecs();
|
||||
} else {
|
||||
addDecodingCodecs();
|
||||
}
|
||||
}
|
||||
|
||||
private void addTranscodingCodecs() {
|
||||
ShadowMediaCodec.CodecConfig codecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 10_000,
|
||||
/* outputBufferSize= */ 10_000,
|
||||
/* codec= */ (in, out) -> out.put(in));
|
||||
addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ true);
|
||||
addTransformerCodec(MimeTypes.AUDIO_AC3, codecConfig, /* isDecoder= */ true);
|
||||
addTransformerCodec(MimeTypes.AUDIO_AMR_NB, codecConfig, /* isDecoder= */ true);
|
||||
addTransformerCodec(MimeTypes.AUDIO_AAC, codecConfig, /* isDecoder= */ false);
|
||||
|
||||
ShadowMediaCodec.CodecConfig throwingCodecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 10_000,
|
||||
/* outputBufferSize= */ 10_000,
|
||||
new ShadowMediaCodec.CodecConfig.Codec() {
|
||||
|
||||
@Override
|
||||
public void process(ByteBuffer in, ByteBuffer out) {
|
||||
out.put(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigured(
|
||||
MediaFormat format,
|
||||
@Nullable Surface surface,
|
||||
@Nullable MediaCrypto crypto,
|
||||
int flags) {
|
||||
throw new IllegalArgumentException("Format unsupported");
|
||||
}
|
||||
});
|
||||
addTransformerCodec(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig, /* isDecoder= */ true);
|
||||
addTransformerCodec(MimeTypes.AUDIO_AMR_NB, throwingCodecConfig, /* isDecoder= */ false);
|
||||
}
|
||||
|
||||
private void addDecodingCodecs() {
|
||||
// Video codecs
|
||||
MediaCodecInfo.CodecProfileLevel avcProfileLevel =
|
||||
createProfileLevel(
|
||||
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
|
||||
MediaCodecInfo.CodecProfileLevel.AVCLevel62);
|
||||
addExoplayerCodec(
|
||||
EXOTEST_VIDEO_AVC,
|
||||
configureCodec(
|
||||
/* codecName= */ "exotest.video.avc",
|
||||
MimeTypes.VIDEO_H264,
|
||||
generateDecodingCodecConfig(MimeTypes.VIDEO_H264),
|
||||
ImmutableList.of(avcProfileLevel),
|
||||
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
|
||||
|
||||
MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel =
|
||||
createProfileLevel(
|
||||
MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain,
|
||||
MediaCodecInfo.CodecProfileLevel.MPEG2LevelML);
|
||||
addExoplayerCodec(
|
||||
EXOTEST_VIDEO_MPEG2,
|
||||
configureCodec(
|
||||
/* codecName= */ "exotest.video.mpeg2",
|
||||
MimeTypes.VIDEO_MPEG2,
|
||||
generateDecodingCodecConfig(MimeTypes.VIDEO_MPEG2),
|
||||
ImmutableList.of(mpeg2ProfileLevel),
|
||||
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_VIDEO_VP9,
|
||||
configureCodec(
|
||||
/* codecName= */ "exotest.video.vp9",
|
||||
MimeTypes.VIDEO_VP9,
|
||||
generateDecodingCodecConfig(MimeTypes.VIDEO_VP9),
|
||||
ImmutableList.of(),
|
||||
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
|
||||
|
||||
// Audio codecs
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_AAC, MimeTypes.AUDIO_AAC, generateDecodingCodecConfig(MimeTypes.AUDIO_AAC));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_AC3, MimeTypes.AUDIO_AC3, generateDecodingCodecConfig(MimeTypes.AUDIO_AC3));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_AC4, MimeTypes.AUDIO_AC4, generateDecodingCodecConfig(MimeTypes.AUDIO_AC4));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_E_AC3,
|
||||
MimeTypes.AUDIO_E_AC3,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_E_AC3_JOC,
|
||||
MimeTypes.AUDIO_E_AC3_JOC,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_E_AC3_JOC));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_FLAC,
|
||||
MimeTypes.AUDIO_FLAC,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_FLAC));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_MPEG,
|
||||
MimeTypes.AUDIO_MPEG,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_MPEG_L2,
|
||||
MimeTypes.AUDIO_MPEG_L2,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_MPEG_L2));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_OPUS,
|
||||
MimeTypes.AUDIO_OPUS,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_OPUS));
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_VORBIS,
|
||||
MimeTypes.AUDIO_VORBIS,
|
||||
generateDecodingCodecConfig(MimeTypes.AUDIO_VORBIS));
|
||||
configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC);
|
||||
configureCodec("exotest.audio.ac3", MimeTypes.AUDIO_AC3);
|
||||
configureCodec("exotest.audio.ac4", MimeTypes.AUDIO_AC4);
|
||||
configureCodec("exotest.audio.eac3", MimeTypes.AUDIO_E_AC3);
|
||||
configureCodec("exotest.audio.eac3joc", MimeTypes.AUDIO_E_AC3_JOC);
|
||||
configureCodec("exotest.audio.flac", MimeTypes.AUDIO_FLAC);
|
||||
configureCodec("exotest.audio.mpeg", MimeTypes.AUDIO_MPEG);
|
||||
configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2);
|
||||
configureCodec("exotest.audio.opus", MimeTypes.AUDIO_OPUS);
|
||||
configureCodec("exotest.audio.vorbis", MimeTypes.AUDIO_VORBIS);
|
||||
|
||||
// Raw audio should use a bypass mode and never need this codec. However, to easily assert
|
||||
// failures of the bypass mode we want to detect when the raw audio is decoded by this class and
|
||||
// thus we need a codec to output samples.
|
||||
addExoplayerCodec(
|
||||
EXOTEST_AUDIO_RAW, MimeTypes.AUDIO_RAW, generateDecodingCodecConfig(MimeTypes.AUDIO_RAW));
|
||||
configureCodec("exotest.audio.raw", MimeTypes.AUDIO_RAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
if (!forTranscoding) {
|
||||
MediaCodecUtil.clearDecoderInfoCache();
|
||||
} else {
|
||||
EncoderUtil.clearCachedEncoders();
|
||||
}
|
||||
MediaCodecUtil.clearDecoderInfoCache();
|
||||
ShadowMediaCodecList.reset();
|
||||
ShadowMediaCodec.clearCodecs();
|
||||
}
|
||||
|
||||
private ShadowMediaCodec.CodecConfig generateDecodingCodecConfig(String mimeType) {
|
||||
// TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
|
||||
// to configure() so we don't have to specify large buffers here.
|
||||
CodecImpl codec = new CodecImpl(mimeType);
|
||||
return new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec);
|
||||
}
|
||||
|
||||
private void addTransformerCodec(
|
||||
String mimeType, ShadowMediaCodec.CodecConfig codecConfig, boolean isDecoder) {
|
||||
String codecName =
|
||||
Util.formatInvariant(
|
||||
isDecoder ? "transformertest.%s.decoder" : "transformertest.%s.encoder",
|
||||
mimeType.replace('/', '.'));
|
||||
addCodec(
|
||||
private void configureCodec(String codecName, String mimeType) {
|
||||
configureCodec(
|
||||
codecName,
|
||||
mimeType,
|
||||
codecConfig,
|
||||
/* profileLevels= */ ImmutableList.of(),
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
isDecoder);
|
||||
}
|
||||
|
||||
private void addExoplayerCodec(
|
||||
String codecName, String mimeType, ShadowMediaCodec.CodecConfig codecConfig) {
|
||||
addExoplayerCodec(
|
||||
codecName,
|
||||
mimeType,
|
||||
codecConfig,
|
||||
/* profileLevels= */ ImmutableList.of(),
|
||||
/* colorFormats= */ ImmutableList.of());
|
||||
}
|
||||
|
||||
private void addExoplayerCodec(
|
||||
private void configureCodec(
|
||||
String codecName,
|
||||
String mimeType,
|
||||
ShadowMediaCodec.CodecConfig codecConfig,
|
||||
List<MediaCodecInfo.CodecProfileLevel> profileLevels,
|
||||
List<Integer> colorFormats) {
|
||||
addCodec(codecName, mimeType, codecConfig, profileLevels, colorFormats, /* isDecoder= */ true);
|
||||
}
|
||||
|
||||
private void addCodec(
|
||||
String codecName,
|
||||
String mimeType,
|
||||
ShadowMediaCodec.CodecConfig codecConfig,
|
||||
List<MediaCodecInfo.CodecProfileLevel> profileLevels,
|
||||
List<Integer> colorFormats,
|
||||
boolean isDecoder) {
|
||||
MediaFormat mediaFormat = new MediaFormat();
|
||||
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities =
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setIsEncoder(!isDecoder);
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat);
|
||||
if (!profileLevels.isEmpty()) {
|
||||
capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0]));
|
||||
}
|
||||
if (!colorFormats.isEmpty()) {
|
||||
capabilities.setColorFormats(Ints.toArray(colorFormats));
|
||||
}
|
||||
|
||||
ShadowMediaCodecList.addCodec(
|
||||
MediaCodecInfoBuilder.newBuilder()
|
||||
.setName(codecName)
|
||||
.setIsEncoder(!isDecoder)
|
||||
.setCapabilities(capabilities.build())
|
||||
.build());
|
||||
|
||||
if (isDecoder) {
|
||||
ShadowMediaCodec.addDecoder(codecName, codecConfig);
|
||||
} else {
|
||||
ShadowMediaCodec.addEncoder(codecName, codecConfig);
|
||||
}
|
||||
// TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
|
||||
// to configure() so we don't have to specify large buffers here.
|
||||
CodecImpl codec = new CodecImpl(mimeType);
|
||||
ShadowMediaCodec.addDecoder(
|
||||
codecName,
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec));
|
||||
}
|
||||
|
||||
private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) {
|
||||
|
@ -430,7 +430,7 @@ public final class Transformer {
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@VisibleForTesting
|
||||
public Builder setClock(Clock clock) {
|
||||
/* package */ Builder setClock(Clock clock) {
|
||||
this.clock = clock;
|
||||
this.listeners = listeners.copy(looper, clock, (listener, flags) -> {});
|
||||
return this;
|
||||
|
@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.test.utils;
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.transformer.Muxer;
|
||||
import androidx.media3.test.utils.DumpableFormat;
|
||||
import androidx.media3.test.utils.Dumper;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -28,7 +28,6 @@ import java.util.List;
|
||||
* testing purposes) and delegates the actual muxing operations to another {@link Muxer} created
|
||||
* using the factory provided.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class TestMuxer implements Muxer, Dumper.Dumpable {
|
||||
|
||||
private final Muxer muxer;
|
@ -29,11 +29,15 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -49,13 +53,14 @@ import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.ExtractorsFactory;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.TestTransformerBuilderFactory;
|
||||
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
@ -69,17 +74,16 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.shadows.MediaCodecInfoBuilder;
|
||||
import org.robolectric.shadows.ShadowMediaCodec;
|
||||
import org.robolectric.shadows.ShadowMediaCodecList;
|
||||
|
||||
/** End-to-end test for {@link Transformer}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class TransformerEndToEndTest {
|
||||
|
||||
@Rule
|
||||
public final ShadowMediaCodecConfig mediaCodecConfig = ShadowMediaCodecConfig.forTranscoding();
|
||||
|
||||
private static final String ASSET_URI_PREFIX = "asset:///media/";
|
||||
private static final String FILE_VIDEO_ONLY = "mp4/sample_18byte_nclx_colr.mp4";
|
||||
private static final String FILE_AUDIO_VIDEO = "mp4/sample.mp4";
|
||||
@ -96,39 +100,39 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
private Context context;
|
||||
private String outputPath;
|
||||
private TestMuxer testMuxer;
|
||||
private FakeClock clock;
|
||||
private ProgressHolder progressHolder;
|
||||
private TestTransformerBuilderFactory testTransformerBuilderFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
outputPath = Util.createTempFile(context, "TransformerTest").getPath();
|
||||
clock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||
progressHolder = new ProgressHolder();
|
||||
testTransformerBuilderFactory = new TestTransformerBuilderFactory(context);
|
||||
createEncodersAndDecoders();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Files.delete(Paths.get(outputPath));
|
||||
removeEncodersAndDecoders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_videoOnlyPassthrough_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_VIDEO_ONLY));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioOnlyPassthrough_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
|
||||
|
||||
@ -136,16 +140,13 @@ public final class TransformerEndToEndTest {
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioOnlyTranscoding_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
|
||||
@ -157,29 +158,24 @@ public final class TransformerEndToEndTest {
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_ENCODER + ".aac"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioAndVideo_withClippingStartAtKeyFrame_completesSuccessfully()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S)
|
||||
@ -196,15 +192,14 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
testMuxer,
|
||||
getDumpFileName(FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S + ".clipped"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_withSubtitles_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build())
|
||||
.build();
|
||||
@ -213,17 +208,13 @@ public final class TransformerEndToEndTest {
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_WITH_SUBTITLES));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SUBTITLES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_successiveTransformations_completesSuccessfully()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
// Transform first media item.
|
||||
@ -235,14 +226,12 @@ public final class TransformerEndToEndTest {
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_concurrentTransformations_throwsError() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -254,44 +243,33 @@ public final class TransformerEndToEndTest {
|
||||
@Test
|
||||
public void startTransformation_removeAudio_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
.setRemoveAudio(true)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).setRemoveAudio(true).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".noaudio"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_removeVideo_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
.setRemoveVideo(true)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).setRemoveVideo(true).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".novideo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_silentAudio_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.experimentalSetForceSilentAudio(true)
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
@ -300,9 +278,7 @@ public final class TransformerEndToEndTest {
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silentaudio"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -310,8 +286,7 @@ public final class TransformerEndToEndTest {
|
||||
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setAudioProcessors(ImmutableList.of(sonicAudioProcessor))
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
@ -320,9 +295,7 @@ public final class TransformerEndToEndTest {
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -331,8 +304,7 @@ public final class TransformerEndToEndTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -353,8 +325,7 @@ public final class TransformerEndToEndTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -381,8 +352,7 @@ public final class TransformerEndToEndTest {
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ true)
|
||||
createTransformerBuilder(/* enableFallback= */ true)
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -407,8 +377,7 @@ public final class TransformerEndToEndTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer1 =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -427,8 +396,7 @@ public final class TransformerEndToEndTest {
|
||||
@Test
|
||||
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
|
||||
.build();
|
||||
@ -437,10 +405,7 @@ public final class TransformerEndToEndTest {
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -455,10 +420,7 @@ public final class TransformerEndToEndTest {
|
||||
}
|
||||
};
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
.addListener(listener)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).addListener(listener).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -474,8 +436,7 @@ public final class TransformerEndToEndTest {
|
||||
public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(
|
||||
@ -496,8 +457,7 @@ public final class TransformerEndToEndTest {
|
||||
public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
|
||||
@ -515,8 +475,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_withIoError_completesWithError() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -536,19 +495,14 @@ public final class TransformerEndToEndTest {
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ false)
|
||||
.addListener(mockListener)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
verify(mockListener)
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
}
|
||||
@ -562,19 +516,14 @@ public final class TransformerEndToEndTest {
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.create(/* enableFallback= */ true)
|
||||
.addListener(mockListener)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ true).addListener(mockListener).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context,
|
||||
testTransformerBuilderFactory.getTestMuxer(),
|
||||
getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
verify(mockListener)
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
}
|
||||
@ -584,11 +533,11 @@ public final class TransformerEndToEndTest {
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(
|
||||
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
|
||||
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.setMaxDelayBetweenSamplesMs(1)
|
||||
.create(/* enableFallback= */ false)
|
||||
createTransformerBuilder(/* enableFallback= */ false)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setMuxerFactory(muxerFactory)
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
@ -602,24 +551,20 @@ public final class TransformerEndToEndTest {
|
||||
@Test
|
||||
public void startTransformation_withUnsetMaxDelayBetweenSamples_completesSuccessfully()
|
||||
throws Exception {
|
||||
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET);
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory
|
||||
.setMaxDelayBetweenSamplesMs(C.TIME_UNSET)
|
||||
.create(/* enableFallback= */ false)
|
||||
.build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).setMuxerFactory(muxerFactory).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_afterCancellation_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -630,8 +575,7 @@ public final class TransformerEndToEndTest {
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -640,7 +584,7 @@ public final class TransformerEndToEndTest {
|
||||
anotherThread.start();
|
||||
Looper looper = anotherThread.getLooper();
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).setLooper(looper).build();
|
||||
createTransformerBuilder(/* enableFallback= */ false).setLooper(looper).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
AtomicReference<Exception> exception = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -660,14 +604,12 @@ public final class TransformerEndToEndTest {
|
||||
countDownLatch.await();
|
||||
|
||||
assertThat(exception.get()).isNull();
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testTransformerBuilderFactory.getTestMuxer(), getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
@ -692,8 +634,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
AtomicInteger previousProgressState =
|
||||
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||
@ -739,8 +680,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
List<Integer> progresses = new ArrayList<>();
|
||||
Handler progressHandler =
|
||||
@ -775,8 +715,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
|
||||
@ -790,8 +729,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION);
|
||||
AtomicInteger previousProgressState =
|
||||
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||
@ -834,8 +772,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -859,8 +796,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void cancel_afterCompletion_doesNotThrow() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -870,8 +806,7 @@ public final class TransformerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void cancel_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer =
|
||||
testTransformerBuilderFactory.create(/* enableFallback= */ false).build();
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -893,10 +828,147 @@ public final class TransformerEndToEndTest {
|
||||
assertThat(illegalStateException.get()).isNotNull();
|
||||
}
|
||||
|
||||
private Transformer.Builder createTransformerBuilder(boolean enableFallback) {
|
||||
return new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.setEncoderFactory(
|
||||
new DefaultEncoderFactory.Builder(context).setEnableFallback(enableFallback).build());
|
||||
}
|
||||
|
||||
private static void createEncodersAndDecoders() {
|
||||
ShadowMediaCodec.CodecConfig codecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 10_000,
|
||||
/* outputBufferSize= */ 10_000,
|
||||
/* codec= */ (in, out) -> out.put(in));
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AAC,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AC3,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_NB,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AAC,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ false);
|
||||
|
||||
ShadowMediaCodec.CodecConfig throwingCodecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
/* inputBufferSize= */ 10_000,
|
||||
/* outputBufferSize= */ 10_000,
|
||||
new ShadowMediaCodec.CodecConfig.Codec() {
|
||||
|
||||
@Override
|
||||
public void process(ByteBuffer in, ByteBuffer out) {
|
||||
out.put(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigured(
|
||||
MediaFormat format,
|
||||
@Nullable Surface surface,
|
||||
@Nullable MediaCrypto crypto,
|
||||
int flags) {
|
||||
throw new IllegalArgumentException("Format unsupported");
|
||||
}
|
||||
});
|
||||
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_WB,
|
||||
throwingCodecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_NB,
|
||||
throwingCodecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ false);
|
||||
}
|
||||
|
||||
private static void addCodec(
|
||||
String mimeType,
|
||||
ShadowMediaCodec.CodecConfig codecConfig,
|
||||
List<Integer> colorFormats,
|
||||
boolean isDecoder) {
|
||||
String codecName =
|
||||
Util.formatInvariant(
|
||||
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
|
||||
if (isDecoder) {
|
||||
ShadowMediaCodec.addDecoder(codecName, codecConfig);
|
||||
} else {
|
||||
ShadowMediaCodec.addEncoder(codecName, codecConfig);
|
||||
}
|
||||
|
||||
MediaFormat mediaFormat = new MediaFormat();
|
||||
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder codecCapabilities =
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setIsEncoder(!isDecoder);
|
||||
|
||||
if (!colorFormats.isEmpty()) {
|
||||
codecCapabilities.setColorFormats(Ints.toArray(colorFormats));
|
||||
}
|
||||
|
||||
ShadowMediaCodecList.addCodec(
|
||||
MediaCodecInfoBuilder.newBuilder()
|
||||
.setName(codecName)
|
||||
.setIsEncoder(!isDecoder)
|
||||
.setCapabilities(codecCapabilities.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
private static void removeEncodersAndDecoders() {
|
||||
ShadowMediaCodec.clearCodecs();
|
||||
ShadowMediaCodecList.reset();
|
||||
EncoderUtil.clearCachedEncoders();
|
||||
}
|
||||
|
||||
private static String getDumpFileName(String originalFileName) {
|
||||
return DUMP_FILE_OUTPUT_DIRECTORY + '/' + originalFileName + '.' + DUMP_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
private final class TestMuxerFactory implements Muxer.Factory {
|
||||
|
||||
private final Muxer.Factory defaultMuxerFactory;
|
||||
|
||||
public TestMuxerFactory() {
|
||||
defaultMuxerFactory = new DefaultMuxer.Factory();
|
||||
}
|
||||
|
||||
public TestMuxerFactory(long maxDelayBetweenSamplesMs) {
|
||||
defaultMuxerFactory = new DefaultMuxer.Factory(maxDelayBetweenSamplesMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Muxer create(String path) throws Muxer.MuxerException {
|
||||
testMuxer = new TestMuxer(path, defaultMuxerFactory);
|
||||
return testMuxer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException {
|
||||
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory);
|
||||
return testMuxer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
|
||||
return defaultMuxerFactory.getSupportedSampleMimeTypes(trackType);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SlowExtractorsFactory implements ExtractorsFactory {
|
||||
|
||||
private final long delayBetweenReadsMs;
|
||||
|
Loading…
x
Reference in New Issue
Block a user