diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2f9089ad21..821ae4e7d7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,9 @@ * Fix `MediaCodec.CryptoException` sometimes being reported as an "unexpected runtime error" when `MediaCodec` is operated in asynchronous mode (default behaviour on API 31+). + * Deprecated `MediaCodecUtil.getCodecProfileAndLevel`. Use + `androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel` + instead. * Transformer: * Track Selection: * Extractors: diff --git a/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java index eca8dac931..58d8573f3a 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java @@ -17,16 +17,23 @@ package androidx.media3.common.util; import static androidx.media3.common.util.Assertions.checkArgument; +import android.annotation.SuppressLint; +import android.media.MediaCodecInfo; import android.util.Pair; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; +import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** Provides utilities for handling various types of codec-specific data. */ +@SuppressLint("InlinedApi") @UnstableApi public final class CodecSpecificDataUtil { @@ -40,6 +47,24 @@ public final class CodecSpecificDataUtil { private static final int EXTENDED_PAR = 0x0F; private static final int RECTANGULAR = 0x00; + // Codecs to constant mappings. + // AVC. + private static final String CODEC_ID_AVC1 = "avc1"; + private static final String CODEC_ID_AVC2 = "avc2"; + // VP9 + private static final String CODEC_ID_VP09 = "vp09"; + // HEVC. + private static final String CODEC_ID_HEV1 = "hev1"; + private static final String CODEC_ID_HVC1 = "hvc1"; + // AV1. + private static final String CODEC_ID_AV01 = "av01"; + // MP4A AAC. + private static final String CODEC_ID_MP4A = "mp4a"; + + private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); + + private static final String TAG = "CodecSpecificDataUtil"; + /** * Parses an ALAC AudioSpecificConfig (i.e. an ALACSpecificConfig). @@ -234,6 +259,96 @@ public final class CodecSpecificDataUtil { return builder.toString(); } + /** + * Returns profile and level (as defined by {@link MediaCodecInfo.CodecProfileLevel}) + * corresponding to the codec description string (as defined by RFC 6381) of the given format. + * + * @param format Media format with a codec description string, as defined by RFC 6381. + * @return A pair (profile constant, level constant) if the codec of the {@code format} is + * well-formed and recognized, or null otherwise. + */ + @Nullable + public static Pair getCodecProfileAndLevel(Format format) { + if (format.codecs == null) { + return null; + } + String[] parts = format.codecs.split("\\."); + // Dolby Vision can use DV, AVC or HEVC codec IDs, so check the MIME type first. + if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { + return getDolbyVisionProfileAndLevel(format.codecs, parts); + } + switch (parts[0]) { + case CODEC_ID_AVC1: + case CODEC_ID_AVC2: + return getAvcProfileAndLevel(format.codecs, parts); + case CODEC_ID_VP09: + return getVp9ProfileAndLevel(format.codecs, parts); + case CODEC_ID_HEV1: + case CODEC_ID_HVC1: + return getHevcProfileAndLevel(format.codecs, parts, format.colorInfo); + case CODEC_ID_AV01: + return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo); + case CODEC_ID_MP4A: + return getAacCodecProfileAndLevel(format.codecs, parts); + default: + return null; + } + } + + /** + * Returns Hevc profile and level corresponding to the codec description string (as defined by RFC + * 6381) and it {@link ColorInfo}. + * + * @param codec The codec description string (as defined by RFC 6381). + * @param parts The codec string split by ".". + * @param colorInfo The {@link ColorInfo}. + * @return A pair (profile constant, level constant) if profile and level are recognized, or + * {@code null} otherwise. + */ + @Nullable + public static Pair getHevcProfileAndLevel( + String codec, String[] parts, @Nullable ColorInfo colorInfo) { + if (parts.length < 4) { + // The codec has fewer parts than required by the HEVC codec string format. + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + // The profile_space gets ignored. + Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + @Nullable String profileString = matcher.group(1); + int profile; + if ("1".equals(profileString)) { + profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain; + } else if ("2".equals(profileString)) { + if (colorInfo != null && colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084) { + profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10; + } else { + // For all other cases, we map to the Main10 profile. Note that this includes HLG + // HDR. On Android 13+, the platform guarantees that a decoder that advertises + // HEVCProfileMain10 will be able to decode HLG. This is not guaranteed for older + // Android versions, but we still map to Main10 for backwards compatibility. + profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10; + } + } else if ("6".equals(profileString)) { + // Framework does not have profileLevel.HEVCProfileMultiviewMain defined. + profile = 6; + } else { + Log.w(TAG, "Unknown HEVC profile string: " + profileString); + return null; + } + @Nullable String levelString = parts[3]; + @Nullable Integer level = hevcCodecStringToProfileLevel(levelString); + if (level == null) { + Log.w(TAG, "Unknown HEVC level string: " + levelString); + return null; + } + return new Pair<>(profile, level); + } + /** * Constructs a NAL unit consisting of the NAL start code followed by the specified data. * @@ -319,5 +434,507 @@ public final class CodecSpecificDataUtil { return true; } + @Nullable + private static Pair getDolbyVisionProfileAndLevel( + String codec, String[] parts) { + if (parts.length < 3) { + // The codec has fewer parts than required by the Dolby Vision codec string format. + Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec); + return null; + } + // The profile_space gets ignored. + Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec); + return null; + } + @Nullable String profileString = matcher.group(1); + @Nullable Integer profile = dolbyVisionStringToProfile(profileString); + if (profile == null) { + Log.w(TAG, "Unknown Dolby Vision profile string: " + profileString); + return null; + } + String levelString = parts[2]; + @Nullable Integer level = dolbyVisionStringToLevel(levelString); + if (level == null) { + Log.w(TAG, "Unknown Dolby Vision level string: " + levelString); + return null; + } + return new Pair<>(profile, level); + } + + @Nullable + private static Pair getAvcProfileAndLevel(String codec, String[] parts) { + if (parts.length < 2) { + // The codec has fewer parts than required by the AVC codec string format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + int profileInteger; + int levelInteger; + try { + if (parts[1].length() == 6) { + // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. + profileInteger = Integer.parseInt(parts[1].substring(0, 2), 16); + levelInteger = Integer.parseInt(parts[1].substring(4), 16); + } else if (parts.length >= 3) { + // Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal. + profileInteger = Integer.parseInt(parts[1]); + levelInteger = Integer.parseInt(parts[2]); + } else { + // We don't recognize the format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + + int profile = avcProfileNumberToConst(profileInteger); + if (profile == -1) { + Log.w(TAG, "Unknown AVC profile: " + profileInteger); + return null; + } + int level = avcLevelNumberToConst(levelInteger); + if (level == -1) { + Log.w(TAG, "Unknown AVC level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + + @Nullable + private static Pair getVp9ProfileAndLevel(String codec, String[] parts) { + if (parts.length < 3) { + Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec); + return null; + } + int profileInteger; + int levelInteger; + try { + profileInteger = Integer.parseInt(parts[1]); + levelInteger = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec); + return null; + } + + int profile = vp9ProfileNumberToConst(profileInteger); + if (profile == -1) { + Log.w(TAG, "Unknown VP9 profile: " + profileInteger); + return null; + } + int level = vp9LevelNumberToConst(levelInteger); + if (level == -1) { + Log.w(TAG, "Unknown VP9 level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + + @Nullable + private static Pair getAv1ProfileAndLevel( + String codec, String[] parts, @Nullable ColorInfo colorInfo) { + if (parts.length < 4) { + Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec); + return null; + } + int profileInteger; + int levelInteger; + int bitDepthInteger; + try { + profileInteger = Integer.parseInt(parts[1]); + levelInteger = Integer.parseInt(parts[2].substring(0, 2)); + bitDepthInteger = Integer.parseInt(parts[3]); + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec); + return null; + } + + if (profileInteger != 0) { + Log.w(TAG, "Unknown AV1 profile: " + profileInteger); + return null; + } + if (bitDepthInteger != 8 && bitDepthInteger != 10) { + Log.w(TAG, "Unknown AV1 bit depth: " + bitDepthInteger); + return null; + } + int profile; + if (bitDepthInteger == 8) { + profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8; + } else if (colorInfo != null + && (colorInfo.hdrStaticInfo != null + || colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG + || colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084)) { + profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10; + } else { + profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10; + } + + int level = av1LevelNumberToConst(levelInteger); + if (level == -1) { + Log.w(TAG, "Unknown AV1 level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + + @Nullable + private static Pair getAacCodecProfileAndLevel(String codec, String[] parts) { + if (parts.length != 3) { + Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); + return null; + } + try { + // Get the object type indication, which is a hexadecimal value (see RFC 6381/ISO 14496-1). + int objectTypeIndication = Integer.parseInt(parts[1], 16); + @Nullable String mimeType = MimeTypes.getMimeTypeFromMp4ObjectType(objectTypeIndication); + if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + // For MPEG-4 audio this is followed by an audio object type indication as a decimal number. + int audioObjectTypeIndication = Integer.parseInt(parts[2]); + int profile = mp4aAudioObjectTypeToProfile(audioObjectTypeIndication); + if (profile != -1) { + // Level is set to zero in AAC decoder CodecProfileLevels. + return new Pair<>(profile, 0); + } + } + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); + } + return null; + } + + private static int avcProfileNumberToConst(int profileNumber) { + switch (profileNumber) { + case 66: + return MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline; + case 77: + return MediaCodecInfo.CodecProfileLevel.AVCProfileMain; + case 88: + return MediaCodecInfo.CodecProfileLevel.AVCProfileExtended; + case 100: + return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; + case 110: + return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10; + case 122: + return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422; + case 244: + return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444; + default: + return -1; + } + } + + private static int avcLevelNumberToConst(int levelNumber) { + // TODO: Find int for CodecProfileLevel.AVCLevel1b. + switch (levelNumber) { + case 10: + return MediaCodecInfo.CodecProfileLevel.AVCLevel1; + case 11: + return MediaCodecInfo.CodecProfileLevel.AVCLevel11; + case 12: + return MediaCodecInfo.CodecProfileLevel.AVCLevel12; + case 13: + return MediaCodecInfo.CodecProfileLevel.AVCLevel13; + case 20: + return MediaCodecInfo.CodecProfileLevel.AVCLevel2; + case 21: + return MediaCodecInfo.CodecProfileLevel.AVCLevel21; + case 22: + return MediaCodecInfo.CodecProfileLevel.AVCLevel22; + case 30: + return MediaCodecInfo.CodecProfileLevel.AVCLevel3; + case 31: + return MediaCodecInfo.CodecProfileLevel.AVCLevel31; + case 32: + return MediaCodecInfo.CodecProfileLevel.AVCLevel32; + case 40: + return MediaCodecInfo.CodecProfileLevel.AVCLevel4; + case 41: + return MediaCodecInfo.CodecProfileLevel.AVCLevel41; + case 42: + return MediaCodecInfo.CodecProfileLevel.AVCLevel42; + case 50: + return MediaCodecInfo.CodecProfileLevel.AVCLevel5; + case 51: + return MediaCodecInfo.CodecProfileLevel.AVCLevel51; + case 52: + return MediaCodecInfo.CodecProfileLevel.AVCLevel52; + default: + return -1; + } + } + + private static int vp9ProfileNumberToConst(int profileNumber) { + switch (profileNumber) { + case 0: + return MediaCodecInfo.CodecProfileLevel.VP9Profile0; + case 1: + return MediaCodecInfo.CodecProfileLevel.VP9Profile1; + case 2: + return MediaCodecInfo.CodecProfileLevel.VP9Profile2; + case 3: + return MediaCodecInfo.CodecProfileLevel.VP9Profile3; + default: + return -1; + } + } + + private static int vp9LevelNumberToConst(int levelNumber) { + switch (levelNumber) { + case 10: + return MediaCodecInfo.CodecProfileLevel.VP9Level1; + case 11: + return MediaCodecInfo.CodecProfileLevel.VP9Level11; + case 20: + return MediaCodecInfo.CodecProfileLevel.VP9Level2; + case 21: + return MediaCodecInfo.CodecProfileLevel.VP9Level21; + case 30: + return MediaCodecInfo.CodecProfileLevel.VP9Level3; + case 31: + return MediaCodecInfo.CodecProfileLevel.VP9Level31; + case 40: + return MediaCodecInfo.CodecProfileLevel.VP9Level4; + case 41: + return MediaCodecInfo.CodecProfileLevel.VP9Level41; + case 50: + return MediaCodecInfo.CodecProfileLevel.VP9Level5; + case 51: + return MediaCodecInfo.CodecProfileLevel.VP9Level51; + case 60: + return MediaCodecInfo.CodecProfileLevel.VP9Level6; + case 61: + return MediaCodecInfo.CodecProfileLevel.VP9Level61; + case 62: + return MediaCodecInfo.CodecProfileLevel.VP9Level62; + default: + return -1; + } + } + + @Nullable + private static Integer hevcCodecStringToProfileLevel(@Nullable String codecString) { + if (codecString == null) { + return null; + } + switch (codecString) { + case "L30": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel1; + case "L60": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel2; + case "L63": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel21; + case "L90": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel3; + case "L93": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31; + case "L120": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4; + case "L123": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel41; + case "L150": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel5; + case "L153": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel51; + case "L156": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel52; + case "L180": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel6; + case "L183": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel61; + case "L186": + return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62; + case "H30": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel1; + case "H60": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel2; + case "H63": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel21; + case "H90": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel3; + case "H93": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel31; + case "H120": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel4; + case "H123": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel41; + case "H150": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel5; + case "H153": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel51; + case "H156": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel52; + case "H180": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel6; + case "H183": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel61; + case "H186": + return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel62; + default: + return null; + } + } + + @Nullable + private static Integer dolbyVisionStringToProfile(@Nullable String profileString) { + if (profileString == null) { + return null; + } + switch (profileString) { + case "00": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavPer; + case "01": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavPen; + case "02": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDer; + case "03": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDen; + case "04": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr; + case "05": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn; + case "06": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDth; + case "07": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtb; + case "08": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt; + case "09": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe; + case "10": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110; + default: + return null; + } + } + + @Nullable + private static Integer dolbyVisionStringToLevel(@Nullable String levelString) { + if (levelString == null) { + return null; + } + // TODO (Internal: b/179261323): use framework constant for level 13. + switch (levelString) { + case "01": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd24; + case "02": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd30; + case "03": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd24; + case "04": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30; + case "05": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60; + case "06": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd24; + case "07": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30; + case "08": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd48; + case "09": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60; + case "10": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd120; + case "11": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k30; + case "12": + return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k60; + case "13": + return 0x1000; + default: + return null; + } + } + + private static int av1LevelNumberToConst(int levelNumber) { + // See https://aomediacodec.github.io/av1-spec/av1-spec.pdf Annex A: Profiles and levels for + // more information on mapping AV1 codec strings to levels. + switch (levelNumber) { + case 0: + return MediaCodecInfo.CodecProfileLevel.AV1Level2; + case 1: + return MediaCodecInfo.CodecProfileLevel.AV1Level21; + case 2: + return MediaCodecInfo.CodecProfileLevel.AV1Level22; + case 3: + return MediaCodecInfo.CodecProfileLevel.AV1Level23; + case 4: + return MediaCodecInfo.CodecProfileLevel.AV1Level3; + case 5: + return MediaCodecInfo.CodecProfileLevel.AV1Level31; + case 6: + return MediaCodecInfo.CodecProfileLevel.AV1Level32; + case 7: + return MediaCodecInfo.CodecProfileLevel.AV1Level33; + case 8: + return MediaCodecInfo.CodecProfileLevel.AV1Level4; + case 9: + return MediaCodecInfo.CodecProfileLevel.AV1Level41; + case 10: + return MediaCodecInfo.CodecProfileLevel.AV1Level42; + case 11: + return MediaCodecInfo.CodecProfileLevel.AV1Level43; + case 12: + return MediaCodecInfo.CodecProfileLevel.AV1Level5; + case 13: + return MediaCodecInfo.CodecProfileLevel.AV1Level51; + case 14: + return MediaCodecInfo.CodecProfileLevel.AV1Level52; + case 15: + return MediaCodecInfo.CodecProfileLevel.AV1Level53; + case 16: + return MediaCodecInfo.CodecProfileLevel.AV1Level6; + case 17: + return MediaCodecInfo.CodecProfileLevel.AV1Level61; + case 18: + return MediaCodecInfo.CodecProfileLevel.AV1Level62; + case 19: + return MediaCodecInfo.CodecProfileLevel.AV1Level63; + case 20: + return MediaCodecInfo.CodecProfileLevel.AV1Level7; + case 21: + return MediaCodecInfo.CodecProfileLevel.AV1Level71; + case 22: + return MediaCodecInfo.CodecProfileLevel.AV1Level72; + case 23: + return MediaCodecInfo.CodecProfileLevel.AV1Level73; + default: + return -1; + } + } + + private static int mp4aAudioObjectTypeToProfile(int profileNumber) { + switch (profileNumber) { + case 1: + return MediaCodecInfo.CodecProfileLevel.AACObjectMain; + case 2: + return MediaCodecInfo.CodecProfileLevel.AACObjectLC; + case 3: + return MediaCodecInfo.CodecProfileLevel.AACObjectSSR; + case 4: + return MediaCodecInfo.CodecProfileLevel.AACObjectLTP; + case 5: + return MediaCodecInfo.CodecProfileLevel.AACObjectHE; + case 6: + return MediaCodecInfo.CodecProfileLevel.AACObjectScalable; + case 17: + return MediaCodecInfo.CodecProfileLevel.AACObjectERLC; + case 20: + return MediaCodecInfo.CodecProfileLevel.AACObjectERScalable; + case 23: + return MediaCodecInfo.CodecProfileLevel.AACObjectLD; + case 29: + return MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS; + case 39: + return MediaCodecInfo.CodecProfileLevel.AACObjectELD; + case 42: + return MediaCodecInfo.CodecProfileLevel.AACObjectXHE; + default: + return -1; + } + } + private CodecSpecificDataUtil() {} } diff --git a/libraries/common/src/test/java/androidx/media3/common/util/CodecSpecificDataUtilTest.java b/libraries/common/src/test/java/androidx/media3/common/util/CodecSpecificDataUtilTest.java index 3293ee5699..74c98fd882 100644 --- a/libraries/common/src/test/java/androidx/media3/common/util/CodecSpecificDataUtilTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/util/CodecSpecificDataUtilTest.java @@ -15,9 +15,16 @@ */ package androidx.media3.common.util; +import static androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel; import static com.google.common.truth.Truth.assertThat; +import android.media.MediaCodecInfo; import android.util.Pair; +import androidx.annotation.Nullable; +import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; +import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,4 +52,154 @@ public class CodecSpecificDataUtilTest { assertThat(sampleRateAndChannelCount.first).isEqualTo(96000); assertThat(sampleRateAndChannelCount.second).isEqualTo(2); } + + @Test + public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_VP9, + "vp09.01.51", + MediaCodecInfo.CodecProfileLevel.VP9Profile1, + MediaCodecInfo.CodecProfileLevel.VP9Level51); + } + + @Test + public void getCodecProfileAndLevel_handlesVp9Profile2CodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_VP9, + "vp09.02.10", + MediaCodecInfo.CodecProfileLevel.VP9Profile2, + MediaCodecInfo.CodecProfileLevel.VP9Level1); + } + + @Test + public void getCodecProfileAndLevel_handlesFullVp9CodecString() { + // Example from https://www.webmproject.org/vp9/mp4/#codecs-parameter-string. + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_VP9, + "vp09.02.10.10.01.09.16.09.01", + MediaCodecInfo.CodecProfileLevel.VP9Profile2, + MediaCodecInfo.CodecProfileLevel.VP9Level1); + } + + @Test + public void getCodecProfileAndLevel_handlesDolbyVisionCodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_DOLBY_VISION, + "dvh1.05.05", + MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn, + MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60); + } + + @Test + public void getCodecProfileAndLevel_handlesDolbyVisionProfile10CodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_DOLBY_VISION, + "dav1.10.09", + MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, + MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60); + } + + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain8CodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_AV1, + "av01.0.10M.08", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8, + MediaCodecInfo.CodecProfileLevel.AV1Level42); + } + + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain10CodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_AV1, + "av01.0.20M.10", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, + MediaCodecInfo.CodecProfileLevel.AV1Level7); + } + + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithHdrInfoSet() { + ColorInfo colorInfo = + new ColorInfo.Builder() + .setColorSpace(C.COLOR_SPACE_BT709) + .setColorRange(C.COLOR_RANGE_LIMITED) + .setColorTransfer(C.COLOR_TRANSFER_SDR) + .setHdrStaticInfo(new byte[] {1, 2, 3, 4, 5, 6, 7}) + .build(); + Format format = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_AV1) + .setCodecs("av01.0.21M.10") + .setColorInfo(colorInfo) + .build(); + assertCodecProfileAndLevelForFormat( + format, + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10, + MediaCodecInfo.CodecProfileLevel.AV1Level71); + } + + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithoutHdrInfoSet() { + ColorInfo colorInfo = + new ColorInfo.Builder() + .setColorSpace(C.COLOR_SPACE_BT709) + .setColorRange(C.COLOR_RANGE_LIMITED) + .setColorTransfer(C.COLOR_TRANSFER_HLG) + .build(); + Format format = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_AV1) + .setCodecs("av01.0.21M.10") + .setColorInfo(colorInfo) + .build(); + assertCodecProfileAndLevelForFormat( + format, + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10, + MediaCodecInfo.CodecProfileLevel.AV1Level71); + } + + @Test + public void getCodecProfileAndLevel_handlesFullAv1CodecString() { + // Example from https://aomediacodec.github.io/av1-isobmff/#codecsparam. + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_AV1, + "av01.0.04M.10.0.112.09.16.09.0", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, + MediaCodecInfo.CodecProfileLevel.AV1Level3); + } + + @Test + public void getCodecProfileAndLevel_rejectsNullCodecString() { + Format format = new Format.Builder().setCodecs(null).build(); + assertThat(getCodecProfileAndLevel(format)).isNull(); + } + + @Test + public void getCodecProfileAndLevel_rejectsEmptyCodecString() { + Format format = new Format.Builder().setCodecs("").build(); + assertThat(getCodecProfileAndLevel(format)).isNull(); + } + + @Test + public void getCodecProfileAndLevel_handlesMvHevcCodecString() { + assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_MV_HEVC, + "hvc1.6.40.L120.BF.80", + /* profile= */ 6, + MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4); + } + + private static void assertCodecProfileAndLevelForCodecsString( + String sampleMimeType, String codecs, int profile, int level) { + Format format = + new Format.Builder().setSampleMimeType(sampleMimeType).setCodecs(codecs).build(); + assertCodecProfileAndLevelForFormat(format, profile, level); + } + + private static void assertCodecProfileAndLevelForFormat(Format format, int profile, int level) { + @Nullable Pair codecProfileAndLevel = getCodecProfileAndLevel(format); + assertThat(codecProfileAndLevel).isNotNull(); + assertThat(codecProfileAndLevel.first).isEqualTo(profile); + assertThat(codecProfileAndLevel.second).isEqualTo(level); + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java index 257b0cb9d5..12ff46a546 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.mediacodec; +import static androidx.media3.common.util.CodecSpecificDataUtil.getHevcProfileAndLevel; import static java.lang.Math.max; import android.annotation.SuppressLint; @@ -28,22 +29,20 @@ import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; -import androidx.media3.common.C; -import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; +import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.container.NalUnitUtil; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.InlineMe; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -66,25 +65,10 @@ public final class MediaCodecUtil { } private static final String TAG = "MediaCodecUtil"; - private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); @GuardedBy("MediaCodecUtil.class") private static final HashMap> decoderInfosCache = new HashMap<>(); - // Codecs to constant mappings. - // AVC. - private static final String CODEC_ID_AVC1 = "avc1"; - private static final String CODEC_ID_AVC2 = "avc2"; - // VP9 - private static final String CODEC_ID_VP09 = "vp09"; - // HEVC. - private static final String CODEC_ID_HEV1 = "hev1"; - private static final String CODEC_ID_HVC1 = "hvc1"; - // AV1. - private static final String CODEC_ID_AV01 = "av01"; - // MP4A AAC. - private static final String CODEC_ID_MP4A = "mp4a"; - // Lazily initialized. private static int maxH264DecodableFrameSize = -1; @@ -296,39 +280,15 @@ public final class MediaCodecUtil { } /** - * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the codec - * description string (as defined by RFC 6381) of the given format. - * - * @param format Media format with a codec description string, as defined by RFC 6381. - * @return A pair (profile constant, level constant) if the codec of the {@code format} is - * well-formed and recognized, or null otherwise. + * @deprecated Use {@link CodecSpecificDataUtil#getCodecProfileAndLevel(Format)}. */ + @InlineMe( + replacement = "CodecSpecificDataUtil.getCodecProfileAndLevel(format)", + imports = {"androidx.media3.common.util.CodecSpecificDataUtil"}) + @Deprecated @Nullable public static Pair getCodecProfileAndLevel(Format format) { - if (format.codecs == null) { - return null; - } - String[] parts = format.codecs.split("\\."); - // Dolby Vision can use DV, AVC or HEVC codec IDs, so check the MIME type first. - if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { - return getDolbyVisionProfileAndLevel(format.codecs, parts); - } - switch (parts[0]) { - case CODEC_ID_AVC1: - case CODEC_ID_AVC2: - return getAvcProfileAndLevel(format.codecs, parts); - case CODEC_ID_VP09: - return getVp9ProfileAndLevel(format.codecs, parts); - case CODEC_ID_HEV1: - case CODEC_ID_HVC1: - return getHevcProfileAndLevel(format.codecs, parts, format.colorInfo); - case CODEC_ID_AV01: - return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo); - case CODEC_ID_MP4A: - return getAacCodecProfileAndLevel(format.codecs, parts); - default: - return null; - } + return CodecSpecificDataUtil.getCodecProfileAndLevel(format); } /** @@ -716,196 +676,6 @@ public final class MediaCodecUtil { return codecInfo.isVendor(); } - @Nullable - private static Pair getDolbyVisionProfileAndLevel( - String codec, String[] parts) { - if (parts.length < 3) { - // The codec has fewer parts than required by the Dolby Vision codec string format. - Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec); - return null; - } - // The profile_space gets ignored. - Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); - if (!matcher.matches()) { - Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec); - return null; - } - @Nullable String profileString = matcher.group(1); - @Nullable Integer profile = dolbyVisionStringToProfile(profileString); - if (profile == null) { - Log.w(TAG, "Unknown Dolby Vision profile string: " + profileString); - return null; - } - String levelString = parts[2]; - @Nullable Integer level = dolbyVisionStringToLevel(levelString); - if (level == null) { - Log.w(TAG, "Unknown Dolby Vision level string: " + levelString); - return null; - } - return new Pair<>(profile, level); - } - - @Nullable - private static Pair getHevcProfileAndLevel( - String codec, String[] parts, @Nullable ColorInfo colorInfo) { - if (parts.length < 4) { - // The codec has fewer parts than required by the HEVC codec string format. - Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); - return null; - } - // The profile_space gets ignored. - Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); - if (!matcher.matches()) { - Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); - return null; - } - @Nullable String profileString = matcher.group(1); - int profile; - if ("1".equals(profileString)) { - profile = CodecProfileLevel.HEVCProfileMain; - } else if ("2".equals(profileString)) { - if (colorInfo != null && colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084) { - profile = CodecProfileLevel.HEVCProfileMain10HDR10; - } else { - // For all other cases, we map to the Main10 profile. Note that this includes HLG - // HDR. On Android 13+, the platform guarantees that a decoder that advertises - // HEVCProfileMain10 will be able to decode HLG. This is not guaranteed for older - // Android versions, but we still map to Main10 for backwards compatibility. - profile = CodecProfileLevel.HEVCProfileMain10; - } - } else if ("6".equals(profileString)) { - // Framework does not have profileLevel.HEVCProfileMultiviewMain defined. - profile = 6; - } else { - Log.w(TAG, "Unknown HEVC profile string: " + profileString); - return null; - } - @Nullable String levelString = parts[3]; - @Nullable Integer level = hevcCodecStringToProfileLevel(levelString); - if (level == null) { - Log.w(TAG, "Unknown HEVC level string: " + levelString); - return null; - } - return new Pair<>(profile, level); - } - - @Nullable - private static Pair getAvcProfileAndLevel(String codec, String[] parts) { - if (parts.length < 2) { - // The codec has fewer parts than required by the AVC codec string format. - Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); - return null; - } - int profileInteger; - int levelInteger; - try { - if (parts[1].length() == 6) { - // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. - profileInteger = Integer.parseInt(parts[1].substring(0, 2), 16); - levelInteger = Integer.parseInt(parts[1].substring(4), 16); - } else if (parts.length >= 3) { - // Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal. - profileInteger = Integer.parseInt(parts[1]); - levelInteger = Integer.parseInt(parts[2]); - } else { - // We don't recognize the format. - Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); - return null; - } - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); - return null; - } - - int profile = avcProfileNumberToConst(profileInteger); - if (profile == -1) { - Log.w(TAG, "Unknown AVC profile: " + profileInteger); - return null; - } - int level = avcLevelNumberToConst(levelInteger); - if (level == -1) { - Log.w(TAG, "Unknown AVC level: " + levelInteger); - return null; - } - return new Pair<>(profile, level); - } - - @Nullable - private static Pair getVp9ProfileAndLevel(String codec, String[] parts) { - if (parts.length < 3) { - Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec); - return null; - } - int profileInteger; - int levelInteger; - try { - profileInteger = Integer.parseInt(parts[1]); - levelInteger = Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec); - return null; - } - - int profile = vp9ProfileNumberToConst(profileInteger); - if (profile == -1) { - Log.w(TAG, "Unknown VP9 profile: " + profileInteger); - return null; - } - int level = vp9LevelNumberToConst(levelInteger); - if (level == -1) { - Log.w(TAG, "Unknown VP9 level: " + levelInteger); - return null; - } - return new Pair<>(profile, level); - } - - @Nullable - private static Pair getAv1ProfileAndLevel( - String codec, String[] parts, @Nullable ColorInfo colorInfo) { - if (parts.length < 4) { - Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec); - return null; - } - int profileInteger; - int levelInteger; - int bitDepthInteger; - try { - profileInteger = Integer.parseInt(parts[1]); - levelInteger = Integer.parseInt(parts[2].substring(0, 2)); - bitDepthInteger = Integer.parseInt(parts[3]); - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec); - return null; - } - - if (profileInteger != 0) { - Log.w(TAG, "Unknown AV1 profile: " + profileInteger); - return null; - } - if (bitDepthInteger != 8 && bitDepthInteger != 10) { - Log.w(TAG, "Unknown AV1 bit depth: " + bitDepthInteger); - return null; - } - int profile; - if (bitDepthInteger == 8) { - profile = CodecProfileLevel.AV1ProfileMain8; - } else if (colorInfo != null - && (colorInfo.hdrStaticInfo != null - || colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG - || colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084)) { - profile = CodecProfileLevel.AV1ProfileMain10HDR10; - } else { - profile = CodecProfileLevel.AV1ProfileMain10; - } - - int level = av1LevelNumberToConst(levelInteger); - if (level == -1) { - Log.w(TAG, "Unknown AV1 level: " + levelInteger); - return null; - } - return new Pair<>(profile, level); - } - /** * Conversion values taken from ISO 14496-10 Table A-1. * @@ -950,31 +720,6 @@ public final class MediaCodecUtil { } } - @Nullable - private static Pair getAacCodecProfileAndLevel(String codec, String[] parts) { - if (parts.length != 3) { - Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); - return null; - } - try { - // Get the object type indication, which is a hexadecimal value (see RFC 6381/ISO 14496-1). - int objectTypeIndication = Integer.parseInt(parts[1], 16); - @Nullable String mimeType = MimeTypes.getMimeTypeFromMp4ObjectType(objectTypeIndication); - if (MimeTypes.AUDIO_AAC.equals(mimeType)) { - // For MPEG-4 audio this is followed by an audio object type indication as a decimal number. - int audioObjectTypeIndication = Integer.parseInt(parts[2]); - int profile = mp4aAudioObjectTypeToProfile(audioObjectTypeIndication); - if (profile != -1) { - // Level is set to zero in AAC decoder CodecProfileLevels. - return new Pair<>(profile, 0); - } - } - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); - } - return null; - } - /** Stably sorts the provided {@code list} in-place, in order of decreasing score. */ private static void sortByScore(List list, ScoreProvider scoreProvider) { Collections.sort(list, (a, b) -> scoreProvider.getScore(b) - scoreProvider.getScore(a)); @@ -1127,335 +872,4 @@ public final class MediaCodecUtil { && tunneling == other.tunneling; } } - - private static int avcProfileNumberToConst(int profileNumber) { - switch (profileNumber) { - case 66: - return CodecProfileLevel.AVCProfileBaseline; - case 77: - return CodecProfileLevel.AVCProfileMain; - case 88: - return CodecProfileLevel.AVCProfileExtended; - case 100: - return CodecProfileLevel.AVCProfileHigh; - case 110: - return CodecProfileLevel.AVCProfileHigh10; - case 122: - return CodecProfileLevel.AVCProfileHigh422; - case 244: - return CodecProfileLevel.AVCProfileHigh444; - default: - return -1; - } - } - - private static int avcLevelNumberToConst(int levelNumber) { - // TODO: Find int for CodecProfileLevel.AVCLevel1b. - switch (levelNumber) { - case 10: - return CodecProfileLevel.AVCLevel1; - case 11: - return CodecProfileLevel.AVCLevel11; - case 12: - return CodecProfileLevel.AVCLevel12; - case 13: - return CodecProfileLevel.AVCLevel13; - case 20: - return CodecProfileLevel.AVCLevel2; - case 21: - return CodecProfileLevel.AVCLevel21; - case 22: - return CodecProfileLevel.AVCLevel22; - case 30: - return CodecProfileLevel.AVCLevel3; - case 31: - return CodecProfileLevel.AVCLevel31; - case 32: - return CodecProfileLevel.AVCLevel32; - case 40: - return CodecProfileLevel.AVCLevel4; - case 41: - return CodecProfileLevel.AVCLevel41; - case 42: - return CodecProfileLevel.AVCLevel42; - case 50: - return CodecProfileLevel.AVCLevel5; - case 51: - return CodecProfileLevel.AVCLevel51; - case 52: - return CodecProfileLevel.AVCLevel52; - default: - return -1; - } - } - - private static int vp9ProfileNumberToConst(int profileNumber) { - switch (profileNumber) { - case 0: - return CodecProfileLevel.VP9Profile0; - case 1: - return CodecProfileLevel.VP9Profile1; - case 2: - return CodecProfileLevel.VP9Profile2; - case 3: - return CodecProfileLevel.VP9Profile3; - default: - return -1; - } - } - - private static int vp9LevelNumberToConst(int levelNumber) { - switch (levelNumber) { - case 10: - return CodecProfileLevel.VP9Level1; - case 11: - return CodecProfileLevel.VP9Level11; - case 20: - return CodecProfileLevel.VP9Level2; - case 21: - return CodecProfileLevel.VP9Level21; - case 30: - return CodecProfileLevel.VP9Level3; - case 31: - return CodecProfileLevel.VP9Level31; - case 40: - return CodecProfileLevel.VP9Level4; - case 41: - return CodecProfileLevel.VP9Level41; - case 50: - return CodecProfileLevel.VP9Level5; - case 51: - return CodecProfileLevel.VP9Level51; - case 60: - return CodecProfileLevel.VP9Level6; - case 61: - return CodecProfileLevel.VP9Level61; - case 62: - return CodecProfileLevel.VP9Level62; - default: - return -1; - } - } - - @Nullable - private static Integer hevcCodecStringToProfileLevel(@Nullable String codecString) { - if (codecString == null) { - return null; - } - switch (codecString) { - case "L30": - return CodecProfileLevel.HEVCMainTierLevel1; - case "L60": - return CodecProfileLevel.HEVCMainTierLevel2; - case "L63": - return CodecProfileLevel.HEVCMainTierLevel21; - case "L90": - return CodecProfileLevel.HEVCMainTierLevel3; - case "L93": - return CodecProfileLevel.HEVCMainTierLevel31; - case "L120": - return CodecProfileLevel.HEVCMainTierLevel4; - case "L123": - return CodecProfileLevel.HEVCMainTierLevel41; - case "L150": - return CodecProfileLevel.HEVCMainTierLevel5; - case "L153": - return CodecProfileLevel.HEVCMainTierLevel51; - case "L156": - return CodecProfileLevel.HEVCMainTierLevel52; - case "L180": - return CodecProfileLevel.HEVCMainTierLevel6; - case "L183": - return CodecProfileLevel.HEVCMainTierLevel61; - case "L186": - return CodecProfileLevel.HEVCMainTierLevel62; - case "H30": - return CodecProfileLevel.HEVCHighTierLevel1; - case "H60": - return CodecProfileLevel.HEVCHighTierLevel2; - case "H63": - return CodecProfileLevel.HEVCHighTierLevel21; - case "H90": - return CodecProfileLevel.HEVCHighTierLevel3; - case "H93": - return CodecProfileLevel.HEVCHighTierLevel31; - case "H120": - return CodecProfileLevel.HEVCHighTierLevel4; - case "H123": - return CodecProfileLevel.HEVCHighTierLevel41; - case "H150": - return CodecProfileLevel.HEVCHighTierLevel5; - case "H153": - return CodecProfileLevel.HEVCHighTierLevel51; - case "H156": - return CodecProfileLevel.HEVCHighTierLevel52; - case "H180": - return CodecProfileLevel.HEVCHighTierLevel6; - case "H183": - return CodecProfileLevel.HEVCHighTierLevel61; - case "H186": - return CodecProfileLevel.HEVCHighTierLevel62; - default: - return null; - } - } - - @Nullable - private static Integer dolbyVisionStringToProfile(@Nullable String profileString) { - if (profileString == null) { - return null; - } - switch (profileString) { - case "00": - return CodecProfileLevel.DolbyVisionProfileDvavPer; - case "01": - return CodecProfileLevel.DolbyVisionProfileDvavPen; - case "02": - return CodecProfileLevel.DolbyVisionProfileDvheDer; - case "03": - return CodecProfileLevel.DolbyVisionProfileDvheDen; - case "04": - return CodecProfileLevel.DolbyVisionProfileDvheDtr; - case "05": - return CodecProfileLevel.DolbyVisionProfileDvheStn; - case "06": - return CodecProfileLevel.DolbyVisionProfileDvheDth; - case "07": - return CodecProfileLevel.DolbyVisionProfileDvheDtb; - case "08": - return CodecProfileLevel.DolbyVisionProfileDvheSt; - case "09": - return CodecProfileLevel.DolbyVisionProfileDvavSe; - case "10": - return CodecProfileLevel.DolbyVisionProfileDvav110; - default: - return null; - } - } - - @Nullable - private static Integer dolbyVisionStringToLevel(@Nullable String levelString) { - if (levelString == null) { - return null; - } - // TODO (Internal: b/179261323): use framework constant for level 13. - switch (levelString) { - case "01": - return CodecProfileLevel.DolbyVisionLevelHd24; - case "02": - return CodecProfileLevel.DolbyVisionLevelHd30; - case "03": - return CodecProfileLevel.DolbyVisionLevelFhd24; - case "04": - return CodecProfileLevel.DolbyVisionLevelFhd30; - case "05": - return CodecProfileLevel.DolbyVisionLevelFhd60; - case "06": - return CodecProfileLevel.DolbyVisionLevelUhd24; - case "07": - return CodecProfileLevel.DolbyVisionLevelUhd30; - case "08": - return CodecProfileLevel.DolbyVisionLevelUhd48; - case "09": - return CodecProfileLevel.DolbyVisionLevelUhd60; - case "10": - return CodecProfileLevel.DolbyVisionLevelUhd120; - case "11": - return CodecProfileLevel.DolbyVisionLevel8k30; - case "12": - return CodecProfileLevel.DolbyVisionLevel8k60; - case "13": - return 0x1000; - default: - return null; - } - } - - private static int av1LevelNumberToConst(int levelNumber) { - // See https://aomediacodec.github.io/av1-spec/av1-spec.pdf Annex A: Profiles and levels for - // more information on mapping AV1 codec strings to levels. - switch (levelNumber) { - case 0: - return CodecProfileLevel.AV1Level2; - case 1: - return CodecProfileLevel.AV1Level21; - case 2: - return CodecProfileLevel.AV1Level22; - case 3: - return CodecProfileLevel.AV1Level23; - case 4: - return CodecProfileLevel.AV1Level3; - case 5: - return CodecProfileLevel.AV1Level31; - case 6: - return CodecProfileLevel.AV1Level32; - case 7: - return CodecProfileLevel.AV1Level33; - case 8: - return CodecProfileLevel.AV1Level4; - case 9: - return CodecProfileLevel.AV1Level41; - case 10: - return CodecProfileLevel.AV1Level42; - case 11: - return CodecProfileLevel.AV1Level43; - case 12: - return CodecProfileLevel.AV1Level5; - case 13: - return CodecProfileLevel.AV1Level51; - case 14: - return CodecProfileLevel.AV1Level52; - case 15: - return CodecProfileLevel.AV1Level53; - case 16: - return CodecProfileLevel.AV1Level6; - case 17: - return CodecProfileLevel.AV1Level61; - case 18: - return CodecProfileLevel.AV1Level62; - case 19: - return CodecProfileLevel.AV1Level63; - case 20: - return CodecProfileLevel.AV1Level7; - case 21: - return CodecProfileLevel.AV1Level71; - case 22: - return CodecProfileLevel.AV1Level72; - case 23: - return CodecProfileLevel.AV1Level73; - default: - return -1; - } - } - - private static int mp4aAudioObjectTypeToProfile(int profileNumber) { - switch (profileNumber) { - case 1: - return CodecProfileLevel.AACObjectMain; - case 2: - return CodecProfileLevel.AACObjectLC; - case 3: - return CodecProfileLevel.AACObjectSSR; - case 4: - return CodecProfileLevel.AACObjectLTP; - case 5: - return CodecProfileLevel.AACObjectHE; - case 6: - return CodecProfileLevel.AACObjectScalable; - case 17: - return CodecProfileLevel.AACObjectERLC; - case 20: - return CodecProfileLevel.AACObjectERScalable; - case 23: - return CodecProfileLevel.AACObjectLD; - case 29: - return CodecProfileLevel.AACObjectHE_PS; - case 39: - return CodecProfileLevel.AACObjectELD; - case 42: - return CodecProfileLevel.AACObjectXHE; - default: - return -1; - } - } } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtilTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtilTest.java index 53e04493f9..b134893c43 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtilTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtilTest.java @@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import android.media.MediaCodecInfo; import android.util.Pair; import androidx.annotation.Nullable; -import androidx.media3.common.C; -import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -204,142 +202,6 @@ public final class MediaCodecUtilTest { 5 }; - @Test - public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_VP9, - "vp09.01.51", - MediaCodecInfo.CodecProfileLevel.VP9Profile1, - MediaCodecInfo.CodecProfileLevel.VP9Level51); - } - - @Test - public void getCodecProfileAndLevel_handlesVp9Profile2CodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_VP9, - "vp09.02.10", - MediaCodecInfo.CodecProfileLevel.VP9Profile2, - MediaCodecInfo.CodecProfileLevel.VP9Level1); - } - - @Test - public void getCodecProfileAndLevel_handlesFullVp9CodecString() { - // Example from https://www.webmproject.org/vp9/mp4/#codecs-parameter-string. - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_VP9, - "vp09.02.10.10.01.09.16.09.01", - MediaCodecInfo.CodecProfileLevel.VP9Profile2, - MediaCodecInfo.CodecProfileLevel.VP9Level1); - } - - @Test - public void getCodecProfileAndLevel_handlesDolbyVisionCodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_DOLBY_VISION, - "dvh1.05.05", - MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn, - MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60); - } - - @Test - public void getCodecProfileAndLevel_handlesDolbyVisionProfile10CodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_DOLBY_VISION, - "dav1.10.09", - MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, - MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60); - } - - @Test - public void getCodecProfileAndLevel_handlesAv1ProfileMain8CodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_AV1, - "av01.0.10M.08", - MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8, - MediaCodecInfo.CodecProfileLevel.AV1Level42); - } - - @Test - public void getCodecProfileAndLevel_handlesAv1ProfileMain10CodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_AV1, - "av01.0.20M.10", - MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, - MediaCodecInfo.CodecProfileLevel.AV1Level7); - } - - @Test - public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithHdrInfoSet() { - ColorInfo colorInfo = - new ColorInfo.Builder() - .setColorSpace(C.COLOR_SPACE_BT709) - .setColorRange(C.COLOR_RANGE_LIMITED) - .setColorTransfer(C.COLOR_TRANSFER_SDR) - .setHdrStaticInfo(new byte[] {1, 2, 3, 4, 5, 6, 7}) - .build(); - Format format = - new Format.Builder() - .setSampleMimeType(MimeTypes.VIDEO_AV1) - .setCodecs("av01.0.21M.10") - .setColorInfo(colorInfo) - .build(); - assertCodecProfileAndLevelForFormat( - format, - MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10, - MediaCodecInfo.CodecProfileLevel.AV1Level71); - } - - @Test - public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithoutHdrInfoSet() { - ColorInfo colorInfo = - new ColorInfo.Builder() - .setColorSpace(C.COLOR_SPACE_BT709) - .setColorRange(C.COLOR_RANGE_LIMITED) - .setColorTransfer(C.COLOR_TRANSFER_HLG) - .build(); - Format format = - new Format.Builder() - .setSampleMimeType(MimeTypes.VIDEO_AV1) - .setCodecs("av01.0.21M.10") - .setColorInfo(colorInfo) - .build(); - assertCodecProfileAndLevelForFormat( - format, - MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10, - MediaCodecInfo.CodecProfileLevel.AV1Level71); - } - - @Test - public void getCodecProfileAndLevel_handlesFullAv1CodecString() { - // Example from https://aomediacodec.github.io/av1-isobmff/#codecsparam. - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_AV1, - "av01.0.04M.10.0.112.09.16.09.0", - MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, - MediaCodecInfo.CodecProfileLevel.AV1Level3); - } - - @Test - public void getCodecProfileAndLevel_rejectsNullCodecString() { - Format format = new Format.Builder().setCodecs(null).build(); - assertThat(MediaCodecUtil.getCodecProfileAndLevel(format)).isNull(); - } - - @Test - public void getCodecProfileAndLevel_rejectsEmptyCodecString() { - Format format = new Format.Builder().setCodecs("").build(); - assertThat(MediaCodecUtil.getCodecProfileAndLevel(format)).isNull(); - } - - @Test - public void getCodecProfileAndLevel_handlesMvHevcCodecString() { - assertCodecProfileAndLevelForCodecsString( - MimeTypes.VIDEO_MV_HEVC, - "hvc1.6.40.L120.BF.80", - /* profile= */ 6, - MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4); - } - @Test public void getHevcBaseLayerCodecProfileAndLevel_handlesFallbackFromMvHevc() { Format format = @@ -364,21 +226,6 @@ public final class MediaCodecUtilTest { assertThat(MediaCodecUtil.getHevcBaseLayerCodecProfileAndLevel(format)).isNull(); } - private static void assertCodecProfileAndLevelForCodecsString( - String sampleMimeType, String codecs, int profile, int level) { - Format format = - new Format.Builder().setSampleMimeType(sampleMimeType).setCodecs(codecs).build(); - assertCodecProfileAndLevelForFormat(format, profile, level); - } - - private static void assertCodecProfileAndLevelForFormat(Format format, int profile, int level) { - @Nullable - Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); - assertThat(codecProfileAndLevel).isNotNull(); - assertThat(codecProfileAndLevel.first).isEqualTo(profile); - assertThat(codecProfileAndLevel.second).isEqualTo(level); - } - private static void assertHevcBaseLayerCodecProfileAndLevelForFormat( Format format, int profile, int level) { @Nullable