diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java index affc09940c..c2167a2495 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java @@ -95,8 +95,10 @@ public interface Codec { * * @param format The {@link Format} (of the output data) used to determine the underlying * encoder and its configuration values. {@link Format#sampleMimeType}, {@link Format#width} - * and {@link Format#height} must be set to those of the desired output video format. {@link - * Format#rotationDegrees} should be 0. The video should always be in landscape orientation. + * and {@link Format#height} are set to those of the desired output video format. {@link + * Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link Format#height}, + * therefore the video is always in landscape orientation. {@link Format#frameRate} is set + * to the output video's frame rate, if available. * @param allowedMimeTypes The non-empty list of allowed output sample {@link MimeTypes MIME * types}. * @return A {@link Codec} for video encoding. 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 8dd9d2d70d..319c1d4d1b 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 @@ -29,7 +29,6 @@ import android.util.Pair; import android.util.Size; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; @@ -38,32 +37,50 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A default implementation of {@link Codec.EncoderFactory}. */ +// TODO(b/224949986) Split audio and video encoder factory. public final class DefaultEncoderFactory implements Codec.EncoderFactory { - private static final int DEFAULT_COLOR_FORMAT = - MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; - private static final int DEFAULT_FRAME_RATE = 60; - private static final int DEFAULT_I_FRAME_INTERVAL_SECS = 1; + private static final int DEFAULT_FRAME_RATE = 30; - @Nullable private final EncoderSelector videoEncoderSelector; + private final EncoderSelector videoEncoderSelector; + private final VideoEncoderSettings requestedVideoEncoderSettings; private final boolean enableFallback; /** - * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, and - * format fallback enabled. - * - *
With format fallback enabled, and when the requested {@link Format} is not supported, {@code - * DefaultEncoderFactory} finds a format that is supported by the device and configures the {@link - * Codec} with it. The fallback process may change the requested {@link Format#sampleMimeType MIME - * type}, resolution, {@link Format#bitrate bitrate}, {@link Format#codecs profile/level}, etc. + * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a + * default {@link VideoEncoderSettings}, and with format fallback enabled. */ public DefaultEncoderFactory() { this(EncoderSelector.DEFAULT, /* enableFallback= */ true); } - /** Creates a new instance. */ + /** Creates a new instance using a default {@link VideoEncoderSettings}. */ + public DefaultEncoderFactory(EncoderSelector videoEncoderSelector, boolean enableFallback) { + this(videoEncoderSelector, new VideoEncoderSettings.Builder().build(), enableFallback); + } + + /** + * Creates a new instance. + * + *
Values in {@code requestedVideoEncoderSettings} could be adjusted to improve encoding + * quality and/or reduce failures. Specifically, {@link VideoEncoderSettings#profile} and {@link + * VideoEncoderSettings#level} are ignored for {@link MimeTypes#VIDEO_H264}. Consider implementing + * {@link Codec.EncoderFactory} if such adjustments are unwanted. + * + *
With format fallback enabled, and when the requested {@link Format} is not supported, {@code
+ * DefaultEncoderFactory} finds a format that is supported by the device and configures the {@link
+ * Codec} with it. The fallback process may change the requested {@link Format#sampleMimeType MIME
+ * type}, resolution, {@link Format#bitrate bitrate}, {@link Format#codecs profile/level}, etc.
+ *
+ * @param videoEncoderSelector The {@link EncoderSelector}.
+ * @param requestedVideoEncoderSettings The {@link VideoEncoderSettings}.
+ * @param enableFallback Whether to enable fallback.
+ */
public DefaultEncoderFactory(
- @Nullable EncoderSelector videoEncoderSelector, boolean enableFallback) {
+ EncoderSelector videoEncoderSelector,
+ VideoEncoderSettings requestedVideoEncoderSettings,
+ boolean enableFallback) {
this.videoEncoderSelector = videoEncoderSelector;
+ this.requestedVideoEncoderSettings = requestedVideoEncoderSettings;
this.enableFallback = enableFallback;
}
@@ -99,6 +116,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
@Override
public Codec createForVideoEncoding(Format format, List The adjustment is applied in-place to {@code mediaFormat}.
+ */
+ private static void adjustMediaFormatForH264EncoderSettings(
+ MediaFormat mediaFormat, MediaCodecInfo encoderInfo) {
+ // TODO(b/210593256): Remove overriding profile/level (before API 29) after switching to in-app
+ // muxing.
+ String mimeType = MimeTypes.VIDEO_H264;
+ if (Util.SDK_INT >= 29) {
+ int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
+ int supportedEncodingLevel =
+ EncoderUtil.findHighestSupportedEncodingLevel(
+ encoderInfo, mimeType, expectedEncodingProfile);
+ if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) {
+ // Use the highest supported profile and use B-frames.
+ mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
+ mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
+ mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 1);
+ }
+ } else if (Util.SDK_INT >= 26) {
+ int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
+ int supportedEncodingLevel =
+ EncoderUtil.findHighestSupportedEncodingLevel(
+ encoderInfo, mimeType, expectedEncodingProfile);
+ if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) {
+ // Use the highest-supported profile, but disable the generation of B-frames using
+ // MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these
+ // system versions.
+ mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
+ mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
+ // TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after switching to
+ // in-app muxing.
+ mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1);
+ }
+ } else if (Util.SDK_INT >= 24) {
+ int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline;
+ int supportedLevel =
+ EncoderUtil.findHighestSupportedEncodingLevel(
+ encoderInfo, mimeType, expectedEncodingProfile);
+ checkState(supportedLevel != EncoderUtil.LEVEL_UNSET);
+ // Use the baseline profile for safest results, as encoding in baseline is required per
+ // https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding
+ mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
+ mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel);
+ } else {
+ // For API levels below 24, setting profile and level can lead to failures in MediaCodec
+ // configuration. The encoder selects the profile/level when we don't set them.
+ mediaFormat.setString(MediaFormat.KEY_PROFILE, null);
+ mediaFormat.setString(MediaFormat.KEY_LEVEL, null);
+ }
}
private interface EncoderFallbackCost {
@@ -305,6 +364,15 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
int getParameterSupportGap(MediaCodecInfo encoderInfo);
}
+ /**
+ * Filters a list of {@link MediaCodecInfo encoders} by a {@link EncoderFallbackCost cost
+ * function}.
+ *
+ * @param encoders A list of {@link MediaCodecInfo encoders}.
+ * @param cost A {@link EncoderFallbackCost cost function}.
+ * @return A list of {@link MediaCodecInfo encoders} with the lowest costs, empty if the costs of
+ * all encoders are {@link Integer#MAX_VALUE}.
+ */
private static ImmutableList Only {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR} and {@link
+ * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR} are allowed.
+ *
+ * @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);
+ this.bitrateMode = bitrateMode;
+ return this;
+ }
+
+ /**
+ * Sets {@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level}. The default
+ * values are both {@link #NO_VALUE}.
+ *
+ * The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel},
+ * or {@link #NO_VALUE}.
+ *
+ * Profile and level settings will be ignored when using {@link DefaultEncoderFactory} and
+ * encoding to H264.
+ *
+ * @param encodingProfile The {@link VideoEncoderSettings#profile}.
+ * @param encodingLevel The {@link VideoEncoderSettings#level}.
+ * @return This builder.
+ */
+ public Builder setEncodingProfileLevel(int encodingProfile, int encodingLevel) {
+ this.profile = encodingProfile;
+ this.level = encodingLevel;
+ return this;
+ }
+
+ /**
+ * Sets {@link VideoEncoderSettings#colorProfile}. The default value is {@link
+ * #DEFAULT_COLOR_PROFILE}.
+ *
+ * The value must be one of the {@code COLOR_*} constants defined in {@link
+ * MediaCodecInfo.CodecCapabilities}.
+ *
+ * @param colorProfile The {@link VideoEncoderSettings#colorProfile}.
+ * @return This builder.
+ */
+ public Builder setColorProfile(int colorProfile) {
+ this.colorProfile = colorProfile;
+ return this;
+ }
+
+ /**
+ * Sets {@link VideoEncoderSettings#iFrameIntervalSeconds}. The default value is {@link
+ * #DEFAULT_I_FRAME_INTERVAL_SECONDS}.
+ *
+ * @param iFrameIntervalSeconds The {@link VideoEncoderSettings#iFrameIntervalSeconds}.
+ * @return This builder.
+ */
+ public Builder setiFrameIntervalSeconds(float iFrameIntervalSeconds) {
+ this.iFrameIntervalSeconds = iFrameIntervalSeconds;
+ return this;
+ }
+
+ /** Builds the instance. */
+ public VideoEncoderSettings build() {
+ return new VideoEncoderSettings(
+ bitrate, bitrateMode, profile, level, colorProfile, iFrameIntervalSeconds);
+ }
+ }
+
+ /** The encoding bitrate. */
+ public final int bitrate;
+ /** One of {@link BitrateMode the allowed modes}. */
+ public final @BitrateMode int bitrateMode;
+ /** The encoding profile. */
+ public final int profile;
+ /** The encoding level. */
+ public final int level;
+ /** The encoding color profile. */
+ public final int colorProfile;
+ /** The encoding I-Frame interval in seconds. */
+ public final float iFrameIntervalSeconds;
+
+ private VideoEncoderSettings(
+ int bitrate,
+ int bitrateMode,
+ int profile,
+ int level,
+ int colorProfile,
+ float iFrameIntervalSeconds) {
+ this.bitrate = bitrate;
+ this.bitrateMode = bitrateMode;
+ this.profile = profile;
+ this.level = level;
+ this.colorProfile = colorProfile;
+ this.iFrameIntervalSeconds = iFrameIntervalSeconds;
+ }
+
+ /**
+ * Returns a {@link VideoEncoderSettings.Builder} initialized with the values of this instance.
+ */
+ public Builder buildUpon() {
+ return new Builder(this);
+ }
+}
diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java
index b899b5e6f8..1e060325e1 100644
--- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java
+++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java
@@ -82,11 +82,13 @@ import org.checkerframework.dataflow.qual.Pure;
.setWidth(requestedEncoderDimensions.getWidth())
.setHeight(requestedEncoderDimensions.getHeight())
.setRotationDegrees(0)
+ .setFrameRate(inputFormat.frameRate)
.setSampleMimeType(
transformationRequest.videoMimeType != null
? transformationRequest.videoMimeType
: inputFormat.sampleMimeType)
.build();
+
encoder = encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes);
Format encoderSupportedFormat = encoder.getConfigurationFormat();
fallbackListener.onTransformationRequestFinalized(
+ *
+ */
+ @Documented
+ @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
+ })
+ public @interface BitrateMode {}
+
+ /** Builds {@link VideoEncoderSettings} instances. */
+ public static final class Builder {
+ private int bitrate;
+ private @BitrateMode int bitrateMode;
+ private int profile;
+ private int level;
+ private int colorProfile;
+ private float iFrameIntervalSeconds;
+
+ /** Creates a new instance. */
+ public Builder() {
+ this.bitrate = NO_VALUE;
+ this.bitrateMode = MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR;
+ this.profile = NO_VALUE;
+ this.level = NO_VALUE;
+ this.colorProfile = DEFAULT_COLOR_PROFILE;
+ this.iFrameIntervalSeconds = DEFAULT_I_FRAME_INTERVAL_SECONDS;
+ }
+
+ private Builder(VideoEncoderSettings videoEncoderSettings) {
+ this.bitrate = videoEncoderSettings.bitrate;
+ this.bitrateMode = videoEncoderSettings.bitrateMode;
+ this.profile = videoEncoderSettings.profile;
+ this.level = videoEncoderSettings.level;
+ this.colorProfile = videoEncoderSettings.colorProfile;
+ this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds;
+ }
+
+ /**
+ * Sets {@link VideoEncoderSettings#bitrate}. The default value is {@link #NO_VALUE}.
+ *
+ * @param bitrate The {@link VideoEncoderSettings#bitrate}.
+ * @return This builder.
+ */
+ public Builder setBitrate(int bitrate) {
+ this.bitrate = bitrate;
+ return this;
+ }
+
+ /**
+ * Sets {@link VideoEncoderSettings#bitrateMode}. The default value is {@code
+ * MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR}.
+ *
+ *