Move Encoder quality API to VideoEncoderSettings.
Some other minor nits and adjustments to the API logic. PiperOrigin-RevId: 459490431
This commit is contained in:
parent
cb87b7432f
commit
91f1777741
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user