Move Encoder quality API to VideoEncoderSettings.

Some other minor nits and adjustments to the API logic.

PiperOrigin-RevId: 459490431
This commit is contained in:
samrobinson 2022-07-07 12:54:02 +00:00 committed by Rohit Singh
parent cb87b7432f
commit 91f1777741
5 changed files with 88 additions and 88 deletions

View File

@ -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 {
* <p>Values in {@code requestedVideoEncoderSettings} may be ignored to improve encoding quality
* and/or reduce failures.
*
* <ul>
* <li>{@link VideoEncoderSettings#bitrate} is ignored if {@link
* Builder#setAutomaticQualityAdjustment(boolean)} is enabled and {@link
* VideoEncoderSettings#bitrateMode} is VBR.
* <li>{@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level} are ignored
* for {@link MimeTypes#VIDEO_H264}
* </ul>
*
* <p>Consider implementing {@link Codec.EncoderFactory} if such adjustments are unwanted.
* <p>{@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level} are ignored
* for {@link MimeTypes#VIDEO_H264}. Consider implementing {@link Codec.EncoderFactory} if such
* adjustments are unwanted.
*
* <p>{@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.
*
* <p>With this enabled, encoders are configured to output high quality video.
*
* <p>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<MediaCodecInfo> encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType);
if (encodersForMimeType.isEmpty()) {
ImmutableList<MediaCodecInfo> 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<MediaCodecInfo> 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 {
* </ul>
*/
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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<MediaCodecInfo> selectEncoderInfos(String mimeType);
ImmutableList<MediaCodecInfo> selectEncoderInfos(String mimeType);
}

View File

@ -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}.
*
* <ul>
* <li>Constant quality: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CQ}.
* <li>Variable bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}.
* <li>Constant bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR}.
* <li>Constant bitrate with frame drops: {@link
* MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR_FD}, available from API31.
* </ul>
*/
@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}.
*
* <p>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}.
*
* <p>Only {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR} and {@link
* MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR} are allowed.
* <p>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.
*
* <p>Default value is {@code false}.
*
* <p>Requires {@link android.media.MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}.
*
* <p>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;
}
}