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