1 /* 2 * Copyright 2022 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 import static com.google.android.exoplayer2.util.Assertions.checkNotNull; 21 import static com.google.android.exoplayer2.util.Assertions.checkState; 22 import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; 23 import static com.google.android.exoplayer2.util.Util.SDK_INT; 24 import static java.lang.Math.abs; 25 26 import android.media.MediaCodecInfo; 27 import android.media.MediaFormat; 28 import android.util.Pair; 29 import android.util.Size; 30 import androidx.annotation.Nullable; 31 import com.google.android.exoplayer2.Format; 32 import com.google.android.exoplayer2.util.Log; 33 import com.google.android.exoplayer2.util.MimeTypes; 34 import com.google.android.exoplayer2.util.Util; 35 import com.google.common.collect.ImmutableList; 36 import java.util.ArrayList; 37 import java.util.List; 38 import org.checkerframework.checker.nullness.qual.RequiresNonNull; 39 40 /** A default implementation of {@link Codec.EncoderFactory}. */ 41 // TODO(b/224949986) Split audio and video encoder factory. 42 public final class DefaultEncoderFactory implements Codec.EncoderFactory { 43 private static final int DEFAULT_FRAME_RATE = 30; 44 private static final String TAG = "DefaultEncoderFactory"; 45 46 private final EncoderSelector videoEncoderSelector; 47 private final VideoEncoderSettings requestedVideoEncoderSettings; 48 private final boolean enableFallback; 49 50 /** 51 * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a 52 * default {@link VideoEncoderSettings}, and with format fallback enabled. 53 */ DefaultEncoderFactory()54 public DefaultEncoderFactory() { 55 this(EncoderSelector.DEFAULT, /* enableFallback= */ true); 56 } 57 58 /** Creates a new instance using a default {@link VideoEncoderSettings}. */ DefaultEncoderFactory(EncoderSelector videoEncoderSelector, boolean enableFallback)59 public DefaultEncoderFactory(EncoderSelector videoEncoderSelector, boolean enableFallback) { 60 this(videoEncoderSelector, VideoEncoderSettings.DEFAULT, enableFallback); 61 } 62 63 /** 64 * Creates a new instance. 65 * 66 * <p>With format fallback enabled, when the requested {@link Format} is not supported, {@code 67 * DefaultEncoderFactory} finds a format that is supported by the device and configures the {@link 68 * Codec} with it. The fallback process may change the requested {@link Format#sampleMimeType MIME 69 * type}, resolution, {@link Format#bitrate bitrate}, {@link Format#codecs profile/level} etc. 70 * 71 * <p>Values in {@code requestedVideoEncoderSettings} could be adjusted to improve encoding 72 * quality and/or reduce failures. Specifically, {@link VideoEncoderSettings#profile} and {@link 73 * VideoEncoderSettings#level} are ignored for {@link MimeTypes#VIDEO_H264}. Consider implementing 74 * {@link Codec.EncoderFactory} if such adjustments are unwanted. 75 * 76 * <p>{@code requestedVideoEncoderSettings} should be handled with care because there is no 77 * fallback support for it. For example, using incompatible {@link VideoEncoderSettings#profile} 78 * and {@link VideoEncoderSettings#level} can cause codec configuration failure. Setting an 79 * unsupported {@link VideoEncoderSettings#bitrateMode} may cause encoder instantiation failure. 80 * 81 * @param videoEncoderSelector The {@link EncoderSelector}. 82 * @param requestedVideoEncoderSettings The {@link VideoEncoderSettings}. 83 * @param enableFallback Whether to enable fallback. 84 */ DefaultEncoderFactory( EncoderSelector videoEncoderSelector, VideoEncoderSettings requestedVideoEncoderSettings, boolean enableFallback)85 public DefaultEncoderFactory( 86 EncoderSelector videoEncoderSelector, 87 VideoEncoderSettings requestedVideoEncoderSettings, 88 boolean enableFallback) { 89 this.videoEncoderSelector = videoEncoderSelector; 90 this.requestedVideoEncoderSettings = requestedVideoEncoderSettings; 91 this.enableFallback = enableFallback; 92 } 93 94 @Override createForAudioEncoding(Format format, List<String> allowedMimeTypes)95 public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes) 96 throws TransformationException { 97 // TODO(b/210591626) Add encoder selection for audio. 98 checkArgument(!allowedMimeTypes.isEmpty()); 99 checkNotNull(format.sampleMimeType); 100 if (!allowedMimeTypes.contains(format.sampleMimeType)) { 101 if (enableFallback) { 102 // TODO(b/210591626): Pick fallback MIME type using same strategy as for encoder 103 // capabilities limitations. 104 format = format.buildUpon().setSampleMimeType(allowedMimeTypes.get(0)).build(); 105 } else { 106 throw createTransformationException(format); 107 } 108 } 109 MediaFormat mediaFormat = 110 MediaFormat.createAudioFormat( 111 checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount); 112 mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate); 113 114 @Nullable 115 String mediaCodecName = EncoderUtil.findCodecForFormat(mediaFormat, /* isDecoder= */ false); 116 if (mediaCodecName == null) { 117 throw createTransformationException(format); 118 } 119 return new DefaultCodec( 120 format, mediaFormat, mediaCodecName, /* isDecoder= */ false, /* outputSurface= */ null); 121 } 122 123 @Override createForVideoEncoding(Format format, List<String> allowedMimeTypes)124 public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) 125 throws TransformationException { 126 if (format.frameRate == Format.NO_VALUE) { 127 format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build(); 128 } 129 checkArgument(format.width != Format.NO_VALUE); 130 checkArgument(format.height != Format.NO_VALUE); 131 // According to interface Javadoc, format.rotationDegrees should be 0. The video should always 132 // be encoded in landscape orientation. 133 checkArgument(format.height <= format.width); 134 checkArgument(format.rotationDegrees == 0); 135 checkNotNull(format.sampleMimeType); 136 checkArgument(!allowedMimeTypes.isEmpty()); 137 checkStateNotNull(videoEncoderSelector); 138 139 @Nullable 140 VideoEncoderQueryResult encoderAndClosestFormatSupport = 141 findEncoderWithClosestFormatSupport( 142 format, 143 requestedVideoEncoderSettings, 144 videoEncoderSelector, 145 allowedMimeTypes, 146 enableFallback); 147 148 if (encoderAndClosestFormatSupport == null) { 149 throw createTransformationException(format); 150 } 151 152 MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.encoder; 153 format = encoderAndClosestFormatSupport.supportedFormat; 154 VideoEncoderSettings supportedVideoEncoderSettings = 155 encoderAndClosestFormatSupport.supportedEncoderSettings; 156 157 String mimeType = checkNotNull(format.sampleMimeType); 158 MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, format.width, format.height); 159 mediaFormat.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); 160 mediaFormat.setInteger( 161 MediaFormat.KEY_BIT_RATE, 162 supportedVideoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE 163 ? supportedVideoEncoderSettings.bitrate 164 : getSuggestedBitrate(format.width, format.height, format.frameRate)); 165 166 mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, supportedVideoEncoderSettings.bitrateMode); 167 168 if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE 169 && supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE 170 && SDK_INT >= 23) { 171 // Set profile and level at the same time to maximize compatibility, or the encoder will pick 172 // the values. 173 mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile); 174 mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedVideoEncoderSettings.level); 175 } 176 177 if (mimeType.equals(MimeTypes.VIDEO_H264)) { 178 adjustMediaFormatForH264EncoderSettings(mediaFormat, encoderInfo); 179 } 180 181 mediaFormat.setInteger( 182 MediaFormat.KEY_COLOR_FORMAT, supportedVideoEncoderSettings.colorProfile); 183 mediaFormat.setFloat( 184 MediaFormat.KEY_I_FRAME_INTERVAL, supportedVideoEncoderSettings.iFrameIntervalSeconds); 185 186 if (Util.SDK_INT >= 23) { 187 // Setting operating rate and priority is supported from API 23. 188 if (supportedVideoEncoderSettings.operatingRate != VideoEncoderSettings.NO_VALUE) { 189 mediaFormat.setInteger( 190 MediaFormat.KEY_OPERATING_RATE, supportedVideoEncoderSettings.operatingRate); 191 } 192 if (supportedVideoEncoderSettings.priority != VideoEncoderSettings.NO_VALUE) { 193 mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, supportedVideoEncoderSettings.priority); 194 } 195 } 196 197 return new DefaultCodec( 198 format, 199 mediaFormat, 200 encoderInfo.getName(), 201 /* isDecoder= */ false, 202 /* outputSurface= */ null); 203 } 204 205 @Override videoNeedsEncoding()206 public boolean videoNeedsEncoding() { 207 return !requestedVideoEncoderSettings.equals(VideoEncoderSettings.DEFAULT); 208 } 209 210 /** 211 * Finds an {@linkplain MediaCodecInfo encoder} that supports the requested format most closely. 212 * 213 * <p>Returns the {@linkplain MediaCodecInfo encoder} and the supported {@link Format} in a {@link 214 * Pair}, or {@code null} if none is found. 215 */ 216 @RequiresNonNull("#1.sampleMimeType") 217 @Nullable findEncoderWithClosestFormatSupport( Format requestedFormat, VideoEncoderSettings videoEncoderSettings, EncoderSelector encoderSelector, List<String> allowedMimeTypes, boolean enableFallback)218 private static VideoEncoderQueryResult findEncoderWithClosestFormatSupport( 219 Format requestedFormat, 220 VideoEncoderSettings videoEncoderSettings, 221 EncoderSelector encoderSelector, 222 List<String> allowedMimeTypes, 223 boolean enableFallback) { 224 String requestedMimeType = requestedFormat.sampleMimeType; 225 @Nullable 226 String mimeType = findFallbackMimeType(encoderSelector, requestedMimeType, allowedMimeTypes); 227 if (mimeType == null || (!enableFallback && !requestedMimeType.equals(mimeType))) { 228 return null; 229 } 230 231 List<MediaCodecInfo> encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType); 232 if (encodersForMimeType.isEmpty()) { 233 return null; 234 } 235 if (!enableFallback) { 236 return new VideoEncoderQueryResult( 237 encodersForMimeType.get(0), requestedFormat, videoEncoderSettings); 238 } 239 240 ImmutableList<MediaCodecInfo> filteredEncoders = 241 filterEncodersByResolution( 242 encodersForMimeType, mimeType, requestedFormat.width, requestedFormat.height); 243 if (filteredEncoders.isEmpty()) { 244 return null; 245 } 246 // The supported resolution is the same for all remaining encoders. 247 Size finalResolution = 248 checkNotNull( 249 EncoderUtil.getSupportedResolution( 250 filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height)); 251 252 int requestedBitrate = 253 videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE 254 ? videoEncoderSettings.bitrate 255 : getSuggestedBitrate( 256 finalResolution.getWidth(), finalResolution.getHeight(), requestedFormat.frameRate); 257 filteredEncoders = filterEncodersByBitrate(filteredEncoders, mimeType, requestedBitrate); 258 if (filteredEncoders.isEmpty()) { 259 return null; 260 } 261 262 filteredEncoders = 263 filterEncodersByBitrateMode(filteredEncoders, mimeType, videoEncoderSettings.bitrateMode); 264 if (filteredEncoders.isEmpty()) { 265 return null; 266 } 267 268 MediaCodecInfo pickedEncoder = filteredEncoders.get(0); 269 int closestSupportedBitrate = 270 EncoderUtil.getSupportedBitrateRange(pickedEncoder, mimeType).clamp(requestedBitrate); 271 VideoEncoderSettings.Builder supportedEncodingSettingBuilder = 272 videoEncoderSettings.buildUpon().setBitrate(closestSupportedBitrate); 273 274 if (videoEncoderSettings.profile == VideoEncoderSettings.NO_VALUE 275 || videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE 276 || videoEncoderSettings.level 277 > EncoderUtil.findHighestSupportedEncodingLevel( 278 pickedEncoder, mimeType, videoEncoderSettings.profile)) { 279 supportedEncodingSettingBuilder.setEncodingProfileLevel( 280 VideoEncoderSettings.NO_VALUE, VideoEncoderSettings.NO_VALUE); 281 } 282 283 Format supportedEncoderFormat = 284 requestedFormat 285 .buildUpon() 286 .setSampleMimeType(mimeType) 287 .setWidth(finalResolution.getWidth()) 288 .setHeight(finalResolution.getHeight()) 289 .setAverageBitrate(closestSupportedBitrate) 290 .build(); 291 return new VideoEncoderQueryResult( 292 pickedEncoder, supportedEncoderFormat, supportedEncodingSettingBuilder.build()); 293 } 294 295 /** Returns a list of encoders that support the requested resolution most closely. */ filterEncodersByResolution( List<MediaCodecInfo> encoders, String mimeType, int requestedWidth, int requestedHeight)296 private static ImmutableList<MediaCodecInfo> filterEncodersByResolution( 297 List<MediaCodecInfo> encoders, String mimeType, int requestedWidth, int requestedHeight) { 298 return filterEncoders( 299 encoders, 300 /* cost= */ (encoderInfo) -> { 301 @Nullable 302 Size closestSupportedResolution = 303 EncoderUtil.getSupportedResolution( 304 encoderInfo, mimeType, requestedWidth, requestedHeight); 305 if (closestSupportedResolution == null) { 306 // Drops encoder. 307 return Integer.MAX_VALUE; 308 } 309 return abs( 310 requestedWidth * requestedHeight 311 - closestSupportedResolution.getWidth() * closestSupportedResolution.getHeight()); 312 }, 313 /* filterName= */ "resolution"); 314 } 315 316 /** Returns a list of encoders that support the requested bitrate most closely. */ filterEncodersByBitrate( List<MediaCodecInfo> encoders, String mimeType, int requestedBitrate)317 private static ImmutableList<MediaCodecInfo> filterEncodersByBitrate( 318 List<MediaCodecInfo> encoders, String mimeType, int requestedBitrate) { 319 return filterEncoders( 320 encoders, 321 /* cost= */ (encoderInfo) -> { 322 int achievableBitrate = 323 EncoderUtil.getSupportedBitrateRange(encoderInfo, mimeType).clamp(requestedBitrate); 324 return abs(achievableBitrate - requestedBitrate); 325 }, 326 /* filterName= */ "bitrate"); 327 } 328 329 /** Returns a list of encoders that support the requested bitrate mode. */ 330 private static ImmutableList<MediaCodecInfo> filterEncodersByBitrateMode( 331 List<MediaCodecInfo> encoders, String mimeType, int requestedBitrateMode) { 332 return filterEncoders( 333 encoders, 334 /* cost= */ (encoderInfo) -> 335 EncoderUtil.isBitrateModeSupported(encoderInfo, mimeType, requestedBitrateMode) 336 ? 0 337 : Integer.MAX_VALUE, // Drops encoder. 338 /* filterName= */ "bitrate mode"); 339 } 340 341 private static final class VideoEncoderQueryResult { 342 public final MediaCodecInfo encoder; 343 public final Format supportedFormat; 344 public final VideoEncoderSettings supportedEncoderSettings; 345 346 public VideoEncoderQueryResult( 347 MediaCodecInfo encoder, 348 Format supportedFormat, 349 VideoEncoderSettings supportedEncoderSettings) { 350 this.encoder = encoder; 351 this.supportedFormat = supportedFormat; 352 this.supportedEncoderSettings = supportedEncoderSettings; 353 } 354 } 355 356 /** 357 * Applying suggested profile/level settings from 358 * https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles 359 * 360 * <p>The adjustment is applied in-place to {@code mediaFormat}. 361 */ 362 private static void adjustMediaFormatForH264EncoderSettings( 363 MediaFormat mediaFormat, MediaCodecInfo encoderInfo) { 364 // TODO(b/210593256): Remove overriding profile/level (before API 29) after switching to in-app 365 // muxing. 366 String mimeType = MimeTypes.VIDEO_H264; 367 if (Util.SDK_INT >= 29) { 368 int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; 369 int supportedEncodingLevel = 370 EncoderUtil.findHighestSupportedEncodingLevel( 371 encoderInfo, mimeType, expectedEncodingProfile); 372 if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { 373 // Use the highest supported profile and use B-frames. 374 mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); 375 mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); 376 mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 1); 377 } 378 } else if (Util.SDK_INT >= 26) { 379 int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; 380 int supportedEncodingLevel = 381 EncoderUtil.findHighestSupportedEncodingLevel( 382 encoderInfo, mimeType, expectedEncodingProfile); 383 if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { 384 // Use the highest-supported profile, but disable the generation of B-frames using 385 // MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these 386 // system versions. 387 mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); 388 mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); 389 // TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after switching to 390 // in-app muxing. 391 mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1); 392 } 393 } else if (Util.SDK_INT >= 24) { 394 int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline; 395 int supportedLevel = 396 EncoderUtil.findHighestSupportedEncodingLevel( 397 encoderInfo, mimeType, expectedEncodingProfile); 398 checkState(supportedLevel != EncoderUtil.LEVEL_UNSET); 399 // Use the baseline profile for safest results, as encoding in baseline is required per 400 // https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding 401 mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); 402 mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel); 403 } else { 404 // For API levels below 24, setting profile and level can lead to failures in MediaCodec 405 // configuration. The encoder selects the profile/level when we don't set them. 406 mediaFormat.setString(MediaFormat.KEY_PROFILE, null); 407 mediaFormat.setString(MediaFormat.KEY_LEVEL, null); 408 } 409 } 410 411 private interface EncoderFallbackCost { 412 /** 413 * Returns a cost that represents the gap between the requested encoding parameter(s) and the 414 * {@linkplain MediaCodecInfo encoder}'s support for them. 415 * 416 * <p>The method must return {@link Integer#MAX_VALUE} when the {@linkplain MediaCodecInfo 417 * encoder} does not support the encoding parameters. 418 */ 419 int getParameterSupportGap(MediaCodecInfo encoderInfo); 420 } 421 422 /** 423 * Filters a list of {@linkplain MediaCodecInfo encoders} by a {@linkplain EncoderFallbackCost 424 * cost function}. 425 * 426 * @param encoders A list of {@linkplain MediaCodecInfo encoders}. 427 * @param cost A {@linkplain EncoderFallbackCost cost function}. 428 * @return A list of {@linkplain MediaCodecInfo encoders} with the lowest costs, empty if the 429 * costs of all encoders are {@link Integer#MAX_VALUE}. 430 */ 431 private static ImmutableList<MediaCodecInfo> filterEncoders( 432 List<MediaCodecInfo> encoders, EncoderFallbackCost cost, String filterName) { 433 List<MediaCodecInfo> filteredEncoders = new ArrayList<>(encoders.size()); 434 435 int minGap = Integer.MAX_VALUE; 436 for (int i = 0; i < encoders.size(); i++) { 437 MediaCodecInfo encoderInfo = encoders.get(i); 438 int gap = cost.getParameterSupportGap(encoderInfo); 439 if (gap == Integer.MAX_VALUE) { 440 continue; 441 } 442 443 if (gap < minGap) { 444 minGap = gap; 445 filteredEncoders.clear(); 446 filteredEncoders.add(encoderInfo); 447 } else if (gap == minGap) { 448 filteredEncoders.add(encoderInfo); 449 } 450 } 451 452 List<MediaCodecInfo> removedEncoders = new ArrayList<>(encoders); 453 removedEncoders.removeAll(filteredEncoders); 454 StringBuilder stringBuilder = 455 new StringBuilder("Encoders removed for ").append(filterName).append(":\n"); 456 for (int i = 0; i < removedEncoders.size(); i++) { 457 MediaCodecInfo encoderInfo = removedEncoders.get(i); 458 stringBuilder.append(Util.formatInvariant(" %s\n", encoderInfo.getName())); 459 } 460 Log.d(TAG, stringBuilder.toString()); 461 462 return ImmutableList.copyOf(filteredEncoders); 463 } 464 465 /** 466 * Finds a {@linkplain MimeTypes MIME type} that is supported by the encoder and in the {@code 467 * allowedMimeTypes}. 468 */ 469 @Nullable 470 private static String findFallbackMimeType( 471 EncoderSelector encoderSelector, String requestedMimeType, List<String> allowedMimeTypes) { 472 if (mimeTypeIsSupported(encoderSelector, requestedMimeType, allowedMimeTypes)) { 473 return requestedMimeType; 474 } else if (mimeTypeIsSupported(encoderSelector, MimeTypes.VIDEO_H265, allowedMimeTypes)) { 475 return MimeTypes.VIDEO_H265; 476 } else if (mimeTypeIsSupported(encoderSelector, MimeTypes.VIDEO_H264, allowedMimeTypes)) { 477 return MimeTypes.VIDEO_H264; 478 } else { 479 for (int i = 0; i < allowedMimeTypes.size(); i++) { 480 String allowedMimeType = allowedMimeTypes.get(i); 481 if (mimeTypeIsSupported(encoderSelector, allowedMimeType, allowedMimeTypes)) { 482 return allowedMimeType; 483 } 484 } 485 } 486 return null; 487 } 488 489 private static boolean mimeTypeIsSupported( 490 EncoderSelector encoderSelector, String mimeType, List<String> allowedMimeTypes) { 491 return !encoderSelector.selectEncoderInfos(mimeType).isEmpty() 492 && allowedMimeTypes.contains(mimeType); 493 } 494 495 /** Computes the video bit rate using the Kush Gauge. */ 496 private static int getSuggestedBitrate(int width, int height, float frameRate) { 497 // TODO(b/210591626) Implement bitrate estimation. 498 // 1080p30 -> 6.2Mbps, 720p30 -> 2.7Mbps. 499 return (int) (width * height * frameRate * 0.1); 500 } 501 502 @RequiresNonNull("#1.sampleMimeType") 503 private static TransformationException createTransformationException(Format format) { 504 return TransformationException.createForCodec( 505 new IllegalArgumentException("The requested encoding format is not supported."), 506 MimeTypes.isVideo(format.sampleMimeType), 507 /* isDecoder= */ false, 508 format, 509 /* mediaCodecName= */ null, 510 TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); 511 } 512 } 513