• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.exoplayer2.transformer;
18 
19 import static com.google.android.exoplayer2.util.Assertions.checkArgument;
20 
21 import androidx.annotation.Nullable;
22 import com.google.android.exoplayer2.C;
23 import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
24 import com.google.android.exoplayer2.source.MediaSource;
25 import com.google.android.exoplayer2.util.MimeTypes;
26 import com.google.android.exoplayer2.util.Util;
27 
28 /** A media transformation request. */
29 public final class TransformationRequest {
30 
31   /** A builder for {@link TransformationRequest} instances. */
32   public static final class Builder {
33 
34     private boolean flattenForSlowMotion;
35     private float scaleX;
36     private float scaleY;
37     private float rotationDegrees;
38     private int outputHeight;
39     @Nullable private String audioMimeType;
40     @Nullable private String videoMimeType;
41     private boolean enableRequestSdrToneMapping;
42     private boolean enableHdrEditing;
43 
44     /**
45      * Creates a new instance with default values.
46      *
47      * <p>Use {@link TransformationRequest#buildUpon()} to obtain a builder representing an existing
48      * {@link TransformationRequest}.
49      */
Builder()50     public Builder() {
51       scaleX = 1;
52       scaleY = 1;
53       outputHeight = C.LENGTH_UNSET;
54     }
55 
Builder(TransformationRequest transformationRequest)56     private Builder(TransformationRequest transformationRequest) {
57       this.flattenForSlowMotion = transformationRequest.flattenForSlowMotion;
58       this.scaleX = transformationRequest.scaleX;
59       this.scaleY = transformationRequest.scaleY;
60       this.rotationDegrees = transformationRequest.rotationDegrees;
61       this.outputHeight = transformationRequest.outputHeight;
62       this.audioMimeType = transformationRequest.audioMimeType;
63       this.videoMimeType = transformationRequest.videoMimeType;
64       this.enableRequestSdrToneMapping = transformationRequest.enableRequestSdrToneMapping;
65       this.enableHdrEditing = transformationRequest.enableHdrEditing;
66     }
67 
68     /**
69      * Sets whether the input should be flattened for media containing slow motion markers.
70      *
71      * <p>The transformed output is obtained by removing the slow motion metadata and by actually
72      * slowing down the parts of the video and audio streams defined in this metadata. The default
73      * value for {@code flattenForSlowMotion} is {@code false}.
74      *
75      * <p>Only Samsung Extension Format (SEF) slow motion metadata type is supported. The
76      * transformation has no effect if the input does not contain this metadata type.
77      *
78      * <p>For SEF slow motion media, the following assumptions are made on the input:
79      *
80      * <ul>
81      *   <li>The input container format is (unfragmented) MP4.
82      *   <li>The input contains an AVC video elementary stream with temporal SVC.
83      *   <li>The recording frame rate of the video is 120 or 240 fps.
84      * </ul>
85      *
86      * <p>If specifying a {@link MediaSource.Factory} using {@link
87      * Transformer.Builder#setMediaSourceFactory(MediaSource.Factory)}, make sure that {@link
88      * Mp4Extractor#FLAG_READ_SEF_DATA} is set on the {@link Mp4Extractor} used. Otherwise, the slow
89      * motion metadata will be ignored and the input won't be flattened.
90      *
91      * @param flattenForSlowMotion Whether to flatten for slow motion.
92      * @return This builder.
93      */
setFlattenForSlowMotion(boolean flattenForSlowMotion)94     public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
95       this.flattenForSlowMotion = flattenForSlowMotion;
96       return this;
97     }
98 
99     /**
100      * Sets the x and y axis scaling factors to apply to each frame's width and height, stretching
101      * the video along these axes appropriately.
102      *
103      * <p>The values default to 1, which corresponds to not scaling along both axes.
104      *
105      * @param scaleX The multiplier by which the frame will scale horizontally, along the x-axis.
106      * @param scaleY The multiplier by which the frame will scale vertically, along the y-axis.
107      * @return This builder.
108      */
setScale(float scaleX, float scaleY)109     public Builder setScale(float scaleX, float scaleY) {
110       this.scaleX = scaleX;
111       this.scaleY = scaleY;
112       return this;
113     }
114 
115     /**
116      * Sets the rotation, in degrees, counterclockwise, to apply to each frame, automatically
117      * adjusting the frame's width and height to preserve all input pixels.
118      *
119      * <p>The default value, 0, corresponds to not applying any rotation.
120      *
121      * @param rotationDegrees The counterclockwise rotation, in degrees.
122      * @return This builder.
123      */
setRotationDegrees(float rotationDegrees)124     public Builder setRotationDegrees(float rotationDegrees) {
125       this.rotationDegrees = rotationDegrees;
126       return this;
127     }
128 
129     /**
130      * Sets the output resolution using the output height.
131      *
132      * <p>The default value {@link C#LENGTH_UNSET} corresponds to using the same height as the
133      * input. Output width of the displayed video will scale to preserve the video's aspect ratio
134      * after other transformations.
135      *
136      * <p>For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480).
137      *
138      * @param outputHeight The output height of the displayed video, in pixels.
139      * @return This builder.
140      */
setResolution(int outputHeight)141     public Builder setResolution(int outputHeight) {
142       this.outputHeight = outputHeight;
143       return this;
144     }
145 
146     /**
147      * Sets the video MIME type of the output.
148      *
149      * <p>The default value is {@code null} which corresponds to using the same MIME type as the
150      * input. Supported MIME types are:
151      *
152      * <ul>
153      *   <li>{@link MimeTypes#VIDEO_H263}
154      *   <li>{@link MimeTypes#VIDEO_H264}
155      *   <li>{@link MimeTypes#VIDEO_H265} from API level 24
156      *   <li>{@link MimeTypes#VIDEO_MP4V}
157      * </ul>
158      *
159      * @param videoMimeType The MIME type of the video samples in the output.
160      * @return This builder.
161      * @throws IllegalArgumentException If the {@code videoMimeType} is non-null but not a video
162      *     {@linkplain MimeTypes MIME type}.
163      */
setVideoMimeType(@ullable String videoMimeType)164     public Builder setVideoMimeType(@Nullable String videoMimeType) {
165       checkArgument(
166           videoMimeType == null || MimeTypes.isVideo(videoMimeType),
167           "Not a video MIME type: " + videoMimeType);
168       this.videoMimeType = videoMimeType;
169       return this;
170     }
171 
172     /**
173      * Sets the audio MIME type of the output.
174      *
175      * <p>The default value is {@code null} which corresponds to using the same MIME type as the
176      * input. Supported MIME types are:
177      *
178      * <ul>
179      *   <li>{@link MimeTypes#AUDIO_AAC}
180      *   <li>{@link MimeTypes#AUDIO_AMR_NB}
181      *   <li>{@link MimeTypes#AUDIO_AMR_WB}
182      * </ul>
183      *
184      * @param audioMimeType The MIME type of the audio samples in the output.
185      * @return This builder.
186      * @throws IllegalArgumentException If the {@code audioMimeType} is non-null but not an audio
187      *     {@linkplain MimeTypes MIME type}.
188      */
setAudioMimeType(@ullable String audioMimeType)189     public Builder setAudioMimeType(@Nullable String audioMimeType) {
190       checkArgument(
191           audioMimeType == null || MimeTypes.isAudio(audioMimeType),
192           "Not an audio MIME type: " + audioMimeType);
193       this.audioMimeType = audioMimeType;
194       return this;
195     }
196 
197     /**
198      * Sets whether to request tone-mapping to standard dynamic range (SDR). If enabled and
199      * supported, high dynamic range (HDR) input will be tone-mapped into an SDR opto-electrical
200      * transfer function before processing.
201      *
202      * <p>The setting has no effect if the input is already in SDR, or if tone-mapping is not
203      * supported. Currently tone-mapping is only guaranteed to be supported from Android T onwards.
204      *
205      * @param enableRequestSdrToneMapping Whether to request tone-mapping down to SDR.
206      * @return This builder.
207      */
setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMapping)208     public Builder setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMapping) {
209       this.enableRequestSdrToneMapping = enableRequestSdrToneMapping;
210       return this;
211     }
212 
213     /**
214      * Sets whether to attempt to process any input video stream as a high dynamic range (HDR)
215      * signal.
216      *
217      * <p>This method is experimental, and will be renamed or removed in a future release. The HDR
218      * editing feature is under development and is intended for developing/testing HDR processing
219      * and encoding support. HDR editing can't be enabled at the same time as {@linkplain
220      * #setEnableRequestSdrToneMapping(boolean) SDR tone-mapping}.
221      *
222      * @param enableHdrEditing Whether to attempt to process any input video stream as a high
223      *     dynamic range (HDR) signal.
224      * @return This builder.
225      */
experimental_setEnableHdrEditing(boolean enableHdrEditing)226     public Builder experimental_setEnableHdrEditing(boolean enableHdrEditing) {
227       this.enableHdrEditing = enableHdrEditing;
228       return this;
229     }
230 
231     /** Builds a {@link TransformationRequest} instance. */
build()232     public TransformationRequest build() {
233       return new TransformationRequest(
234           flattenForSlowMotion,
235           scaleX,
236           scaleY,
237           rotationDegrees,
238           outputHeight,
239           audioMimeType,
240           videoMimeType,
241           enableRequestSdrToneMapping,
242           enableHdrEditing);
243     }
244   }
245 
246   /**
247    * Whether the input should be flattened for media containing slow motion markers.
248    *
249    * @see Builder#setFlattenForSlowMotion(boolean)
250    */
251   public final boolean flattenForSlowMotion;
252   /**
253    * The requested scale factor, on the x-axis, of the output video, or 1 if inferred from the
254    * input.
255    *
256    * @see Builder#setScale(float, float)
257    */
258   public final float scaleX;
259   /**
260    * The requested scale factor, on the y-axis, of the output video, or 1 if inferred from the
261    * input.
262    *
263    * @see Builder#setScale(float, float)
264    */
265   public final float scaleY;
266   /**
267    * The requested rotation, in degrees, of the output video, or 0 if inferred from the input.
268    *
269    * @see Builder#setRotationDegrees(float)
270    */
271   public final float rotationDegrees;
272   /**
273    * The requested height of the output video, or {@link C#LENGTH_UNSET} if inferred from the input.
274    *
275    * @see Builder#setResolution(int)
276    */
277   public final int outputHeight;
278   /**
279    * The requested output audio sample {@linkplain MimeTypes MIME type}, or {@code null} if inferred
280    * from the input.
281    *
282    * @see Builder#setAudioMimeType(String)
283    */
284   @Nullable public final String audioMimeType;
285   /**
286    * The requested output video sample {@linkplain MimeTypes MIME type}, or {@code null} if inferred
287    * from the input.
288    *
289    * @see Builder#setVideoMimeType(String)
290    */
291   @Nullable public final String videoMimeType;
292   /** Whether to request tone-mapping to standard dynamic range (SDR). */
293   public final boolean enableRequestSdrToneMapping;
294 
295   /**
296    * Whether to attempt to process any input video stream as a high dynamic range (HDR) signal.
297    *
298    * @see Builder#experimental_setEnableHdrEditing(boolean)
299    */
300   public final boolean enableHdrEditing;
301 
TransformationRequest( boolean flattenForSlowMotion, float scaleX, float scaleY, float rotationDegrees, int outputHeight, @Nullable String audioMimeType, @Nullable String videoMimeType, boolean enableRequestSdrToneMapping, boolean enableHdrEditing)302   private TransformationRequest(
303       boolean flattenForSlowMotion,
304       float scaleX,
305       float scaleY,
306       float rotationDegrees,
307       int outputHeight,
308       @Nullable String audioMimeType,
309       @Nullable String videoMimeType,
310       boolean enableRequestSdrToneMapping,
311       boolean enableHdrEditing) {
312     checkArgument(!enableHdrEditing || !enableRequestSdrToneMapping);
313     this.flattenForSlowMotion = flattenForSlowMotion;
314     this.scaleX = scaleX;
315     this.scaleY = scaleY;
316     this.rotationDegrees = rotationDegrees;
317     this.outputHeight = outputHeight;
318     this.audioMimeType = audioMimeType;
319     this.videoMimeType = videoMimeType;
320     this.enableRequestSdrToneMapping = enableRequestSdrToneMapping;
321     this.enableHdrEditing = enableHdrEditing;
322   }
323 
324   @Override
equals(@ullable Object o)325   public boolean equals(@Nullable Object o) {
326     if (this == o) {
327       return true;
328     }
329     if (!(o instanceof TransformationRequest)) {
330       return false;
331     }
332     TransformationRequest that = (TransformationRequest) o;
333     return flattenForSlowMotion == that.flattenForSlowMotion
334         && scaleX == that.scaleX
335         && scaleY == that.scaleY
336         && rotationDegrees == that.rotationDegrees
337         && outputHeight == that.outputHeight
338         && Util.areEqual(audioMimeType, that.audioMimeType)
339         && Util.areEqual(videoMimeType, that.videoMimeType)
340         && enableRequestSdrToneMapping == that.enableRequestSdrToneMapping
341         && enableHdrEditing == that.enableHdrEditing;
342   }
343 
344   @Override
hashCode()345   public int hashCode() {
346     int result = (flattenForSlowMotion ? 1 : 0);
347     result = 31 * result + Float.floatToIntBits(scaleX);
348     result = 31 * result + Float.floatToIntBits(scaleY);
349     result = 31 * result + Float.floatToIntBits(rotationDegrees);
350     result = 31 * result + outputHeight;
351     result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0);
352     result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0);
353     result = 31 * result + (enableRequestSdrToneMapping ? 1 : 0);
354     result = 31 * result + (enableHdrEditing ? 1 : 0);
355     return result;
356   }
357 
358   /**
359    * Returns a new {@link TransformationRequest.Builder} initialized with the values of this
360    * instance.
361    */
buildUpon()362   public Builder buildUpon() {
363     return new Builder(this);
364   }
365 }
366