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 5735e79c76..9b7d2ba4e1 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java @@ -18,6 +18,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.transformer.CodecFactoryUtil.createCodec; @@ -151,30 +152,44 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { // 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)) { + int supportedEncodingLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { // Use the highest supported profile and use B-frames. mediaFormat.setInteger( MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); 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. + int supportedEncodingLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + 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, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); + 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 >= 23) { + int supportedLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); + 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, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel); } else { - // Use the baseline profile for safest results. + // 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, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); } @@ -270,11 +285,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { @Nullable String codecs = null; if (profileLevel != null && requestedFormat.sampleMimeType.equals(mimeType) - && EncoderUtil.isProfileLevelSupported( - pickedEncoder, - mimeType, - /* profile= */ profileLevel.first, - /* level= */ profileLevel.second)) { + && profileLevel.second + <= EncoderUtil.findHighestSupportedEncodingLevel( + pickedEncoder, mimeType, /* profile= */ profileLevel.first)) { codecs = requestedFormat.codecs; } 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 a95d29ffae..3a6c9bc63d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java @@ -113,28 +113,26 @@ public final class EncoderUtil { } /** - * Checks whether the {@link MediaCodecInfo encoder} supports the given profile and level. + * Finds the highest supported encoding level given a profile. * * @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. + * @return The highest supported encoding level, as documented in {@link + * MediaCodecInfo.CodecProfileLevel}, or {@link #LEVEL_UNSET} if the profile is not supported. */ - public static boolean isProfileLevelSupported( - MediaCodecInfo encoderInfo, String mimeType, int profile, int level) { + public static int findHighestSupportedEncodingLevel( + MediaCodecInfo encoderInfo, String mimeType, int profile) { // TODO(b/214964116): Merge into MediaCodecUtil. MediaCodecInfo.CodecProfileLevel[] profileLevels = encoderInfo.getCapabilitiesForType(mimeType).profileLevels; for (MediaCodecInfo.CodecProfileLevel profileLevel : profileLevels) { - if (profileLevel.profile == profile - && (level == LEVEL_UNSET || profileLevel.level == level)) { - return true; + if (profileLevel.profile == profile) { + return profileLevel.level; } } - return false; + return LEVEL_UNSET; } /**