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