diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java index d6ba06c05c..b609b1c023 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java @@ -56,7 +56,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) { @@ -80,15 +79,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} @@ -119,19 +112,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; @@ -140,11 +122,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { requestedVideoEncoderSettings = VideoEncoderSettings.DEFAULT; } return new DefaultEncoderFactory( - context, - encoderSelector, - requestedVideoEncoderSettings, - enableFallback, - automaticQualityAdjustment); + context, encoderSelector, requestedVideoEncoderSettings, enableFallback); } } @@ -152,7 +130,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. @@ -182,25 +159,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 @@ -276,11 +238,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 { @@ -367,46 +329,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); @@ -414,7 +384,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); } @@ -428,7 +398,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. */ @@ -642,7 +612,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/libraries/transformer/src/main/java/androidx/media3/transformer/DeviceMappedEncoderBitrateProvider.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DeviceMappedEncoderBitrateProvider.java index d40aa86e63..ec38576bff 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DeviceMappedEncoderBitrateProvider.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DeviceMappedEncoderBitrateProvider.java @@ -25,10 +25,14 @@ import androidx.media3.common.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/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderBitrateProvider.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderBitrateProvider.java index 02205aeb3b..59eed209c4 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderBitrateProvider.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderBitrateProvider.java @@ -26,11 +26,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/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderSelector.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderSelector.java index c68719a1a4..0f1c7b445d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderSelector.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderSelector.java @@ -20,7 +20,7 @@ import android.media.MediaCodec; import android.media.MediaCodecInfo; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; -import java.util.List; +import com.google.common.collect.ImmutableList; /** Selector of {@link MediaCodec} encoder instances. */ @UnstableApi @@ -37,8 +37,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/libraries/transformer/src/main/java/androidx/media3/transformer/VideoEncoderSettings.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoEncoderSettings.java index 90db319e17..7c043878fd 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoEncoderSettings.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoEncoderSettings.java @@ -16,7 +16,10 @@ package androidx.media3.transformer; +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR; +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Assertions.checkState; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; @@ -48,14 +51,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") @@ -63,10 +63,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 {} @@ -80,11 +78,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; @@ -102,11 +101,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. */ @@ -119,16 +121,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; } @@ -194,8 +193,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, @@ -204,13 +223,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; @@ -224,6 +244,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, @@ -233,7 +255,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; @@ -242,6 +265,7 @@ public final class VideoEncoderSettings { this.iFrameIntervalSeconds = iFrameIntervalSeconds; this.operatingRate = operatingRate; this.priority = priority; + this.enableHighQualityTargeting = enableHighQualityTargeting; } /** @@ -267,7 +291,8 @@ public final class VideoEncoderSettings { && colorProfile == that.colorProfile && iFrameIntervalSeconds == that.iFrameIntervalSeconds && operatingRate == that.operatingRate - && priority == that.priority; + && priority == that.priority + && enableHighQualityTargeting == that.enableHighQualityTargeting; } @Override @@ -281,6 +306,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; } }