Transcode to a muxer-supported sample MIME type.
If the output sample MIME type is inferred from the input but is not supported by the muxer, we fallback to transcoding to a supported sample MIME type. The audio and video renderers need to make sure not to select the PassthroughSamplePipeline for this case. Which sample MIME type to choose is decided by the EncoderFactory. PiperOrigin-RevId: 423272812
This commit is contained in:
parent
a1424c834f
commit
d277deb335
@ -0,0 +1,61 @@
|
||||
containerMimeType = video/mp4
|
||||
format 0:
|
||||
sampleMimeType = audio/mp4a-latm
|
||||
channelCount = 6
|
||||
sampleRate = 48000
|
||||
pcmEncoding = 2
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = 1896404418
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 0
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -2134951116
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 2667
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = 97556101
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 5334
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -1448980924
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 8000
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = 1871012467
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 10667
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -1317831364
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 13334
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -1728189539
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 16000
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -1715881661
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 18667
|
||||
sample:
|
||||
trackIndex = 0
|
||||
dataHashCode = -1428554542
|
||||
size = 1536
|
||||
isKeyFrame = true
|
||||
presentationTimeUs = 21334
|
||||
released = true
|
@ -31,6 +31,7 @@ import androidx.media3.exoplayer.audio.AudioProcessor;
|
||||
import androidx.media3.exoplayer.audio.AudioProcessor.AudioFormat;
|
||||
import androidx.media3.exoplayer.audio.SonicAudioProcessor;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
/**
|
||||
@ -63,8 +64,9 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
public AudioSamplePipeline(
|
||||
Format inputFormat,
|
||||
TransformationRequest transformationRequest,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
Codec.DecoderFactory decoderFactory,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
List<String> allowedOutputMimeTypes,
|
||||
FallbackListener fallbackListener)
|
||||
throws TransformationException {
|
||||
decoderInputBuffer =
|
||||
@ -110,7 +112,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
.setChannelCount(encoderInputAudioFormat.channelCount)
|
||||
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
||||
.build();
|
||||
encoder = encoderFactory.createForAudioEncoding(requestedOutputFormat);
|
||||
encoder = encoderFactory.createForAudioEncoding(requestedOutputFormat, allowedOutputMimeTypes);
|
||||
|
||||
fallbackListener.onTransformationRequestFinalized(
|
||||
createFallbackRequest(
|
||||
|
@ -31,6 +31,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@ -56,7 +57,7 @@ public final class Codec {
|
||||
* @param format The {@link Format} (of the input data) used to determine the underlying {@link
|
||||
* MediaCodec} and its configuration values.
|
||||
* @return A configured and started decoder wrapper.
|
||||
* @throws TransformationException If the underlying codec cannot be created.
|
||||
* @throws TransformationException If no suitable codec can be created.
|
||||
*/
|
||||
Codec createForAudioDecoding(Format format) throws TransformationException;
|
||||
|
||||
@ -67,7 +68,7 @@ public final class Codec {
|
||||
* MediaCodec} and its configuration values.
|
||||
* @param outputSurface The {@link Surface} to which the decoder output is rendered.
|
||||
* @return A configured and started decoder wrapper.
|
||||
* @throws TransformationException If the underlying codec cannot be created.
|
||||
* @throws TransformationException If no suitable codec can be created.
|
||||
*/
|
||||
Codec createForVideoDecoding(Format format, Surface outputSurface)
|
||||
throws TransformationException;
|
||||
@ -82,25 +83,39 @@ public final class Codec {
|
||||
/**
|
||||
* Returns a {@link Codec} for audio encoding.
|
||||
*
|
||||
* <p>This method must validate that the {@link Codec} is configured to produce one of the
|
||||
* {@code allowedMimeTypes}. The {@link Format#sampleMimeType sample MIME type} given in {@code
|
||||
* format} is not necessarily allowed.
|
||||
*
|
||||
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
||||
* MediaCodec} and its configuration values.
|
||||
* @param allowedMimeTypes The non-empty list of allowed output sample {@link MimeTypes MIME
|
||||
* types}.
|
||||
* @return A configured and started encoder wrapper.
|
||||
* @throws TransformationException If the underlying codec cannot be created.
|
||||
* @throws TransformationException If no suitable codec can be created.
|
||||
*/
|
||||
Codec createForAudioEncoding(Format format) throws TransformationException;
|
||||
Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException;
|
||||
|
||||
/**
|
||||
* Returns a {@link Codec} for video encoding.
|
||||
*
|
||||
* <p>This method must validate that the {@link Codec} is configured to produce one of the
|
||||
* {@code allowedMimeTypes}. The {@link Format#sampleMimeType sample MIME type} given in {@code
|
||||
* format} is not necessarily allowed.
|
||||
*
|
||||
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
||||
* MediaCodec} and its configuration values. {@link Format#sampleMimeType}, {@link
|
||||
* Format#width} and {@link Format#height} must be set to those of the desired output video
|
||||
* format. {@link Format#rotationDegrees} should be 0. The video should always be in
|
||||
* landscape orientation.
|
||||
* @param allowedMimeTypes The non-empty list of allowed output sample {@link MimeTypes MIME
|
||||
* types}.
|
||||
* @return A configured and started encoder wrapper.
|
||||
* @throws TransformationException If the underlying codec cannot be created.
|
||||
* @throws TransformationException If no suitable codec can be created.
|
||||
*/
|
||||
Codec createForVideoEncoding(Format format) throws TransformationException;
|
||||
Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException;
|
||||
}
|
||||
|
||||
// MediaCodec decoders always output 16 bit PCM, unless configured to output PCM float.
|
||||
|
@ -34,13 +34,15 @@ import androidx.media3.common.util.MediaFormatUtil;
|
||||
import androidx.media3.common.util.TraceUtil;
|
||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/** A default {@link Codec.DecoderFactory} and {@link Codec.EncoderFactory}. */
|
||||
/* package */ final class DefaultCodecFactory
|
||||
implements Codec.DecoderFactory, Codec.EncoderFactory {
|
||||
// TODO(b/214973843): Add option to disable fallback.
|
||||
|
||||
// TODO(b/210591626) Fall back adaptively to H265 if possible.
|
||||
// TODO(b/210591626): Fall back adaptively to H265 if possible.
|
||||
private static final String DEFAULT_FALLBACK_MIME_TYPE = MimeTypes.VIDEO_H264;
|
||||
private static final int DEFAULT_COLOR_FORMAT = CodecCapabilities.COLOR_FormatSurface;
|
||||
private static final int DEFAULT_FRAME_RATE = 60;
|
||||
@ -85,7 +87,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format) throws TransformationException {
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
checkArgument(!allowedMimeTypes.isEmpty());
|
||||
if (!allowedMimeTypes.contains(format.sampleMimeType)) {
|
||||
// TODO(b/210591626): Pick fallback MIME type using same strategy as for encoder
|
||||
// capabilities limitations.
|
||||
format = format.buildUpon().setSampleMimeType(allowedMimeTypes.get(0)).build();
|
||||
}
|
||||
MediaFormat mediaFormat =
|
||||
MediaFormat.createAudioFormat(
|
||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||
@ -100,7 +109,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForVideoEncoding(Format format) throws TransformationException {
|
||||
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
checkArgument(format.width != Format.NO_VALUE);
|
||||
checkArgument(format.height != Format.NO_VALUE);
|
||||
// According to interface Javadoc, format.rotationDegrees should be 0. The video should always
|
||||
@ -108,7 +118,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
checkArgument(format.height <= format.width);
|
||||
checkArgument(format.rotationDegrees == 0);
|
||||
checkNotNull(format.sampleMimeType);
|
||||
format = getVideoEncoderSupportedFormat(format);
|
||||
|
||||
checkArgument(!allowedMimeTypes.isEmpty());
|
||||
|
||||
format = getVideoEncoderSupportedFormat(format, allowedMimeTypes);
|
||||
|
||||
MediaFormat mediaFormat =
|
||||
MediaFormat.createVideoFormat(
|
||||
@ -191,14 +204,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
@RequiresNonNull("#1.sampleMimeType")
|
||||
private static Format getVideoEncoderSupportedFormat(Format requestedFormat)
|
||||
throws TransformationException {
|
||||
private static Format getVideoEncoderSupportedFormat(
|
||||
Format requestedFormat, List<String> allowedMimeTypes) throws TransformationException {
|
||||
String mimeType = requestedFormat.sampleMimeType;
|
||||
Format.Builder formatBuilder = requestedFormat.buildUpon();
|
||||
|
||||
// TODO(b/210591626) Implement encoder filtering.
|
||||
if (EncoderUtil.getSupportedEncoders(mimeType).isEmpty()) {
|
||||
mimeType = DEFAULT_FALLBACK_MIME_TYPE;
|
||||
if (!allowedMimeTypes.contains(mimeType)
|
||||
|| EncoderUtil.getSupportedEncoders(mimeType).isEmpty()) {
|
||||
mimeType =
|
||||
allowedMimeTypes.contains(DEFAULT_FALLBACK_MIME_TYPE)
|
||||
? DEFAULT_FALLBACK_MIME_TYPE
|
||||
: allowedMimeTypes.get(0);
|
||||
if (EncoderUtil.getSupportedEncoders(mimeType).isEmpty()) {
|
||||
throw createTransformationException(
|
||||
new IllegalArgumentException(
|
||||
|
@ -68,23 +68,18 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||
return false;
|
||||
}
|
||||
Format inputFormat = checkNotNull(formatHolder.format);
|
||||
String sampleMimeType = checkNotNull(inputFormat.sampleMimeType);
|
||||
if (transformationRequest.audioMimeType == null
|
||||
&& !muxerWrapper.supportsSampleMimeType(sampleMimeType)) {
|
||||
throw TransformationException.createForMuxer(
|
||||
new IllegalArgumentException(
|
||||
"The output sample MIME inferred from the input format is not supported by the muxer."
|
||||
+ " Sample MIME type: "
|
||||
+ sampleMimeType),
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
if (shouldPassthrough(inputFormat)) {
|
||||
samplePipeline =
|
||||
new PassthroughSamplePipeline(inputFormat, transformationRequest, fallbackListener);
|
||||
} else {
|
||||
samplePipeline =
|
||||
new AudioSamplePipeline(
|
||||
inputFormat, transformationRequest, encoderFactory, decoderFactory, fallbackListener);
|
||||
inputFormat,
|
||||
transformationRequest,
|
||||
decoderFactory,
|
||||
encoderFactory,
|
||||
muxerWrapper.getSupportedSampleMimeTypes(getTrackType()),
|
||||
fallbackListener);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -94,6 +89,10 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||
&& !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) {
|
||||
return false;
|
||||
}
|
||||
if (transformationRequest.audioMimeType == null
|
||||
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
|
||||
return false;
|
||||
}
|
||||
if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -77,16 +77,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
return false;
|
||||
}
|
||||
Format inputFormat = checkNotNull(formatHolder.format);
|
||||
String sampleMimeType = checkNotNull(inputFormat.sampleMimeType);
|
||||
if (transformationRequest.videoMimeType == null
|
||||
&& !muxerWrapper.supportsSampleMimeType(sampleMimeType)) {
|
||||
throw TransformationException.createForMuxer(
|
||||
new IllegalArgumentException(
|
||||
"The output sample MIME inferred from the input format is not supported by the muxer."
|
||||
+ " Sample MIME type: "
|
||||
+ sampleMimeType),
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
if (shouldPassthrough(inputFormat)) {
|
||||
samplePipeline =
|
||||
new PassthroughSamplePipeline(inputFormat, transformationRequest, fallbackListener);
|
||||
@ -96,8 +86,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
context,
|
||||
inputFormat,
|
||||
transformationRequest,
|
||||
encoderFactory,
|
||||
decoderFactory,
|
||||
encoderFactory,
|
||||
muxerWrapper.getSupportedSampleMimeTypes(getTrackType()),
|
||||
fallbackListener,
|
||||
debugViewProvider);
|
||||
}
|
||||
@ -112,6 +103,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
&& !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) {
|
||||
return false;
|
||||
}
|
||||
if (transformationRequest.videoMimeType == null
|
||||
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
|
||||
return false;
|
||||
}
|
||||
if (transformationRequest.outputHeight != C.LENGTH_UNSET
|
||||
&& transformationRequest.outputHeight != inputFormat.height) {
|
||||
return false;
|
||||
|
@ -29,6 +29,7 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
@ -54,8 +55,9 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
Context context,
|
||||
Format inputFormat,
|
||||
TransformationRequest transformationRequest,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
Codec.DecoderFactory decoderFactory,
|
||||
Codec.EncoderFactory encoderFactory,
|
||||
List<String> allowedOutputMimeTypes,
|
||||
FallbackListener fallbackListener,
|
||||
Transformer.DebugViewProvider debugViewProvider)
|
||||
throws TransformationException {
|
||||
@ -115,7 +117,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
? transformationRequest.videoMimeType
|
||||
: inputFormat.sampleMimeType)
|
||||
.build();
|
||||
encoder = encoderFactory.createForVideoEncoding(requestedOutputFormat);
|
||||
encoder = encoderFactory.createForVideoEncoding(requestedOutputFormat, allowedOutputMimeTypes);
|
||||
fallbackListener.onTransformationRequestFinalized(
|
||||
createFallbackRequest(
|
||||
transformationRequest, requestedOutputFormat, encoder.getConfigurationFormat()));
|
||||
|
@ -23,6 +23,7 @@ import static androidx.media3.transformer.Transformer.PROGRESS_STATE_WAITING_FOR
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ -75,7 +76,6 @@ public final class TransformerTest {
|
||||
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_VIDEO_UNSUPPORTED = "vp9/bear-vp9.webm";
|
||||
private static final String FILE_UNKNOWN_DURATION = "mp4/sample_fragmented.mp4";
|
||||
public static final String DUMP_FILE_OUTPUT_DIRECTORY = "transformerdumps";
|
||||
public static final String DUMP_FILE_EXTENSION = "dump";
|
||||
@ -103,11 +103,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_videoOnlyPassthrough_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -118,11 +114,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioOnlyPassthrough_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_ENCODER);
|
||||
|
||||
@ -136,9 +128,7 @@ public final class TransformerTest {
|
||||
@Test
|
||||
public void startTransformation_audioOnlyTranscoding_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
|
||||
@ -155,11 +145,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_audioAndVideo_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -171,9 +157,7 @@ public final class TransformerTest {
|
||||
@Test
|
||||
public void startTransformation_withSubtitles_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build())
|
||||
.build();
|
||||
@ -188,11 +172,7 @@ public final class TransformerTest {
|
||||
@Test
|
||||
public void startTransformation_successiveTransformations_completesSuccessfully()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
// Transform first media item.
|
||||
@ -209,7 +189,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_concurrentTransformations_throwsError() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -220,12 +200,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_removeAudio_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setRemoveAudio(true)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().setRemoveAudio(true).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -237,12 +212,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_removeVideo_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setRemoveVideo(true)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().setRemoveVideo(true).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -258,9 +228,7 @@ public final class TransformerTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -281,14 +249,14 @@ public final class TransformerTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
.setTransformationRequest( // Request transcoding so that decoder is used.
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build())
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_DECODER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||
@ -298,6 +266,34 @@ public final class TransformerTest {
|
||||
verify(mockListener3, times(1)).onTransformationError(mediaItem, exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_withMultipleListeners_callsEachOnFallback() throws Exception {
|
||||
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
TransformationRequest originalTransformationRequest =
|
||||
new TransformationRequest.Builder().build();
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer =
|
||||
createTransformerBuilder()
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
verify(mockListener1, times(1))
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
verify(mockListener2, times(1))
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
verify(mockListener3, times(1))
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_afterBuildUponWithListenerRemoved_onlyCallsRemainingListeners()
|
||||
throws Exception {
|
||||
@ -305,9 +301,7 @@ public final class TransformerTest {
|
||||
Transformer.Listener mockListener2 = mock(Transformer.Listener.class);
|
||||
Transformer.Listener mockListener3 = mock(Transformer.Listener.class);
|
||||
Transformer transformer1 =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.addListener(mockListener1)
|
||||
.addListener(mockListener2)
|
||||
.addListener(mockListener3)
|
||||
@ -319,16 +313,14 @@ public final class TransformerTest {
|
||||
TransformerTestRunner.runUntilCompleted(transformer2);
|
||||
|
||||
verify(mockListener1, times(1)).onTransformationCompleted(mediaItem);
|
||||
verify(mockListener2, times(0)).onTransformationCompleted(mediaItem);
|
||||
verify(mockListener2, never()).onTransformationCompleted(mediaItem);
|
||||
verify(mockListener3, times(1)).onTransformationCompleted(mediaItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
|
||||
.build();
|
||||
@ -344,9 +336,7 @@ public final class TransformerTest {
|
||||
public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(
|
||||
@ -367,9 +357,7 @@ public final class TransformerTest {
|
||||
public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported by encoder and muxer
|
||||
@ -389,9 +377,7 @@ public final class TransformerTest {
|
||||
public void startTransformation_withVideoEncoderFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
createTransformerBuilder()
|
||||
.setTransformationRequest(
|
||||
new TransformationRequest.Builder()
|
||||
.setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type
|
||||
@ -409,7 +395,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_withIoError_completesWithError() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -420,50 +406,32 @@ public final class TransformerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_withAudioMuxerFormatUnsupported_completesWithError()
|
||||
public void startTransformation_withAudioMuxerFormatFallback_completesSuccessfully()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
||||
TransformationRequest originalTransformationRequest =
|
||||
new TransformationRequest.Builder().build();
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer = createTransformerBuilder().addListener(mockListener).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(exception).hasCauseThat().hasMessageThat().contains("audio");
|
||||
assertThat(exception.errorCode)
|
||||
.isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
verify(mockListener, times(1))
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_withVideoMuxerFormatUnsupported_completesWithError()
|
||||
throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_UNSUPPORTED);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||
|
||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(exception).hasCauseThat().hasMessageThat().contains("video");
|
||||
assertThat(exception.errorCode)
|
||||
.isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
// TODO(b/214012830): Add a test to check that the correct exception is thrown when the muxer
|
||||
// doesn't support the output sample MIME type inferred from the input once it is possible to
|
||||
// disable fallback.
|
||||
|
||||
@Test
|
||||
public void startTransformation_afterCancellation_completesSuccessfully() throws Exception {
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -482,12 +450,7 @@ public final class TransformerTest {
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
anotherThread.start();
|
||||
Looper looper = anotherThread.getLooper();
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setLooper(looper)
|
||||
.setClock(clock)
|
||||
.setMuxerFactory(new TestMuxerFactory())
|
||||
.build();
|
||||
Transformer transformer = createTransformerBuilder().setLooper(looper).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
AtomicReference<Exception> exception = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -512,7 +475,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void startTransformation_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
@ -539,7 +502,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
AtomicInteger previousProgressState =
|
||||
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||
@ -585,7 +548,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_knownDuration_givesIncreasingPercentages() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
List<Integer> progresses = new ArrayList<>();
|
||||
Handler progressHandler =
|
||||
@ -620,7 +583,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_noCurrentTransformation_returnsNoTransformation() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
|
||||
@ -634,7 +597,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_UNKNOWN_DURATION);
|
||||
AtomicInteger previousProgressState =
|
||||
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||
@ -677,7 +640,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void getProgress_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -701,7 +664,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void cancel_afterCompletion_doesNotThrow() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
@ -711,7 +674,7 @@ public final class TransformerTest {
|
||||
|
||||
@Test
|
||||
public void cancel_fromWrongThread_throwsError() throws Exception {
|
||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||
Transformer transformer = createTransformerBuilder().build();
|
||||
HandlerThread anotherThread = new HandlerThread("AnotherThread");
|
||||
AtomicReference<IllegalStateException> illegalStateException = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
@ -733,6 +696,10 @@ public final class TransformerTest {
|
||||
assertThat(illegalStateException.get()).isNotNull();
|
||||
}
|
||||
|
||||
private Transformer.Builder createTransformerBuilder() {
|
||||
return new Transformer.Builder(context).setClock(clock).setMuxerFactory(new TestMuxerFactory());
|
||||
}
|
||||
|
||||
private static void createEncodersAndDecoders() {
|
||||
ShadowMediaCodec.CodecConfig codecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
|
Loading…
x
Reference in New Issue
Block a user