diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java index 8fc7480ad0..471a5d9ed0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java @@ -54,7 +54,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { @Nullable private EncoderSelector encoderSelector; @Nullable private VideoEncoderSettings requestedVideoEncoderSettings; private boolean enableFallback; - private boolean automaticQualityAdjustment; /** Creates a new {@link Builder}. */ public Builder(Context context) { @@ -78,15 +77,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { *

Values in {@code requestedVideoEncoderSettings} may be ignored to improve encoding quality * and/or reduce failures. * - *

- * - *

Consider implementing {@link Codec.EncoderFactory} if such adjustments are unwanted. + *

{@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level} are ignored + * for {@link MimeTypes#VIDEO_H264}. Consider implementing {@link Codec.EncoderFactory} if such + * adjustments are unwanted. * *

{@code requestedVideoEncoderSettings} should be handled with care because there is no * fallback support for it. For example, using incompatible {@link VideoEncoderSettings#profile} @@ -117,19 +110,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { return this; } - /** - * Sets whether to use automatic quality adjustment. - * - *

With this enabled, encoders are configured to output high quality video. - * - *

Default value is {@code false}. - */ - public Builder setAutomaticQualityAdjustment(boolean automaticQualityAdjustment) { - this.automaticQualityAdjustment = automaticQualityAdjustment; - return this; - } - /** Creates an instance of {@link DefaultEncoderFactory}, using defaults if values are unset. */ + @SuppressWarnings("deprecation") public DefaultEncoderFactory build() { if (encoderSelector == null) { encoderSelector = EncoderSelector.DEFAULT; @@ -138,11 +120,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { requestedVideoEncoderSettings = VideoEncoderSettings.DEFAULT; } return new DefaultEncoderFactory( - context, - encoderSelector, - requestedVideoEncoderSettings, - enableFallback, - automaticQualityAdjustment); + context, encoderSelector, requestedVideoEncoderSettings, enableFallback); } } @@ -150,7 +128,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { private final EncoderSelector videoEncoderSelector; private final VideoEncoderSettings requestedVideoEncoderSettings; private final boolean enableFallback; - private final boolean automaticQualityAdjustment; /** * @deprecated Use {@link Builder} instead. @@ -180,25 +157,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { EncoderSelector videoEncoderSelector, VideoEncoderSettings requestedVideoEncoderSettings, boolean enableFallback) { - this( - context, - videoEncoderSelector, - requestedVideoEncoderSettings, - enableFallback, - /* automaticQualityAdjustment= */ false); - } - - private DefaultEncoderFactory( - Context context, - EncoderSelector videoEncoderSelector, - VideoEncoderSettings requestedVideoEncoderSettings, - boolean enableFallback, - boolean automaticQualityAdjustment) { this.context = context; this.videoEncoderSelector = videoEncoderSelector; this.requestedVideoEncoderSettings = requestedVideoEncoderSettings; this.enableFallback = enableFallback; - this.automaticQualityAdjustment = automaticQualityAdjustment; } @Override @@ -274,11 +236,11 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, round(format.frameRate)); int bitrate; - if (automaticQualityAdjustment) { + + if (supportedVideoEncoderSettings.enableHighQualityTargeting) { bitrate = new DeviceMappedEncoderBitrateProvider() - .getBitrate( - encoderInfo.getName(), format.width, format.height, round(format.frameRate)); + .getBitrate(encoderInfo.getName(), format.width, format.height, format.frameRate); } else if (supportedVideoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE) { bitrate = supportedVideoEncoderSettings.bitrate; } else { @@ -365,46 +327,54 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { return null; } - List encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType); - if (encodersForMimeType.isEmpty()) { + ImmutableList filteredEncoderInfos = + encoderSelector.selectEncoderInfos(mimeType); + if (filteredEncoderInfos.isEmpty()) { return null; } + if (!enableFallback) { return new VideoEncoderQueryResult( - encodersForMimeType.get(0), requestedFormat, videoEncoderSettings); + filteredEncoderInfos.get(0), requestedFormat, videoEncoderSettings); } - ImmutableList filteredEncoders = + filteredEncoderInfos = filterEncodersByResolution( - encodersForMimeType, mimeType, requestedFormat.width, requestedFormat.height); - if (filteredEncoders.isEmpty()) { + filteredEncoderInfos, mimeType, requestedFormat.width, requestedFormat.height); + if (filteredEncoderInfos.isEmpty()) { return null; } // The supported resolution is the same for all remaining encoders. Size finalResolution = checkNotNull( EncoderUtil.getSupportedResolution( - filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height)); + filteredEncoderInfos.get(0), + mimeType, + requestedFormat.width, + requestedFormat.height)); int requestedBitrate = videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE ? videoEncoderSettings.bitrate : getSuggestedBitrate( finalResolution.getWidth(), finalResolution.getHeight(), requestedFormat.frameRate); - filteredEncoders = filterEncodersByBitrate(filteredEncoders, mimeType, requestedBitrate); - if (filteredEncoders.isEmpty()) { + + filteredEncoderInfos = + filterEncodersByBitrate(filteredEncoderInfos, mimeType, requestedBitrate); + if (filteredEncoderInfos.isEmpty()) { return null; } - filteredEncoders = - filterEncodersByBitrateMode(filteredEncoders, mimeType, videoEncoderSettings.bitrateMode); - if (filteredEncoders.isEmpty()) { + filteredEncoderInfos = + filterEncodersByBitrateMode( + filteredEncoderInfos, mimeType, videoEncoderSettings.bitrateMode); + if (filteredEncoderInfos.isEmpty()) { return null; } - MediaCodecInfo pickedEncoder = filteredEncoders.get(0); + MediaCodecInfo pickedEncoderInfo = filteredEncoderInfos.get(0); int closestSupportedBitrate = - EncoderUtil.getSupportedBitrateRange(pickedEncoder, mimeType).clamp(requestedBitrate); + EncoderUtil.getSupportedBitrateRange(pickedEncoderInfo, mimeType).clamp(requestedBitrate); VideoEncoderSettings.Builder supportedEncodingSettingBuilder = videoEncoderSettings.buildUpon().setBitrate(closestSupportedBitrate); @@ -412,7 +382,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { || videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE || videoEncoderSettings.level > EncoderUtil.findHighestSupportedEncodingLevel( - pickedEncoder, mimeType, videoEncoderSettings.profile)) { + pickedEncoderInfo, mimeType, videoEncoderSettings.profile)) { supportedEncodingSettingBuilder.setEncodingProfileLevel( VideoEncoderSettings.NO_VALUE, VideoEncoderSettings.NO_VALUE); } @@ -426,7 +396,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { .setAverageBitrate(closestSupportedBitrate) .build(); return new VideoEncoderQueryResult( - pickedEncoder, supportedEncoderFormat, supportedEncodingSettingBuilder.build()); + pickedEncoderInfo, supportedEncoderFormat, supportedEncodingSettingBuilder.build()); } /** Returns a list of encoders that support the requested resolution most closely. */ @@ -640,7 +610,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { * */ private static int getSuggestedBitrate(int width, int height, float frameRate) { - // TODO(b/210591626) Implement bitrate estimation. + // TODO(b/210591626) Refactor into a BitrateProvider. // Assume medium motion factor. // 1080p60 -> 16.6Mbps, 720p30 -> 3.7Mbps. return (int) (width * height * frameRate * 0.07 * 2); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DeviceMappedEncoderBitrateProvider.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DeviceMappedEncoderBitrateProvider.java index 381b3434f1..ff064e4e90 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DeviceMappedEncoderBitrateProvider.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DeviceMappedEncoderBitrateProvider.java @@ -23,10 +23,14 @@ import com.google.android.exoplayer2.util.Util; public class DeviceMappedEncoderBitrateProvider implements EncoderBitrateProvider { @Override - public int getBitrate(String encoderName, int width, int height, int frameRate) { + public int getBitrate(String encoderName, int width, int height, float frameRate) { double bitrateMultiplier = getBitrateMultiplierFromMapping( - encoderName, Util.SDK_INT, Build.MODEL, "" + width + "x" + height, frameRate); + encoderName, + Util.SDK_INT, + Build.MODEL, + "" + width + "x" + height, + Math.round(frameRate)); return (int) (width * height * frameRate * bitrateMultiplier); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderBitrateProvider.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderBitrateProvider.java index 3aab2480c3..05756a1398 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderBitrateProvider.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderBitrateProvider.java @@ -24,11 +24,11 @@ public interface EncoderBitrateProvider { /** * Returns a recommended bitrate that the encoder should target. * - * @param encoderName The name of the encoder, see {@link MediaCodecInfo#getName()} + * @param encoderName The name of the encoder, see {@link MediaCodecInfo#getName()}. * @param width The output width of the video after encoding. * @param height The output height of the video after encoding. * @param frameRate The expected output frame rate of the video after encoding. * @return The bitrate the encoder should target. */ - int getBitrate(String encoderName, int width, int height, int frameRate); + int getBitrate(String encoderName, int width, int height, float frameRate); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderSelector.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderSelector.java index 8e720ff882..0527652e07 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderSelector.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderSelector.java @@ -19,7 +19,7 @@ package com.google.android.exoplayer2.transformer; import android.media.MediaCodec; import android.media.MediaCodecInfo; import com.google.android.exoplayer2.util.MimeTypes; -import java.util.List; +import com.google.common.collect.ImmutableList; /** Selector of {@link MediaCodec} encoder instances. */ public interface EncoderSelector { @@ -35,8 +35,8 @@ public interface EncoderSelector { * order. * * @param mimeType The {@linkplain MimeTypes MIME type} for which an encoder is required. - * @return An unmodifiable list of {@linkplain MediaCodecInfo encoders} that support the {@code + * @return An immutable list of {@linkplain MediaCodecInfo encoders} that support the {@code * mimeType}. The list may be empty. */ - List selectEncoderInfos(String mimeType); + ImmutableList selectEncoderInfos(String mimeType); } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java index 7a8d615aed..efa7db26b7 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java @@ -16,7 +16,10 @@ package com.google.android.exoplayer2.transformer; +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR; +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; @@ -46,14 +49,11 @@ public final class VideoEncoderSettings { public static final VideoEncoderSettings DEFAULT = new Builder().build(); /** - * The allowed values for {@code bitrateMode}, one of + * The allowed values for {@code bitrateMode}. * *

*/ @SuppressLint("InlinedApi") @@ -61,10 +61,8 @@ public final class VideoEncoderSettings { @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({ - MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ, - MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR, - MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR, - MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR_FD + BITRATE_MODE_VBR, + BITRATE_MODE_CBR, }) public @interface BitrateMode {} @@ -78,11 +76,12 @@ public final class VideoEncoderSettings { private float iFrameIntervalSeconds; private int operatingRate; private int priority; + private boolean enableHighQualityTargeting; /** Creates a new instance. */ public Builder() { this.bitrate = NO_VALUE; - this.bitrateMode = MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; + this.bitrateMode = BITRATE_MODE_VBR; this.profile = NO_VALUE; this.level = NO_VALUE; this.colorProfile = DEFAULT_COLOR_PROFILE; @@ -100,11 +99,14 @@ public final class VideoEncoderSettings { this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds; this.operatingRate = videoEncoderSettings.operatingRate; this.priority = videoEncoderSettings.priority; + this.enableHighQualityTargeting = videoEncoderSettings.enableHighQualityTargeting; } /** * Sets {@link VideoEncoderSettings#bitrate}. The default value is {@link #NO_VALUE}. * + *

Can not be set if enabling {@link #setEnableHighQualityTargeting(boolean)}. + * * @param bitrate The {@link VideoEncoderSettings#bitrate}. * @return This builder. */ @@ -117,16 +119,13 @@ public final class VideoEncoderSettings { * Sets {@link VideoEncoderSettings#bitrateMode}. The default value is {@code * MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR}. * - *

Only {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR} and {@link - * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR} are allowed. + *

Value must be in {@link BitrateMode}. * * @param bitrateMode The {@link VideoEncoderSettings#bitrateMode}. * @return This builder. */ public Builder setBitrateMode(@BitrateMode int bitrateMode) { - checkArgument( - bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR - || bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); + checkArgument(bitrateMode == BITRATE_MODE_VBR || bitrateMode == BITRATE_MODE_CBR); this.bitrateMode = bitrateMode; return this; } @@ -192,8 +191,28 @@ public final class VideoEncoderSettings { return this; } + /** + * Sets whether to enable automatic adjustment of the bitrate to target a high quality encoding. + * + *

Default value is {@code false}. + * + *

Requires {@link android.media.MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}. + * + *

Can not be enabled alongside setting a custom bitrate with {@link #setBitrate(int)}. + */ + public Builder setEnableHighQualityTargeting(boolean enableHighQualityTargeting) { + this.enableHighQualityTargeting = enableHighQualityTargeting; + return this; + } + /** Builds the instance. */ public VideoEncoderSettings build() { + checkState( + !enableHighQualityTargeting || bitrate == NO_VALUE, + "Bitrate can not be set if enabling high quality targeting."); + checkState( + !enableHighQualityTargeting || bitrateMode == BITRATE_MODE_VBR, + "Bitrate mode must be VBR if enabling high quality targeting."); return new VideoEncoderSettings( bitrate, bitrateMode, @@ -202,13 +221,14 @@ public final class VideoEncoderSettings { colorProfile, iFrameIntervalSeconds, operatingRate, - priority); + priority, + enableHighQualityTargeting); } } /** The encoding bitrate. */ public final int bitrate; - /** One of {@linkplain BitrateMode the allowed modes}. */ + /** One of {@linkplain BitrateMode}. */ public final @BitrateMode int bitrateMode; /** The encoding profile. */ public final int profile; @@ -222,6 +242,8 @@ public final class VideoEncoderSettings { public final int operatingRate; /** The encoder {@link MediaFormat#KEY_PRIORITY priority}. */ public final int priority; + /** Whether the encoder should automatically set the bitrate to target a high quality encoding. */ + public final boolean enableHighQualityTargeting; private VideoEncoderSettings( int bitrate, @@ -231,7 +253,8 @@ public final class VideoEncoderSettings { int colorProfile, float iFrameIntervalSeconds, int operatingRate, - int priority) { + int priority, + boolean enableHighQualityTargeting) { this.bitrate = bitrate; this.bitrateMode = bitrateMode; this.profile = profile; @@ -240,6 +263,7 @@ public final class VideoEncoderSettings { this.iFrameIntervalSeconds = iFrameIntervalSeconds; this.operatingRate = operatingRate; this.priority = priority; + this.enableHighQualityTargeting = enableHighQualityTargeting; } /** @@ -265,7 +289,8 @@ public final class VideoEncoderSettings { && colorProfile == that.colorProfile && iFrameIntervalSeconds == that.iFrameIntervalSeconds && operatingRate == that.operatingRate - && priority == that.priority; + && priority == that.priority + && enableHighQualityTargeting == that.enableHighQualityTargeting; } @Override @@ -279,6 +304,7 @@ public final class VideoEncoderSettings { result = 31 * result + Float.floatToIntBits(iFrameIntervalSeconds); result = 31 * result + operatingRate; result = 31 * result + priority; + result = 31 * result + (enableHighQualityTargeting ? 1 : 0); return result; } }