• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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