Commit 4240da59 by hschlueter Committed by tonihei

Add TransformationRequest.

PiperOrigin-RevId: 417786661
parent 5c8b5e5b
Showing with 480 additions and 245 deletions
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
* Increase required min API version to 21. * Increase required min API version to 21.
* `TransformationException` is now used to describe errors that occur * `TransformationException` is now used to describe errors that occur
during a transformation. during a transformation.
* Add `TransformationRequest` for specifying the transformation options.
* MediaSession extension: * MediaSession extension:
* Remove deprecated call to `onStop(/* reset= */ true)` and provide an * Remove deprecated call to `onStop(/* reset= */ true)` and provide an
opt-out flag for apps that don't want to clear the playlist on stop. opt-out flag for apps that don't want to clear the playlist on stop.
......
...@@ -22,6 +22,7 @@ import android.content.Context; ...@@ -22,6 +22,7 @@ import android.content.Context;
import android.graphics.Matrix; import android.graphics.Matrix;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.transformer.TransformationRequest;
import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.Transformer;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.HashSet; import java.util.HashSet;
...@@ -43,9 +44,12 @@ public final class RepeatedTranscodeTransformationTest { ...@@ -43,9 +44,12 @@ public final class RepeatedTranscodeTransformationTest {
transformationMatrix.postTranslate((float) 0.1, (float) 0.1); transformationMatrix.postTranslate((float) 0.1, (float) 0.1);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setTransformationRequest(
new TransformationRequest.Builder()
.setVideoMimeType(MimeTypes.VIDEO_H265) .setVideoMimeType(MimeTypes.VIDEO_H265)
.setTransformationMatrix(transformationMatrix) .setTransformationMatrix(transformationMatrix)
.setAudioMimeType(MimeTypes.AUDIO_AMR_NB) .setAudioMimeType(MimeTypes.AUDIO_AMR_NB)
.build())
.build(); .build();
Set<Long> differentOutputSizesBytes = new HashSet<>(); Set<Long> differentOutputSizesBytes = new HashSet<>();
...@@ -74,9 +78,12 @@ public final class RepeatedTranscodeTransformationTest { ...@@ -74,9 +78,12 @@ public final class RepeatedTranscodeTransformationTest {
transformationMatrix.postTranslate((float) 0.1, (float) 0.1); transformationMatrix.postTranslate((float) 0.1, (float) 0.1);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setRemoveAudio(true)
.setTransformationRequest(
new TransformationRequest.Builder()
.setVideoMimeType(MimeTypes.VIDEO_H265) .setVideoMimeType(MimeTypes.VIDEO_H265)
.setTransformationMatrix(transformationMatrix) .setTransformationMatrix(transformationMatrix)
.setRemoveAudio(true) .build())
.build(); .build();
Set<Long> differentOutputSizesBytes = new HashSet<>(); Set<Long> differentOutputSizesBytes = new HashSet<>();
...@@ -103,8 +110,11 @@ public final class RepeatedTranscodeTransformationTest { ...@@ -103,8 +110,11 @@ public final class RepeatedTranscodeTransformationTest {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transcodingTransformer = Transformer transcodingTransformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setAudioMimeType(MimeTypes.AUDIO_AMR_NB)
.setRemoveVideo(true) .setRemoveVideo(true)
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AMR_NB)
.build())
.build(); .build();
Set<Long> differentOutputSizesBytes = new HashSet<>(); Set<Long> differentOutputSizesBytes = new HashSet<>();
......
...@@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.transformer.mh.AndroidTestUtil.runTr ...@@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.transformer.mh.AndroidTestUtil.runTr
import android.content.Context; import android.content.Context;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.transformer.TransformationRequest;
import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.Transformer;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -32,7 +33,10 @@ public class SefTransformationTest { ...@@ -32,7 +33,10 @@ public class SefTransformationTest {
public void sefTransform() throws Exception { public void sefTransform() throws Exception {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = Transformer transformer =
new Transformer.Builder(context).setFlattenForSlowMotion(true).build(); new Transformer.Builder(context)
.setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.build();
runTransformer( runTransformer(
context, context,
/* testId = */ "sefTransform", /* testId = */ "sefTransform",
......
...@@ -22,6 +22,7 @@ import android.content.Context; ...@@ -22,6 +22,7 @@ import android.content.Context;
import android.graphics.Matrix; import android.graphics.Matrix;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.transformer.TransformationRequest;
import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.Transformer;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -35,7 +36,12 @@ public class SetTransformationMatrixTransformationTest { ...@@ -35,7 +36,12 @@ public class SetTransformationMatrixTransformationTest {
Matrix transformationMatrix = new Matrix(); Matrix transformationMatrix = new Matrix();
transformationMatrix.postTranslate(/* dx= */ .2f, /* dy= */ .1f); transformationMatrix.postTranslate(/* dx= */ .2f, /* dy= */ .1f);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context).setTransformationMatrix(transformationMatrix).build(); new Transformer.Builder(context)
.setTransformationRequest(
new TransformationRequest.Builder()
.setTransformationMatrix(transformationMatrix)
.build())
.build();
runTransformer( runTransformer(
context, context,
......
...@@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024; private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
private final Format inputFormat; private final Format inputFormat;
private final Transformation transformation; private final TransformationRequest transformationRequest;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Codec decoder; private final Codec decoder;
...@@ -66,12 +66,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -66,12 +66,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public AudioSamplePipeline( public AudioSamplePipeline(
Format inputFormat, Format inputFormat,
Transformation transformation, TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory) Codec.DecoderFactory decoderFactory)
throws TransformationException { throws TransformationException {
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.transformation = transformation; this.transformationRequest = transformationRequest;
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
decoderInputBuffer = decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
...@@ -294,7 +294,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -294,7 +294,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
decoderOutputFormat.sampleRate, decoderOutputFormat.sampleRate,
decoderOutputFormat.channelCount, decoderOutputFormat.channelCount,
decoderOutputFormat.pcmEncoding); decoderOutputFormat.pcmEncoding);
if (transformation.flattenForSlowMotion) { if (transformationRequest.flattenForSlowMotion) {
try { try {
outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat); outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat);
flushSonicAndSetSpeed(currentSpeed); flushSonicAndSetSpeed(currentSpeed);
...@@ -310,9 +310,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -310,9 +310,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
encoderFactory.createForAudioEncoding( encoderFactory.createForAudioEncoding(
new Format.Builder() new Format.Builder()
.setSampleMimeType( .setSampleMimeType(
transformation.audioMimeType == null transformationRequest.audioMimeType == null
? inputFormat.sampleMimeType ? inputFormat.sampleMimeType
: transformation.audioMimeType) : transformationRequest.audioMimeType)
.setSampleRate(outputAudioFormat.sampleRate) .setSampleRate(outputAudioFormat.sampleRate)
.setChannelCount(outputAudioFormat.channelCount) .setChannelCount(outputAudioFormat.channelCount)
.setAverageBitrate(DEFAULT_ENCODER_BITRATE) .setAverageBitrate(DEFAULT_ENCODER_BITRATE)
...@@ -322,7 +322,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -322,7 +322,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
private boolean isSpeedChanging(BufferInfo bufferInfo) { private boolean isSpeedChanging(BufferInfo bufferInfo) {
if (!transformation.flattenForSlowMotion) { if (!transformationRequest.flattenForSlowMotion) {
return false; return false;
} }
float newSpeed = speedProvider.getSpeed(bufferInfo.presentationTimeUs); float newSpeed = speedProvider.getSpeed(bufferInfo.presentationTimeUs);
......
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.transformer;
import android.graphics.Matrix;
import androidx.annotation.Nullable;
/** A media transformation configuration. */
/* package */ final class Transformation {
public final boolean removeAudio;
public final boolean removeVideo;
public final boolean flattenForSlowMotion;
public final int outputHeight;
public final Matrix transformationMatrix;
public final String containerMimeType;
@Nullable public final String audioMimeType;
@Nullable public final String videoMimeType;
public Transformation(
boolean removeAudio,
boolean removeVideo,
boolean flattenForSlowMotion,
int outputHeight,
Matrix transformationMatrix,
String containerMimeType,
@Nullable String audioMimeType,
@Nullable String videoMimeType) {
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
this.flattenForSlowMotion = flattenForSlowMotion;
this.outputHeight = outputHeight;
this.transformationMatrix = transformationMatrix;
this.containerMimeType = containerMimeType;
this.audioMimeType = audioMimeType;
this.videoMimeType = videoMimeType;
}
}
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.transformer;
import android.graphics.Matrix;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
/** A media transformation request. */
public final class TransformationRequest {
/** A builder for {@link TransformationRequest} instances. */
public static final class Builder {
private Matrix transformationMatrix;
private boolean flattenForSlowMotion;
private int outputHeight;
@Nullable private String audioMimeType;
@Nullable private String videoMimeType;
/**
* Creates a new instance with default values.
*
* <p>Use {@link TransformationRequest#buildUpon()} to obtain a builder representing an existing
* {@link TransformationRequest}.
*/
public Builder() {
transformationMatrix = new Matrix();
outputHeight = C.LENGTH_UNSET;
}
private Builder(TransformationRequest transformationRequest) {
this.transformationMatrix = transformationRequest.transformationMatrix;
this.flattenForSlowMotion = transformationRequest.flattenForSlowMotion;
this.outputHeight = transformationRequest.outputHeight;
this.audioMimeType = transformationRequest.audioMimeType;
this.videoMimeType = transformationRequest.videoMimeType;
}
/**
* Sets the transformation matrix. The default value is to apply no change.
*
* <p>This can be used to perform operations supported by {@link Matrix}, like scaling and
* rotating the video.
*
* <p>For now, resolution will not be affected by this method.
*
* @param transformationMatrix The transformation to apply to video frames.
* @return This builder.
*/
public Builder setTransformationMatrix(Matrix transformationMatrix) {
// TODO(b/201293185): After {@link #setResolution} supports arbitrary resolutions,
// allow transformations to change the resolution, by scaling to the appropriate min/max
// values. This will also be required to create the VertexTransformation class, in order to
// have aspect ratio helper methods (which require resolution to change).
this.transformationMatrix = transformationMatrix;
return this;
}
/**
* Sets whether the input should be flattened for media containing slow motion markers. The
* transformed output is obtained by removing the slow motion metadata and by actually slowing
* down the parts of the video and audio streams defined in this metadata. The default value for
* {@code flattenForSlowMotion} is {@code false}.
*
* <p>Only Samsung Extension Format (SEF) slow motion metadata type is supported. The
* transformation has no effect if the input does not contain this metadata type.
*
* <p>For SEF slow motion media, the following assumptions are made on the input:
*
* <ul>
* <li>The input container format is (unfragmented) MP4.
* <li>The input contains an AVC video elementary stream with temporal SVC.
* <li>The recording frame rate of the video is 120 or 240 fps.
* </ul>
*
* <p>If specifying a {@link MediaSourceFactory} using {@link
* Transformer.Builder#setMediaSourceFactory(MediaSourceFactory)}, make sure that {@link
* Mp4Extractor#FLAG_READ_SEF_DATA} is set on the {@link Mp4Extractor} used. Otherwise, the slow
* motion metadata will be ignored and the input won't be flattened.
*
* @param flattenForSlowMotion Whether to flatten for slow motion.
* @return This builder.
*/
public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
this.flattenForSlowMotion = flattenForSlowMotion;
return this;
}
/**
* Sets the output resolution using the output height. The default value is the same height as
* the input. Output width will scale to preserve the input video's aspect ratio.
*
* <p>For now, only "popular" heights like 144, 240, 360, 480, 720, 1080, 1440, or 2160 are
* supported, to ensure compatibility on different devices.
*
* <p>For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480).
*
* @param outputHeight The output height in pixels.
* @return This builder.
*/
public Builder setResolution(int outputHeight) {
// TODO(b/201293185): Restructure to input a Presentation class.
// TODO(b/201293185): Check encoder codec capabilities in order to allow arbitrary
// resolutions and reasonable fallbacks.
if (outputHeight != 144
&& outputHeight != 240
&& outputHeight != 360
&& outputHeight != 480
&& outputHeight != 720
&& outputHeight != 1080
&& outputHeight != 1440
&& outputHeight != 2160) {
throw new IllegalArgumentException(
"Please use a height of 144, 240, 360, 480, 720, 1080, 1440, or 2160.");
}
this.outputHeight = outputHeight;
return this;
}
/**
* Sets the video MIME type of the output. The default value is to use the same MIME type as the
* input. Supported values are:
*
* <ul>
* <li>{@link MimeTypes#VIDEO_H263}
* <li>{@link MimeTypes#VIDEO_H264}
* <li>{@link MimeTypes#VIDEO_H265} from API level 24
* <li>{@link MimeTypes#VIDEO_MP4V}
* </ul>
*
* @param videoMimeType The MIME type of the video samples in the output.
* @return This builder.
*/
public Builder setVideoMimeType(String videoMimeType) {
// TODO(b/209469847): Validate videoMimeType here once deprecated
// Transformer.Builder#setOuputMimeType(String) has been removed.
this.videoMimeType = videoMimeType;
return this;
}
/**
* Sets the audio MIME type of the output. The default value is to use the same MIME type as the
* input. Supported values are:
*
* <ul>
* <li>{@link MimeTypes#AUDIO_AAC}
* <li>{@link MimeTypes#AUDIO_AMR_NB}
* <li>{@link MimeTypes#AUDIO_AMR_WB}
* </ul>
*
* @param audioMimeType The MIME type of the audio samples in the output.
* @return This builder.
*/
public Builder setAudioMimeType(String audioMimeType) {
// TODO(b/209469847): Validate audioMimeType here once deprecated
// Transformer.Builder#setOuputMimeType(String) has been removed.
this.audioMimeType = audioMimeType;
return this;
}
/** Builds a {@link TransformationRequest} instance. */
public TransformationRequest build() {
return new TransformationRequest(
transformationMatrix, flattenForSlowMotion, outputHeight, audioMimeType, videoMimeType);
}
}
public final Matrix transformationMatrix;
public final boolean flattenForSlowMotion;
public final int outputHeight;
@Nullable public final String audioMimeType;
@Nullable public final String videoMimeType;
private TransformationRequest(
Matrix transformationMatrix,
boolean flattenForSlowMotion,
int outputHeight,
@Nullable String audioMimeType,
@Nullable String videoMimeType) {
this.transformationMatrix = transformationMatrix;
this.flattenForSlowMotion = flattenForSlowMotion;
this.outputHeight = outputHeight;
this.audioMimeType = audioMimeType;
this.videoMimeType = videoMimeType;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TransformationRequest)) {
return false;
}
TransformationRequest that = (TransformationRequest) o;
return transformationMatrix.equals(that.transformationMatrix)
&& flattenForSlowMotion == that.flattenForSlowMotion
&& outputHeight == that.outputHeight
&& Util.areEqual(audioMimeType, that.audioMimeType)
&& Util.areEqual(videoMimeType, that.videoMimeType);
}
@Override
public int hashCode() {
int result = transformationMatrix.hashCode();
result = 31 * result + (flattenForSlowMotion ? 1 : 0);
result = 31 * result + outputHeight;
result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0);
result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0);
return result;
}
/**
* Returns a new {@link TransformationRequest.Builder} initialized with the values of this
* instance.
*/
public Builder buildUpon() {
return new Builder(this);
}
}
...@@ -97,12 +97,9 @@ public final class Transformer { ...@@ -97,12 +97,9 @@ public final class Transformer {
private Muxer.Factory muxerFactory; private Muxer.Factory muxerFactory;
private boolean removeAudio; private boolean removeAudio;
private boolean removeVideo; private boolean removeVideo;
private boolean flattenForSlowMotion;
private int outputHeight;
private Matrix transformationMatrix;
private String containerMimeType; private String containerMimeType;
@Nullable private String audioMimeType; // TODO(b/204869912): Make final once deprecated setters are removed.
@Nullable private String videoMimeType; private TransformationRequest transformationRequest;
private Transformer.Listener listener; private Transformer.Listener listener;
private DebugViewProvider debugViewProvider; private DebugViewProvider debugViewProvider;
private Looper looper; private Looper looper;
...@@ -113,14 +110,13 @@ public final class Transformer { ...@@ -113,14 +110,13 @@ public final class Transformer {
@Deprecated @Deprecated
public Builder() { public Builder() {
muxerFactory = new FrameworkMuxer.Factory(); muxerFactory = new FrameworkMuxer.Factory();
outputHeight = C.LENGTH_UNSET;
transformationMatrix = new Matrix();
containerMimeType = MimeTypes.VIDEO_MP4;
listener = new Listener() {}; listener = new Listener() {};
looper = Util.getCurrentOrMainLooper(); looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE; debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4;
transformationRequest = new TransformationRequest.Builder().build();
} }
/** /**
...@@ -131,14 +127,13 @@ public final class Transformer { ...@@ -131,14 +127,13 @@ public final class Transformer {
public Builder(Context context) { public Builder(Context context) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
muxerFactory = new FrameworkMuxer.Factory(); muxerFactory = new FrameworkMuxer.Factory();
outputHeight = C.LENGTH_UNSET;
transformationMatrix = new Matrix();
containerMimeType = MimeTypes.VIDEO_MP4;
listener = new Listener() {}; listener = new Listener() {};
looper = Util.getCurrentOrMainLooper(); looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE; debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4;
this.transformationRequest = new TransformationRequest.Builder().build();
} }
/** Creates a builder with the values of the provided {@link Transformer}. */ /** Creates a builder with the values of the provided {@link Transformer}. */
...@@ -146,14 +141,10 @@ public final class Transformer { ...@@ -146,14 +141,10 @@ public final class Transformer {
this.context = transformer.context; this.context = transformer.context;
this.mediaSourceFactory = transformer.mediaSourceFactory; this.mediaSourceFactory = transformer.mediaSourceFactory;
this.muxerFactory = transformer.muxerFactory; this.muxerFactory = transformer.muxerFactory;
this.removeAudio = transformer.transformation.removeAudio; this.removeAudio = transformer.removeAudio;
this.removeVideo = transformer.transformation.removeVideo; this.removeVideo = transformer.removeVideo;
this.flattenForSlowMotion = transformer.transformation.flattenForSlowMotion; this.containerMimeType = transformer.containerMimeType;
this.outputHeight = transformer.transformation.outputHeight; this.transformationRequest = transformer.transformationRequest;
this.transformationMatrix = transformer.transformation.transformationMatrix;
this.containerMimeType = transformer.transformation.containerMimeType;
this.audioMimeType = transformer.transformation.audioMimeType;
this.videoMimeType = transformer.transformation.videoMimeType;
this.listener = transformer.listener; this.listener = transformer.listener;
this.looper = transformer.looper; this.looper = transformer.looper;
this.encoderFactory = transformer.encoderFactory; this.encoderFactory = transformer.encoderFactory;
...@@ -169,6 +160,17 @@ public final class Transformer { ...@@ -169,6 +160,17 @@ public final class Transformer {
} }
/** /**
* Sets the {@link TransformationRequest} which configures the editing and transcoding options.
*
* @param transformationRequest The {@link TransformationRequest}.
* @return This builder.
*/
public Builder setTransformationRequest(TransformationRequest transformationRequest) {
this.transformationRequest = transformationRequest;
return this;
}
/**
* Sets the {@link MediaSourceFactory} to be used to retrieve the inputs to transform. The * Sets the {@link MediaSourceFactory} to be used to retrieve the inputs to transform. The
* default value is a {@link DefaultMediaSourceFactory} built with the context provided in * default value is a {@link DefaultMediaSourceFactory} built with the context provided in
* {@link #Builder(Context) the constructor}. * {@link #Builder(Context) the constructor}.
...@@ -210,83 +212,31 @@ public final class Transformer { ...@@ -210,83 +212,31 @@ public final class Transformer {
} }
/** /**
* Sets whether the input should be flattened for media containing slow motion markers. The * @deprecated Use {@link TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}
* transformed output is obtained by removing the slow motion metadata and by actually slowing * instead.
* down the parts of the video and audio streams defined in this metadata. The default value for
* {@code flattenForSlowMotion} is {@code false}.
*
* <p>Only Samsung Extension Format (SEF) slow motion metadata type is supported. The
* transformation has no effect if the input does not contain this metadata type.
*
* <p>For SEF slow motion media, the following assumptions are made on the input:
*
* <ul>
* <li>The input container format is (unfragmented) MP4.
* <li>The input contains an AVC video elementary stream with temporal SVC.
* <li>The recording frame rate of the video is 120 or 240 fps.
* </ul>
*
* <p>If specifying a {@link MediaSourceFactory} using {@link
* #setMediaSourceFactory(MediaSourceFactory)}, make sure that {@link
* Mp4Extractor#FLAG_READ_SEF_DATA} is set on the {@link Mp4Extractor} used. Otherwise, the slow
* motion metadata will be ignored and the input won't be flattened.
*
* @param flattenForSlowMotion Whether to flatten for slow motion.
* @return This builder.
*/ */
@Deprecated
public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) { public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
this.flattenForSlowMotion = flattenForSlowMotion; transformationRequest =
transformationRequest.buildUpon().setFlattenForSlowMotion(flattenForSlowMotion).build();
return this; return this;
} }
/** /** @deprecated Use {@link TransformationRequest.Builder#setResolution(int)} instead. */
* Sets the output resolution using the output height. The default value is the same height as @Deprecated
* the input. Output width will scale to preserve the input video's aspect ratio.
*
* <p>For now, only "popular" heights like 144, 240, 360, 480, 720, 1080, 1440, or 2160 are
* supported, to ensure compatibility on different devices.
*
* <p>For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480).
*
* @param outputHeight The output height in pixels.
* @return This builder.
*/
public Builder setResolution(int outputHeight) { public Builder setResolution(int outputHeight) {
// TODO(Internal b/201293185): Restructure to input a Presentation class. transformationRequest = transformationRequest.buildUpon().setResolution(outputHeight).build();
// TODO(Internal b/201293185): Check encoder codec capabilities in order to allow arbitrary
// resolutions and reasonable fallbacks.
if (outputHeight != 144
&& outputHeight != 240
&& outputHeight != 360
&& outputHeight != 480
&& outputHeight != 720
&& outputHeight != 1080
&& outputHeight != 1440
&& outputHeight != 2160) {
throw new IllegalArgumentException(
"Please use a height of 144, 240, 360, 480, 720, 1080, 1440, or 2160.");
}
this.outputHeight = outputHeight;
return this; return this;
} }
/** /**
* Sets the transformation matrix. The default value is to apply no change. * @deprecated Use {@link TransformationRequest.Builder#setTransformationMatrix(Matrix)}
* * instead.
* <p>This can be used to perform operations supported by {@link Matrix}, like scaling and
* rotating the video.
*
* <p>For now, resolution will not be affected by this method.
*
* @param transformationMatrix The transformation to apply to video frames.
* @return This builder.
*/ */
@Deprecated
public Builder setTransformationMatrix(Matrix transformationMatrix) { public Builder setTransformationMatrix(Matrix transformationMatrix) {
// TODO(Internal b/201293185): After {@link #setResolution} supports arbitrary resolutions, transformationRequest =
// allow transformations to change the resolution, by scaling to the appropriate min/max transformationRequest.buildUpon().setTransformationMatrix(transformationMatrix).build();
// values. This will also be required to create the VertexTransformation class, in order to
// have aspect ratio helper methods (which require resolution to change).
this.transformationMatrix = transformationMatrix;
return this; return this;
} }
...@@ -300,40 +250,19 @@ public final class Transformer { ...@@ -300,40 +250,19 @@ public final class Transformer {
return this; return this;
} }
/** /** @deprecated Use {@link TransformationRequest.Builder#setVideoMimeType(String)} instead. */
* Sets the video MIME type of the output. The default value is to use the same MIME type as the @Deprecated
* input. Supported values are:
*
* <ul>
* <li>{@link MimeTypes#VIDEO_H263}
* <li>{@link MimeTypes#VIDEO_H264}
* <li>{@link MimeTypes#VIDEO_H265} from API level 24
* <li>{@link MimeTypes#VIDEO_MP4V}
* </ul>
*
* @param videoMimeType The MIME type of the video samples in the output.
* @return This builder.
*/
public Builder setVideoMimeType(String videoMimeType) { public Builder setVideoMimeType(String videoMimeType) {
this.videoMimeType = videoMimeType; transformationRequest =
transformationRequest.buildUpon().setVideoMimeType(videoMimeType).build();
return this; return this;
} }
/** /** @deprecated Use {@link TransformationRequest.Builder#setAudioMimeType(String)} instead. */
* Sets the audio MIME type of the output. The default value is to use the same MIME type as the @Deprecated
* input. Supported values are:
*
* <ul>
* <li>{@link MimeTypes#AUDIO_AAC}
* <li>{@link MimeTypes#AUDIO_AMR_NB}
* <li>{@link MimeTypes#AUDIO_AMR_WB}
* </ul>
*
* @param audioMimeType The MIME type of the audio samples in the output.
* @return This builder.
*/
public Builder setAudioMimeType(String audioMimeType) { public Builder setAudioMimeType(String audioMimeType) {
this.audioMimeType = audioMimeType; transformationRequest =
transformationRequest.buildUpon().setAudioMimeType(audioMimeType).build();
return this; return this;
} }
...@@ -432,7 +361,7 @@ public final class Transformer { ...@@ -432,7 +361,7 @@ public final class Transformer {
checkNotNull(context); checkNotNull(context);
if (mediaSourceFactory == null) { if (mediaSourceFactory == null) {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
if (flattenForSlowMotion) { if (transformationRequest.flattenForSlowMotion) {
defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA); defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA);
} }
mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory); mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
...@@ -440,27 +369,20 @@ public final class Transformer { ...@@ -440,27 +369,20 @@ public final class Transformer {
checkState( checkState(
muxerFactory.supportsOutputMimeType(containerMimeType), muxerFactory.supportsOutputMimeType(containerMimeType),
"Unsupported container MIME type: " + containerMimeType); "Unsupported container MIME type: " + containerMimeType);
if (audioMimeType != null) { if (transformationRequest.audioMimeType != null) {
checkSampleMimeType(audioMimeType); checkSampleMimeType(transformationRequest.audioMimeType);
} }
if (videoMimeType != null) { if (transformationRequest.videoMimeType != null) {
checkSampleMimeType(videoMimeType); checkSampleMimeType(transformationRequest.videoMimeType);
} }
Transformation transformation =
new Transformation(
removeAudio,
removeVideo,
flattenForSlowMotion,
outputHeight,
transformationMatrix,
containerMimeType,
audioMimeType,
videoMimeType);
return new Transformer( return new Transformer(
context, context,
mediaSourceFactory, mediaSourceFactory,
muxerFactory, muxerFactory,
transformation, removeAudio,
removeVideo,
containerMimeType,
transformationRequest,
listener, listener,
looper, looper,
clock, clock,
...@@ -551,7 +473,10 @@ public final class Transformer { ...@@ -551,7 +473,10 @@ public final class Transformer {
private final Context context; private final Context context;
private final MediaSourceFactory mediaSourceFactory; private final MediaSourceFactory mediaSourceFactory;
private final Muxer.Factory muxerFactory; private final Muxer.Factory muxerFactory;
private final Transformation transformation; private final boolean removeAudio;
private final boolean removeVideo;
private final String containerMimeType;
private final TransformationRequest transformationRequest;
private final Looper looper; private final Looper looper;
private final Clock clock; private final Clock clock;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
...@@ -567,20 +492,24 @@ public final class Transformer { ...@@ -567,20 +492,24 @@ public final class Transformer {
Context context, Context context,
MediaSourceFactory mediaSourceFactory, MediaSourceFactory mediaSourceFactory,
Muxer.Factory muxerFactory, Muxer.Factory muxerFactory,
Transformation transformation, boolean removeAudio,
boolean removeVideo,
String containerMimeType,
TransformationRequest transformationRequest,
Transformer.Listener listener, Transformer.Listener listener,
Looper looper, Looper looper,
Clock clock, Clock clock,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) { Transformer.DebugViewProvider debugViewProvider) {
checkState( checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed.");
!transformation.removeAudio || !transformation.removeVideo,
"Audio and video cannot both be removed.");
this.context = context; this.context = context;
this.mediaSourceFactory = mediaSourceFactory; this.mediaSourceFactory = mediaSourceFactory;
this.muxerFactory = muxerFactory; this.muxerFactory = muxerFactory;
this.transformation = transformation; this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
this.containerMimeType = containerMimeType;
this.transformationRequest = transformationRequest;
this.listener = listener; this.listener = listener;
this.looper = looper; this.looper = looper;
this.clock = clock; this.clock = clock;
...@@ -626,7 +555,7 @@ public final class Transformer { ...@@ -626,7 +555,7 @@ public final class Transformer {
* @throws IOException If an error occurs opening the output file for writing. * @throws IOException If an error occurs opening the output file for writing.
*/ */
public void startTransformation(MediaItem mediaItem, String path) throws IOException { public void startTransformation(MediaItem mediaItem, String path) throws IOException {
startTransformation(mediaItem, muxerFactory.create(path, transformation.containerMimeType)); startTransformation(mediaItem, muxerFactory.create(path, containerMimeType));
} }
/** /**
...@@ -654,8 +583,7 @@ public final class Transformer { ...@@ -654,8 +583,7 @@ public final class Transformer {
@RequiresApi(26) @RequiresApi(26)
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor)
throws IOException { throws IOException {
startTransformation( startTransformation(mediaItem, muxerFactory.create(parcelFileDescriptor, containerMimeType));
mediaItem, muxerFactory.create(parcelFileDescriptor, transformation.containerMimeType));
} }
private void startTransformation(MediaItem mediaItem, Muxer muxer) { private void startTransformation(MediaItem mediaItem, Muxer muxer) {
...@@ -664,8 +592,7 @@ public final class Transformer { ...@@ -664,8 +592,7 @@ public final class Transformer {
throw new IllegalStateException("There is already a transformation in progress."); throw new IllegalStateException("There is already a transformation in progress.");
} }
MuxerWrapper muxerWrapper = MuxerWrapper muxerWrapper = new MuxerWrapper(muxer, muxerFactory, containerMimeType);
new MuxerWrapper(muxer, muxerFactory, transformation.containerMimeType);
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
trackSelector.setParameters( trackSelector.setParameters(
...@@ -688,7 +615,9 @@ public final class Transformer { ...@@ -688,7 +615,9 @@ public final class Transformer {
new TransformerRenderersFactory( new TransformerRenderersFactory(
context, context,
muxerWrapper, muxerWrapper,
transformation, removeAudio,
removeVideo,
transformationRequest,
encoderFactory, encoderFactory,
decoderFactory, decoderFactory,
debugViewProvider)) debugViewProvider))
...@@ -779,7 +708,9 @@ public final class Transformer { ...@@ -779,7 +708,9 @@ public final class Transformer {
private final Context context; private final Context context;
private final MuxerWrapper muxerWrapper; private final MuxerWrapper muxerWrapper;
private final TransformerMediaClock mediaClock; private final TransformerMediaClock mediaClock;
private final Transformation transformation; private final boolean removeAudio;
private final boolean removeVideo;
private final TransformationRequest transformationRequest;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory; private final Codec.DecoderFactory decoderFactory;
private final Transformer.DebugViewProvider debugViewProvider; private final Transformer.DebugViewProvider debugViewProvider;
...@@ -787,13 +718,17 @@ public final class Transformer { ...@@ -787,13 +718,17 @@ public final class Transformer {
public TransformerRenderersFactory( public TransformerRenderersFactory(
Context context, Context context,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
Transformation transformation, boolean removeAudio,
boolean removeVideo,
TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) { Transformer.DebugViewProvider debugViewProvider) {
this.context = context; this.context = context;
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
this.transformation = transformation; this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
this.transformationRequest = transformationRequest;
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory; this.decoderFactory = decoderFactory;
this.debugViewProvider = debugViewProvider; this.debugViewProvider = debugViewProvider;
...@@ -807,22 +742,22 @@ public final class Transformer { ...@@ -807,22 +742,22 @@ public final class Transformer {
AudioRendererEventListener audioRendererEventListener, AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput, TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) { MetadataOutput metadataRendererOutput) {
int rendererCount = transformation.removeAudio || transformation.removeVideo ? 1 : 2; int rendererCount = removeAudio || removeVideo ? 1 : 2;
Renderer[] renderers = new Renderer[rendererCount]; Renderer[] renderers = new Renderer[rendererCount];
int index = 0; int index = 0;
if (!transformation.removeAudio) { if (!removeAudio) {
renderers[index] = renderers[index] =
new TransformerAudioRenderer( new TransformerAudioRenderer(
muxerWrapper, mediaClock, transformation, encoderFactory, decoderFactory); muxerWrapper, mediaClock, transformationRequest, encoderFactory, decoderFactory);
index++; index++;
} }
if (!transformation.removeVideo) { if (!removeVideo) {
renderers[index] = renderers[index] =
new TransformerVideoRenderer( new TransformerVideoRenderer(
context, context,
muxerWrapper, muxerWrapper,
mediaClock, mediaClock,
transformation, transformationRequest,
encoderFactory, encoderFactory,
decoderFactory, decoderFactory,
debugViewProvider); debugViewProvider);
......
...@@ -39,10 +39,10 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; ...@@ -39,10 +39,10 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
public TransformerAudioRenderer( public TransformerAudioRenderer(
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
Transformation transformation, TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory) { Codec.DecoderFactory decoderFactory) {
super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation); super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformationRequest);
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory; this.decoderFactory = decoderFactory;
decoderInputBuffer = decoderInputBuffer =
...@@ -69,7 +69,8 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; ...@@ -69,7 +69,8 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
Format inputFormat = checkNotNull(formatHolder.format); Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) { if (shouldTranscode(inputFormat)) {
samplePipeline = samplePipeline =
new AudioSamplePipeline(inputFormat, transformation, encoderFactory, decoderFactory); new AudioSamplePipeline(
inputFormat, transformationRequest, encoderFactory, decoderFactory);
} else { } else {
samplePipeline = new PassthroughSamplePipeline(inputFormat); samplePipeline = new PassthroughSamplePipeline(inputFormat);
} }
...@@ -77,11 +78,11 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; ...@@ -77,11 +78,11 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
} }
private boolean shouldTranscode(Format inputFormat) { private boolean shouldTranscode(Format inputFormat) {
if (transformation.audioMimeType != null if (transformationRequest.audioMimeType != null
&& !transformation.audioMimeType.equals(inputFormat.sampleMimeType)) { && !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) {
return true; return true;
} }
if (transformation.flattenForSlowMotion && isSlowMotion(inputFormat)) { if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
return true; return true;
} }
return false; return false;
......
...@@ -38,7 +38,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -38,7 +38,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
protected final MuxerWrapper muxerWrapper; protected final MuxerWrapper muxerWrapper;
protected final TransformerMediaClock mediaClock; protected final TransformerMediaClock mediaClock;
protected final Transformation transformation; protected final TransformationRequest transformationRequest;
protected boolean isRendererStarted; protected boolean isRendererStarted;
protected boolean muxerWrapperTrackAdded; protected boolean muxerWrapperTrackAdded;
...@@ -50,11 +50,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -50,11 +50,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
int trackType, int trackType,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
Transformation transformation) { TransformationRequest transformationRequest) {
super(trackType); super(trackType);
this.muxerWrapper = muxerWrapper; this.muxerWrapper = muxerWrapper;
this.mediaClock = mediaClock; this.mediaClock = mediaClock;
this.transformation = transformation; this.transformationRequest = transformationRequest;
} }
@Override @Override
...@@ -65,14 +65,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -65,14 +65,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
} else if ((MimeTypes.isAudio(sampleMimeType) } else if ((MimeTypes.isAudio(sampleMimeType)
&& muxerWrapper.supportsSampleMimeType( && muxerWrapper.supportsSampleMimeType(
transformation.audioMimeType == null transformationRequest.audioMimeType == null
? sampleMimeType ? sampleMimeType
: transformation.audioMimeType)) : transformationRequest.audioMimeType))
|| (MimeTypes.isVideo(sampleMimeType) || (MimeTypes.isVideo(sampleMimeType)
&& muxerWrapper.supportsSampleMimeType( && muxerWrapper.supportsSampleMimeType(
transformation.videoMimeType == null transformationRequest.videoMimeType == null
? sampleMimeType ? sampleMimeType
: transformation.videoMimeType))) { : transformationRequest.videoMimeType))) {
return RendererCapabilities.create(C.FORMAT_HANDLED); return RendererCapabilities.create(C.FORMAT_HANDLED);
} else { } else {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE);
......
...@@ -45,11 +45,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -45,11 +45,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Context context, Context context,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
Transformation transformation, TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) { Transformer.DebugViewProvider debugViewProvider) {
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation); super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformationRequest);
this.context = context; this.context = context;
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory; this.decoderFactory = decoderFactory;
...@@ -81,29 +81,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -81,29 +81,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
new VideoSamplePipeline( new VideoSamplePipeline(
context, context,
inputFormat, inputFormat,
transformation, transformationRequest,
encoderFactory, encoderFactory,
decoderFactory, decoderFactory,
debugViewProvider); debugViewProvider);
} else { } else {
samplePipeline = new PassthroughSamplePipeline(inputFormat); samplePipeline = new PassthroughSamplePipeline(inputFormat);
} }
if (transformation.flattenForSlowMotion) { if (transformationRequest.flattenForSlowMotion) {
sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat); sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat);
} }
return true; return true;
} }
private boolean shouldTranscode(Format inputFormat) { private boolean shouldTranscode(Format inputFormat) {
if (transformation.videoMimeType != null if (transformationRequest.videoMimeType != null
&& !transformation.videoMimeType.equals(inputFormat.sampleMimeType)) { && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) {
return true; return true;
} }
if (transformation.outputHeight != C.LENGTH_UNSET if (transformationRequest.outputHeight != C.LENGTH_UNSET
&& transformation.outputHeight != inputFormat.height) { && transformationRequest.outputHeight != inputFormat.height) {
return true; return true;
} }
if (!transformation.transformationMatrix.isIdentity()) { if (!transformationRequest.transformationMatrix.isIdentity()) {
return true; return true;
} }
return false; return false;
......
...@@ -50,7 +50,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -50,7 +50,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public VideoSamplePipeline( public VideoSamplePipeline(
Context context, Context context,
Format inputFormat, Format inputFormat,
Transformation transformation, TransformationRequest transformationRequest,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) Transformer.DebugViewProvider debugViewProvider)
...@@ -63,10 +63,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -63,10 +63,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// TODO(internal b/209781577): Think about which edge length should be set for portrait videos. // TODO(internal b/209781577): Think about which edge length should be set for portrait videos.
int outputWidth = inputFormat.width; int outputWidth = inputFormat.width;
int outputHeight = inputFormat.height; int outputHeight = inputFormat.height;
if (transformation.outputHeight != C.LENGTH_UNSET if (transformationRequest.outputHeight != C.LENGTH_UNSET
&& transformation.outputHeight != inputFormat.height) { && transformationRequest.outputHeight != inputFormat.height) {
outputWidth = inputFormat.width * transformation.outputHeight / inputFormat.height; outputWidth = inputFormat.width * transformationRequest.outputHeight / inputFormat.height;
outputHeight = transformation.outputHeight; outputHeight = transformationRequest.outputHeight;
} }
if (inputFormat.height > inputFormat.width) { if (inputFormat.height > inputFormat.width) {
...@@ -83,7 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -83,7 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// them back for improved encoder compatibility. // them back for improved encoder compatibility.
// TODO(internal b/201293185): After fragment shader transformations are implemented, put // TODO(internal b/201293185): After fragment shader transformations are implemented, put
// postrotation in a later vertex shader. // postrotation in a later vertex shader.
transformation.transformationMatrix.postRotate(outputRotationDegrees); transformationRequest.transformationMatrix.postRotate(outputRotationDegrees);
encoder = encoder =
encoderFactory.createForVideoEncoding( encoderFactory.createForVideoEncoding(
...@@ -92,19 +92,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -92,19 +92,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
.setHeight(outputHeight) .setHeight(outputHeight)
.setRotationDegrees(0) .setRotationDegrees(0)
.setSampleMimeType( .setSampleMimeType(
transformation.videoMimeType != null transformationRequest.videoMimeType != null
? transformation.videoMimeType ? transformationRequest.videoMimeType
: inputFormat.sampleMimeType) : inputFormat.sampleMimeType)
.build()); .build());
if (inputFormat.height != outputHeight if (inputFormat.height != outputHeight
|| inputFormat.width != outputWidth || inputFormat.width != outputWidth
|| !transformation.transformationMatrix.isIdentity()) { || !transformationRequest.transformationMatrix.isIdentity()) {
frameEditor = frameEditor =
FrameEditor.create( FrameEditor.create(
context, context,
outputWidth, outputWidth,
outputHeight, outputHeight,
transformation.transformationMatrix, transformationRequest.transformationMatrix,
/* outputSurface= */ checkNotNull(encoder.getInputSurface()), /* outputSurface= */ checkNotNull(encoder.getInputSurface()),
debugViewProvider); debugViewProvider);
} }
......
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.transformer;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Matrix;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.util.MimeTypes;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link TransformationRequest}. */
@RunWith(AndroidJUnit4.class)
public class TransformationRequestTest {
@Test
public void buildUponTransformationRequest_createsEqualTransformationRequest() {
TransformationRequest request = createTestTransformationRequest();
assertThat(request.buildUpon().build()).isEqualTo(request);
}
private static TransformationRequest createTestTransformationRequest() {
Matrix transformationMatrix = new Matrix();
transformationMatrix.preRotate(36);
transformationMatrix.postTranslate((float) 0.5, (float) -0.2);
return new TransformationRequest.Builder()
.setFlattenForSlowMotion(true)
.setAudioMimeType(MimeTypes.AUDIO_AAC)
.setVideoMimeType(MimeTypes.VIDEO_H264)
.setTransformationMatrix(transformationMatrix)
.build();
}
}
...@@ -51,4 +51,36 @@ public class TransformerBuilderTest { ...@@ -51,4 +51,36 @@ public class TransformerBuilderTest {
IllegalStateException.class, IllegalStateException.class,
() -> new Transformer.Builder(context).setRemoveAudio(true).setRemoveVideo(true).build()); () -> new Transformer.Builder(context).setRemoveAudio(true).setRemoveVideo(true).build());
} }
// TODO(b/209469847): Move this test to TransformationRequestBuilderTest once deprecated
// Transformer.Builder#setOuputMimeType(String) has been removed.
@Test
public void build_withUnsupportedAudioMimeType_throws() {
Context context = ApplicationProvider.getApplicationContext();
TransformationRequest transformationRequest =
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_UNKNOWN).build();
assertThrows(
IllegalStateException.class,
() ->
new Transformer.Builder(context)
.setTransformationRequest(transformationRequest)
.build());
}
// TODO(b/209469847): Move this test to TransformationRequestBuilderTest once deprecated
// Transformer.Builder#setOuputMimeType(String) has been removed.
@Test
public void build_withUnsupportedVideoMimeType_throws() {
Context context = ApplicationProvider.getApplicationContext();
TransformationRequest transformationRequest =
new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_UNKNOWN).build();
assertThrows(
IllegalStateException.class,
() ->
new Transformer.Builder(context)
.setTransformationRequest(transformationRequest)
.build());
}
} }
...@@ -224,9 +224,10 @@ public final class TransformerTest { ...@@ -224,9 +224,10 @@ public final class TransformerTest {
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setFlattenForSlowMotion(true)
.setClock(clock) .setClock(clock)
.setMuxerFactory(new TestMuxerFactory()) .setMuxerFactory(new TestMuxerFactory())
.setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.build(); .build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION);
...@@ -243,7 +244,10 @@ public final class TransformerTest { ...@@ -243,7 +244,10 @@ public final class TransformerTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setClock(clock) .setClock(clock)
.setMuxerFactory(new TestMuxerFactory()) .setMuxerFactory(new TestMuxerFactory())
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type .setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type
.build())
.build(); .build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
...@@ -262,7 +266,10 @@ public final class TransformerTest { ...@@ -262,7 +266,10 @@ public final class TransformerTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setClock(clock) .setClock(clock)
.setMuxerFactory(new TestMuxerFactory()) .setMuxerFactory(new TestMuxerFactory())
.setTransformationRequest(
new TransformationRequest.Builder()
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type
.build())
.build(); .build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED);
...@@ -281,7 +288,10 @@ public final class TransformerTest { ...@@ -281,7 +288,10 @@ public final class TransformerTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setClock(clock) .setClock(clock)
.setMuxerFactory(new TestMuxerFactory()) .setMuxerFactory(new TestMuxerFactory())
.setTransformationRequest(
new TransformationRequest.Builder()
.setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type .setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type
.build())
.build(); .build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment