diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/RepeatedTranscodeTransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/RepeatedTranscodeTransformationTest.java index 4c056ff803..be2162a7e5 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/RepeatedTranscodeTransformationTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/RepeatedTranscodeTransformationTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.content.Context; import android.graphics.Matrix; import androidx.media3.common.MimeTypes; +import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.Transformer; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -43,9 +44,12 @@ public final class RepeatedTranscodeTransformationTest { transformationMatrix.postTranslate((float) 0.1, (float) 0.1); Transformer transformer = new Transformer.Builder(context) - .setVideoMimeType(MimeTypes.VIDEO_H265) - .setTransformationMatrix(transformationMatrix) - .setAudioMimeType(MimeTypes.AUDIO_AMR_NB) + .setTransformationRequest( + new TransformationRequest.Builder() + .setVideoMimeType(MimeTypes.VIDEO_H265) + .setTransformationMatrix(transformationMatrix) + .setAudioMimeType(MimeTypes.AUDIO_AMR_NB) + .build()) .build(); Set differentOutputSizesBytes = new HashSet<>(); @@ -74,9 +78,12 @@ public final class RepeatedTranscodeTransformationTest { transformationMatrix.postTranslate((float) 0.1, (float) 0.1); Transformer transformer = new Transformer.Builder(context) - .setVideoMimeType(MimeTypes.VIDEO_H265) - .setTransformationMatrix(transformationMatrix) .setRemoveAudio(true) + .setTransformationRequest( + new TransformationRequest.Builder() + .setVideoMimeType(MimeTypes.VIDEO_H265) + .setTransformationMatrix(transformationMatrix) + .build()) .build(); Set differentOutputSizesBytes = new HashSet<>(); @@ -103,8 +110,11 @@ public final class RepeatedTranscodeTransformationTest { Context context = ApplicationProvider.getApplicationContext(); Transformer transcodingTransformer = new Transformer.Builder(context) - .setAudioMimeType(MimeTypes.AUDIO_AMR_NB) .setRemoveVideo(true) + .setTransformationRequest( + new TransformationRequest.Builder() + .setAudioMimeType(MimeTypes.AUDIO_AMR_NB) + .build()) .build(); Set differentOutputSizesBytes = new HashSet<>(); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SefTransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SefTransformationTest.java index f071450801..53118ed4d0 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SefTransformationTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SefTransformationTest.java @@ -19,6 +19,7 @@ import static androidx.media3.transformer.mh.AndroidTestUtil.SEF_ASSET_URI_STRIN import static androidx.media3.transformer.mh.AndroidTestUtil.runTransformer; import android.content.Context; +import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.Transformer; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -32,7 +33,10 @@ public class SefTransformationTest { public void sefTransform() throws Exception { Context context = ApplicationProvider.getApplicationContext(); Transformer transformer = - new Transformer.Builder(context).setFlattenForSlowMotion(true).build(); + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) + .build(); runTransformer( context, /* testId = */ "sefTransform", diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetTransformationMatrixTransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetTransformationMatrixTransformationTest.java index 6fdcfe0d33..7b87803f17 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetTransformationMatrixTransformationTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetTransformationMatrixTransformationTest.java @@ -20,6 +20,7 @@ import static androidx.media3.transformer.mh.AndroidTestUtil.runTransformer; import android.content.Context; import android.graphics.Matrix; +import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.Transformer; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -35,7 +36,12 @@ public class SetTransformationMatrixTransformationTest { Matrix transformationMatrix = new Matrix(); transformationMatrix.postTranslate(/* dx= */ .2f, /* dy= */ .1f); Transformer transformer = - new Transformer.Builder(context).setTransformationMatrix(transformationMatrix).build(); + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .setTransformationMatrix(transformationMatrix) + .build()) + .build(); runTransformer( context, diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java index 9d4b1eb873..26a0e7810c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java @@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024; private final Format inputFormat; - private final Transformation transformation; + private final TransformationRequest transformationRequest; private final Codec.EncoderFactory encoderFactory; private final Codec decoder; @@ -66,12 +66,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public AudioSamplePipeline( Format inputFormat, - Transformation transformation, + TransformationRequest transformationRequest, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory) throws TransformationException { this.inputFormat = inputFormat; - this.transformation = transformation; + this.transformationRequest = transformationRequest; this.encoderFactory = encoderFactory; decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); @@ -294,7 +294,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; decoderOutputFormat.sampleRate, decoderOutputFormat.channelCount, decoderOutputFormat.pcmEncoding); - if (transformation.flattenForSlowMotion) { + if (transformationRequest.flattenForSlowMotion) { try { outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat); flushSonicAndSetSpeed(currentSpeed); @@ -310,9 +310,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; encoderFactory.createForAudioEncoding( new Format.Builder() .setSampleMimeType( - transformation.audioMimeType == null + transformationRequest.audioMimeType == null ? inputFormat.sampleMimeType - : transformation.audioMimeType) + : transformationRequest.audioMimeType) .setSampleRate(outputAudioFormat.sampleRate) .setChannelCount(outputAudioFormat.channelCount) .setAverageBitrate(DEFAULT_ENCODER_BITRATE) @@ -322,7 +322,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } private boolean isSpeedChanging(BufferInfo bufferInfo) { - if (!transformation.flattenForSlowMotion) { + if (!transformationRequest.flattenForSlowMotion) { return false; } float newSpeed = speedProvider.getSpeed(bufferInfo.presentationTimeUs); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformation.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformation.java deleted file mode 100644 index 206784e661..0000000000 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformation.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 androidx.media3.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; - } -} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java new file mode 100644 index 0000000000..5d569d541e --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java @@ -0,0 +1,241 @@ +/* + * 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 androidx.media3.transformer; + +import android.graphics.Matrix; +import androidx.annotation.Nullable; +import androidx.media3.common.C; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.source.MediaSourceFactory; +import androidx.media3.extractor.mp4.Mp4Extractor; + +/** A media transformation request. */ +@UnstableApi +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. + * + *

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. + * + *

This can be used to perform operations supported by {@link Matrix}, like scaling and + * rotating the video. + * + *

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}. + * + *

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. + * + *

For SEF slow motion media, the following assumptions are made on the input: + * + *

    + *
  • The input container format is (unfragmented) MP4. + *
  • The input contains an AVC video elementary stream with temporal SVC. + *
  • The recording frame rate of the video is 120 or 240 fps. + *
+ * + *

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. + * + *

For now, only "popular" heights like 144, 240, 360, 480, 720, 1080, 1440, or 2160 are + * supported, to ensure compatibility on different devices. + * + *

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: + * + *

    + *
  • {@link MimeTypes#VIDEO_H263} + *
  • {@link MimeTypes#VIDEO_H264} + *
  • {@link MimeTypes#VIDEO_H265} from API level 24 + *
  • {@link MimeTypes#VIDEO_MP4V} + *
+ * + * @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: + * + *
    + *
  • {@link MimeTypes#AUDIO_AAC} + *
  • {@link MimeTypes#AUDIO_AMR_NB} + *
  • {@link MimeTypes#AUDIO_AMR_WB} + *
+ * + * @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); + } +} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index 29c2f2eb3a..421132dee3 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -99,12 +99,9 @@ public final class Transformer { private Muxer.Factory muxerFactory; private boolean removeAudio; private boolean removeVideo; - private boolean flattenForSlowMotion; - private int outputHeight; - private Matrix transformationMatrix; private String containerMimeType; - @Nullable private String audioMimeType; - @Nullable private String videoMimeType; + // TODO(b/204869912): Make final once deprecated setters are removed. + private TransformationRequest transformationRequest; private Transformer.Listener listener; private DebugViewProvider debugViewProvider; private Looper looper; @@ -115,14 +112,13 @@ public final class Transformer { @Deprecated public Builder() { muxerFactory = new FrameworkMuxer.Factory(); - outputHeight = C.LENGTH_UNSET; - transformationMatrix = new Matrix(); - containerMimeType = MimeTypes.VIDEO_MP4; listener = new Listener() {}; looper = Util.getCurrentOrMainLooper(); clock = Clock.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT; debugViewProvider = DebugViewProvider.NONE; + containerMimeType = MimeTypes.VIDEO_MP4; + transformationRequest = new TransformationRequest.Builder().build(); } /** @@ -133,14 +129,13 @@ public final class Transformer { public Builder(Context context) { this.context = context.getApplicationContext(); muxerFactory = new FrameworkMuxer.Factory(); - outputHeight = C.LENGTH_UNSET; - transformationMatrix = new Matrix(); - containerMimeType = MimeTypes.VIDEO_MP4; listener = new Listener() {}; looper = Util.getCurrentOrMainLooper(); clock = Clock.DEFAULT; encoderFactory = Codec.EncoderFactory.DEFAULT; debugViewProvider = DebugViewProvider.NONE; + containerMimeType = MimeTypes.VIDEO_MP4; + this.transformationRequest = new TransformationRequest.Builder().build(); } /** Creates a builder with the values of the provided {@link Transformer}. */ @@ -148,14 +143,10 @@ public final class Transformer { this.context = transformer.context; this.mediaSourceFactory = transformer.mediaSourceFactory; this.muxerFactory = transformer.muxerFactory; - this.removeAudio = transformer.transformation.removeAudio; - this.removeVideo = transformer.transformation.removeVideo; - this.flattenForSlowMotion = transformer.transformation.flattenForSlowMotion; - this.outputHeight = transformer.transformation.outputHeight; - this.transformationMatrix = transformer.transformation.transformationMatrix; - this.containerMimeType = transformer.transformation.containerMimeType; - this.audioMimeType = transformer.transformation.audioMimeType; - this.videoMimeType = transformer.transformation.videoMimeType; + this.removeAudio = transformer.removeAudio; + this.removeVideo = transformer.removeVideo; + this.containerMimeType = transformer.containerMimeType; + this.transformationRequest = transformer.transformationRequest; this.listener = transformer.listener; this.looper = transformer.looper; this.encoderFactory = transformer.encoderFactory; @@ -170,6 +161,17 @@ public final class Transformer { return this; } + /** + * 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 * default value is a {@link DefaultMediaSourceFactory} built with the context provided in @@ -212,83 +214,31 @@ public final class Transformer { } /** - * 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}. - * - *

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. - * - *

For SEF slow motion media, the following assumptions are made on the input: - * - *

    - *
  • The input container format is (unfragmented) MP4. - *
  • The input contains an AVC video elementary stream with temporal SVC. - *
  • The recording frame rate of the video is 120 or 240 fps. - *
- * - *

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 Use {@link TransformationRequest.Builder#setFlattenForSlowMotion(boolean)} + * instead. */ + @Deprecated public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) { - this.flattenForSlowMotion = flattenForSlowMotion; + transformationRequest = + transformationRequest.buildUpon().setFlattenForSlowMotion(flattenForSlowMotion).build(); 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. - * - *

For now, only "popular" heights like 144, 240, 360, 480, 720, 1080, 1440, or 2160 are - * supported, to ensure compatibility on different devices. - * - *

For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480). - * - * @param outputHeight The output height in pixels. - * @return This builder. - */ + /** @deprecated Use {@link TransformationRequest.Builder#setResolution(int)} instead. */ + @Deprecated public Builder setResolution(int outputHeight) { - // TODO(Internal b/201293185): Restructure to input a Presentation class. - // 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; + transformationRequest = transformationRequest.buildUpon().setResolution(outputHeight).build(); return this; } /** - * Sets the transformation matrix. The default value is to apply no change. - * - *

This can be used to perform operations supported by {@link Matrix}, like scaling and - * rotating the video. - * - *

For now, resolution will not be affected by this method. - * - * @param transformationMatrix The transformation to apply to video frames. - * @return This builder. + * @deprecated Use {@link TransformationRequest.Builder#setTransformationMatrix(Matrix)} + * instead. */ + @Deprecated public Builder setTransformationMatrix(Matrix transformationMatrix) { - // TODO(Internal 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; + transformationRequest = + transformationRequest.buildUpon().setTransformationMatrix(transformationMatrix).build(); return this; } @@ -302,40 +252,19 @@ public final class Transformer { 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: - * - *

    - *
  • {@link MimeTypes#VIDEO_H263} - *
  • {@link MimeTypes#VIDEO_H264} - *
  • {@link MimeTypes#VIDEO_H265} from API level 24 - *
  • {@link MimeTypes#VIDEO_MP4V} - *
- * - * @param videoMimeType The MIME type of the video samples in the output. - * @return This builder. - */ + /** @deprecated Use {@link TransformationRequest.Builder#setVideoMimeType(String)} instead. */ + @Deprecated public Builder setVideoMimeType(String videoMimeType) { - this.videoMimeType = videoMimeType; + transformationRequest = + transformationRequest.buildUpon().setVideoMimeType(videoMimeType).build(); 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: - * - *
    - *
  • {@link MimeTypes#AUDIO_AAC} - *
  • {@link MimeTypes#AUDIO_AMR_NB} - *
  • {@link MimeTypes#AUDIO_AMR_WB} - *
- * - * @param audioMimeType The MIME type of the audio samples in the output. - * @return This builder. - */ + /** @deprecated Use {@link TransformationRequest.Builder#setAudioMimeType(String)} instead. */ + @Deprecated public Builder setAudioMimeType(String audioMimeType) { - this.audioMimeType = audioMimeType; + transformationRequest = + transformationRequest.buildUpon().setAudioMimeType(audioMimeType).build(); return this; } @@ -434,7 +363,7 @@ public final class Transformer { checkNotNull(context); if (mediaSourceFactory == null) { DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); - if (flattenForSlowMotion) { + if (transformationRequest.flattenForSlowMotion) { defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA); } mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory); @@ -442,27 +371,20 @@ public final class Transformer { checkState( muxerFactory.supportsOutputMimeType(containerMimeType), "Unsupported container MIME type: " + containerMimeType); - if (audioMimeType != null) { - checkSampleMimeType(audioMimeType); + if (transformationRequest.audioMimeType != null) { + checkSampleMimeType(transformationRequest.audioMimeType); } - if (videoMimeType != null) { - checkSampleMimeType(videoMimeType); + if (transformationRequest.videoMimeType != null) { + checkSampleMimeType(transformationRequest.videoMimeType); } - Transformation transformation = - new Transformation( - removeAudio, - removeVideo, - flattenForSlowMotion, - outputHeight, - transformationMatrix, - containerMimeType, - audioMimeType, - videoMimeType); return new Transformer( context, mediaSourceFactory, muxerFactory, - transformation, + removeAudio, + removeVideo, + containerMimeType, + transformationRequest, listener, looper, clock, @@ -553,7 +475,10 @@ public final class Transformer { private final Context context; private final MediaSourceFactory mediaSourceFactory; 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 Clock clock; private final Codec.EncoderFactory encoderFactory; @@ -569,20 +494,24 @@ public final class Transformer { Context context, MediaSourceFactory mediaSourceFactory, Muxer.Factory muxerFactory, - Transformation transformation, + boolean removeAudio, + boolean removeVideo, + String containerMimeType, + TransformationRequest transformationRequest, Transformer.Listener listener, Looper looper, Clock clock, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory, Transformer.DebugViewProvider debugViewProvider) { - checkState( - !transformation.removeAudio || !transformation.removeVideo, - "Audio and video cannot both be removed."); + checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed."); this.context = context; this.mediaSourceFactory = mediaSourceFactory; this.muxerFactory = muxerFactory; - this.transformation = transformation; + this.removeAudio = removeAudio; + this.removeVideo = removeVideo; + this.containerMimeType = containerMimeType; + this.transformationRequest = transformationRequest; this.listener = listener; this.looper = looper; this.clock = clock; @@ -628,7 +557,7 @@ public final class Transformer { * @throws IOException If an error occurs opening the output file for writing. */ public void startTransformation(MediaItem mediaItem, String path) throws IOException { - startTransformation(mediaItem, muxerFactory.create(path, transformation.containerMimeType)); + startTransformation(mediaItem, muxerFactory.create(path, containerMimeType)); } /** @@ -656,8 +585,7 @@ public final class Transformer { @RequiresApi(26) public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) throws IOException { - startTransformation( - mediaItem, muxerFactory.create(parcelFileDescriptor, transformation.containerMimeType)); + startTransformation(mediaItem, muxerFactory.create(parcelFileDescriptor, containerMimeType)); } private void startTransformation(MediaItem mediaItem, Muxer muxer) { @@ -666,8 +594,7 @@ public final class Transformer { throw new IllegalStateException("There is already a transformation in progress."); } - MuxerWrapper muxerWrapper = - new MuxerWrapper(muxer, muxerFactory, transformation.containerMimeType); + MuxerWrapper muxerWrapper = new MuxerWrapper(muxer, muxerFactory, containerMimeType); this.muxerWrapper = muxerWrapper; DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); trackSelector.setParameters( @@ -690,7 +617,9 @@ public final class Transformer { new TransformerRenderersFactory( context, muxerWrapper, - transformation, + removeAudio, + removeVideo, + transformationRequest, encoderFactory, decoderFactory, debugViewProvider)) @@ -781,7 +710,9 @@ public final class Transformer { private final Context context; private final MuxerWrapper muxerWrapper; 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.DecoderFactory decoderFactory; private final Transformer.DebugViewProvider debugViewProvider; @@ -789,13 +720,17 @@ public final class Transformer { public TransformerRenderersFactory( Context context, MuxerWrapper muxerWrapper, - Transformation transformation, + boolean removeAudio, + boolean removeVideo, + TransformationRequest transformationRequest, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory, Transformer.DebugViewProvider debugViewProvider) { this.context = context; this.muxerWrapper = muxerWrapper; - this.transformation = transformation; + this.removeAudio = removeAudio; + this.removeVideo = removeVideo; + this.transformationRequest = transformationRequest; this.encoderFactory = encoderFactory; this.decoderFactory = decoderFactory; this.debugViewProvider = debugViewProvider; @@ -809,22 +744,22 @@ public final class Transformer { AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) { - int rendererCount = transformation.removeAudio || transformation.removeVideo ? 1 : 2; + int rendererCount = removeAudio || removeVideo ? 1 : 2; Renderer[] renderers = new Renderer[rendererCount]; int index = 0; - if (!transformation.removeAudio) { + if (!removeAudio) { renderers[index] = new TransformerAudioRenderer( - muxerWrapper, mediaClock, transformation, encoderFactory, decoderFactory); + muxerWrapper, mediaClock, transformationRequest, encoderFactory, decoderFactory); index++; } - if (!transformation.removeVideo) { + if (!removeVideo) { renderers[index] = new TransformerVideoRenderer( context, muxerWrapper, mediaClock, - transformation, + transformationRequest, encoderFactory, decoderFactory, debugViewProvider); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java index 9bc0ed82d5..663b440e53 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java @@ -39,10 +39,10 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData; public TransformerAudioRenderer( MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, - Transformation transformation, + TransformationRequest transformationRequest, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory) { - super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation); + super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformationRequest); this.encoderFactory = encoderFactory; this.decoderFactory = decoderFactory; decoderInputBuffer = @@ -69,7 +69,8 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData; Format inputFormat = checkNotNull(formatHolder.format); if (shouldTranscode(inputFormat)) { samplePipeline = - new AudioSamplePipeline(inputFormat, transformation, encoderFactory, decoderFactory); + new AudioSamplePipeline( + inputFormat, transformationRequest, encoderFactory, decoderFactory); } else { samplePipeline = new PassthroughSamplePipeline(inputFormat); } @@ -77,11 +78,11 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData; } private boolean shouldTranscode(Format inputFormat) { - if (transformation.audioMimeType != null - && !transformation.audioMimeType.equals(inputFormat.sampleMimeType)) { + if (transformationRequest.audioMimeType != null + && !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) { return true; } - if (transformation.flattenForSlowMotion && isSlowMotion(inputFormat)) { + if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) { return true; } return false; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java index 85a8eab957..e460e253d5 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java @@ -38,7 +38,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; protected final MuxerWrapper muxerWrapper; protected final TransformerMediaClock mediaClock; - protected final Transformation transformation; + protected final TransformationRequest transformationRequest; protected boolean isRendererStarted; protected boolean muxerWrapperTrackAdded; @@ -50,11 +50,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int trackType, MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, - Transformation transformation) { + TransformationRequest transformationRequest) { super(trackType); this.muxerWrapper = muxerWrapper; this.mediaClock = mediaClock; - this.transformation = transformation; + this.transformationRequest = transformationRequest; } @Override @@ -65,14 +65,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); } else if ((MimeTypes.isAudio(sampleMimeType) && muxerWrapper.supportsSampleMimeType( - transformation.audioMimeType == null + transformationRequest.audioMimeType == null ? sampleMimeType - : transformation.audioMimeType)) + : transformationRequest.audioMimeType)) || (MimeTypes.isVideo(sampleMimeType) && muxerWrapper.supportsSampleMimeType( - transformation.videoMimeType == null + transformationRequest.videoMimeType == null ? sampleMimeType - : transformation.videoMimeType))) { + : transformationRequest.videoMimeType))) { return RendererCapabilities.create(C.FORMAT_HANDLED); } else { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java index ea70704118..e1f3847776 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java @@ -45,11 +45,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; Context context, MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, - Transformation transformation, + TransformationRequest transformationRequest, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory, Transformer.DebugViewProvider debugViewProvider) { - super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation); + super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformationRequest); this.context = context; this.encoderFactory = encoderFactory; this.decoderFactory = decoderFactory; @@ -81,29 +81,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; new VideoSamplePipeline( context, inputFormat, - transformation, + transformationRequest, encoderFactory, decoderFactory, debugViewProvider); } else { samplePipeline = new PassthroughSamplePipeline(inputFormat); } - if (transformation.flattenForSlowMotion) { + if (transformationRequest.flattenForSlowMotion) { sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat); } return true; } private boolean shouldTranscode(Format inputFormat) { - if (transformation.videoMimeType != null - && !transformation.videoMimeType.equals(inputFormat.sampleMimeType)) { + if (transformationRequest.videoMimeType != null + && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { return true; } - if (transformation.outputHeight != C.LENGTH_UNSET - && transformation.outputHeight != inputFormat.height) { + if (transformationRequest.outputHeight != C.LENGTH_UNSET + && transformationRequest.outputHeight != inputFormat.height) { return true; } - if (!transformation.transformationMatrix.isIdentity()) { + if (!transformationRequest.transformationMatrix.isIdentity()) { return true; } return false; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java index e2416fb1dc..8d9f83b672 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java @@ -50,7 +50,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public VideoSamplePipeline( Context context, Format inputFormat, - Transformation transformation, + TransformationRequest transformationRequest, Codec.EncoderFactory encoderFactory, Codec.DecoderFactory decoderFactory, Transformer.DebugViewProvider debugViewProvider) @@ -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. int outputWidth = inputFormat.width; int outputHeight = inputFormat.height; - if (transformation.outputHeight != C.LENGTH_UNSET - && transformation.outputHeight != inputFormat.height) { - outputWidth = inputFormat.width * transformation.outputHeight / inputFormat.height; - outputHeight = transformation.outputHeight; + if (transformationRequest.outputHeight != C.LENGTH_UNSET + && transformationRequest.outputHeight != inputFormat.height) { + outputWidth = inputFormat.width * transformationRequest.outputHeight / inputFormat.height; + outputHeight = transformationRequest.outputHeight; } if (inputFormat.height > inputFormat.width) { @@ -83,7 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // them back for improved encoder compatibility. // TODO(internal b/201293185): After fragment shader transformations are implemented, put // postrotation in a later vertex shader. - transformation.transformationMatrix.postRotate(outputRotationDegrees); + transformationRequest.transformationMatrix.postRotate(outputRotationDegrees); encoder = encoderFactory.createForVideoEncoding( @@ -92,19 +92,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; .setHeight(outputHeight) .setRotationDegrees(0) .setSampleMimeType( - transformation.videoMimeType != null - ? transformation.videoMimeType + transformationRequest.videoMimeType != null + ? transformationRequest.videoMimeType : inputFormat.sampleMimeType) .build()); if (inputFormat.height != outputHeight || inputFormat.width != outputWidth - || !transformation.transformationMatrix.isIdentity()) { + || !transformationRequest.transformationMatrix.isIdentity()) { frameEditor = FrameEditor.create( context, outputWidth, outputHeight, - transformation.transformationMatrix, + transformationRequest.transformationMatrix, /* outputSurface= */ checkNotNull(encoder.getInputSurface()), debugViewProvider); } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java new file mode 100644 index 0000000000..848e5a0c73 --- /dev/null +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java @@ -0,0 +1,49 @@ +/* + * 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 androidx.media3.transformer; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.Matrix; +import androidx.media3.common.MimeTypes; +import androidx.test.ext.junit.runners.AndroidJUnit4; +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(); + } +} diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerBuilderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerBuilderTest.java index 4495e7d651..ac53be2b6f 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerBuilderTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerBuilderTest.java @@ -51,4 +51,36 @@ public class TransformerBuilderTest { IllegalStateException.class, () -> 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()); + } } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java index ea0d4a592e..d223328ba6 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java @@ -224,9 +224,10 @@ public final class TransformerTest { public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { Transformer transformer = new Transformer.Builder(context) - .setFlattenForSlowMotion(true) .setClock(clock) .setMuxerFactory(new TestMuxerFactory()) + .setTransformationRequest( + new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) .build(); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION); @@ -243,7 +244,10 @@ public final class TransformerTest { new Transformer.Builder(context) .setClock(clock) .setMuxerFactory(new TestMuxerFactory()) - .setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type + .setTransformationRequest( + new TransformationRequest.Builder() + .setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type + .build()) .build(); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY); @@ -262,7 +266,10 @@ public final class TransformerTest { new Transformer.Builder(context) .setClock(clock) .setMuxerFactory(new TestMuxerFactory()) - .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type + .setTransformationRequest( + new TransformationRequest.Builder() + .setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type + .build()) .build(); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED); @@ -281,7 +288,10 @@ public final class TransformerTest { new Transformer.Builder(context) .setClock(clock) .setMuxerFactory(new TestMuxerFactory()) - .setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type + .setTransformationRequest( + new TransformationRequest.Builder() + .setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type + .build()) .build(); MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);