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 7af2582486..3c2312a6d8 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java @@ -31,6 +31,7 @@ import androidx.annotation.Nullable; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -119,29 +120,65 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { /* mediaCodecName= */ null); } + MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.first; format = encoderAndClosestFormatSupport.second; - MediaFormat mediaFormat = - MediaFormat.createVideoFormat( - checkNotNull(format.sampleMimeType), format.width, format.height); + String mimeType = checkNotNull(format.sampleMimeType); + MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, format.width, format.height); mediaFormat.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.averageBitrate); @Nullable Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel != null) { + // The codecProfileAndLevel is supported by the encoder. mediaFormat.setInteger(MediaFormat.KEY_PROFILE, codecProfileAndLevel.first); if (SDK_INT >= 23) { mediaFormat.setInteger(MediaFormat.KEY_LEVEL, codecProfileAndLevel.second); } } + // TODO(b/210593256): Remove overriding profile/level (before API 29) after switching to in-app + // muxing. + if (mimeType.equals(MimeTypes.VIDEO_H264)) { + // Applying suggested profile/level settings from + // https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles + if (Util.SDK_INT >= 29) { + if (EncoderUtil.isProfileLevelSupported( + encoderInfo, + mimeType, + MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, + EncoderUtil.LEVEL_UNSET)) { + // Use the highest supported profile and use B-frames. + mediaFormat.setInteger( + MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 1); + } + } else if (Util.SDK_INT >= 26) { + if (EncoderUtil.isProfileLevelSupported( + encoderInfo, + mimeType, + MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, + EncoderUtil.LEVEL_UNSET)) { + // Use the highest-supported profile, but disable the generation of B-frames. This + // accommodates some limitations in the MediaMuxer in these system versions. + mediaFormat.setInteger( + MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1); + } + } else { + // Use the baseline profile for safest results. + mediaFormat.setInteger( + MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); + } + } + mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, DEFAULT_COLOR_FORMAT); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL_SECS); return createCodec( format, mediaFormat, - encoderAndClosestFormatSupport.first.getName(), + encoderInfo.getName(), /* isVideo= */ true, /* isDecoder= */ false, /* outputSurface= */ null); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java index d4257549d1..a95d29ffae 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java @@ -25,6 +25,7 @@ import android.util.Pair; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; @@ -37,6 +38,9 @@ import java.util.List; @UnstableApi public final class EncoderUtil { + /** A value to indicate the encoding level is not set. */ + public static final int LEVEL_UNSET = Format.NO_VALUE; + private static final List encoders = new ArrayList<>(); /** @@ -108,14 +112,25 @@ public final class EncoderUtil { : null; } - /** Returns whether the {@link MediaCodecInfo encoder} supports the given profile and level. */ + /** + * Checks whether the {@link MediaCodecInfo encoder} supports the given profile and level. + * + * @param encoderInfo The {@link MediaCodecInfo encoderInfo}. + * @param mimeType The {@link MimeTypes MIME type}. + * @param profile The encoding profile. + * @param level The encoding level, specify {@link #LEVEL_UNSET} if checking whether the encoder + * supports a specific profile. + * @return Whether the profile and level (if set) is supported by the encoder. + */ public static boolean isProfileLevelSupported( MediaCodecInfo encoderInfo, String mimeType, int profile, int level) { + // TODO(b/214964116): Merge into MediaCodecUtil. MediaCodecInfo.CodecProfileLevel[] profileLevels = encoderInfo.getCapabilitiesForType(mimeType).profileLevels; for (MediaCodecInfo.CodecProfileLevel profileLevel : profileLevels) { - if (profileLevel.profile == profile && profileLevel.level == level) { + if (profileLevel.profile == profile + && (level == LEVEL_UNSET || profileLevel.level == level)) { return true; } }