Move getCodecProfileAndLevel to CodecSpecificDataUtil

`Muxer` module needs to use this method, hence moved to common.

This CL also makes `getHevcProfileAndLevel` public because this is used
in `MediaCodecUtil`.

PiperOrigin-RevId: 671739166
This commit is contained in:
sheenachhabra 2024-09-06 06:48:48 -07:00 committed by Copybara-Service
parent a1357befff
commit 327b1c8ad8
5 changed files with 786 additions and 748 deletions

View File

@ -7,6 +7,9 @@
* Fix `MediaCodec.CryptoException` sometimes being reported as an * Fix `MediaCodec.CryptoException` sometimes being reported as an
"unexpected runtime error" when `MediaCodec` is operated in asynchronous "unexpected runtime error" when `MediaCodec` is operated in asynchronous
mode (default behaviour on API 31+). mode (default behaviour on API 31+).
* Deprecated `MediaCodecUtil.getCodecProfileAndLevel`. Use
`androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel`
instead.
* Transformer: * Transformer:
* Track Selection: * Track Selection:
* Extractors: * Extractors:

View File

@ -17,16 +17,23 @@ package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import android.annotation.SuppressLint;
import android.media.MediaCodecInfo;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Provides utilities for handling various types of codec-specific data. */ /** Provides utilities for handling various types of codec-specific data. */
@SuppressLint("InlinedApi")
@UnstableApi @UnstableApi
public final class CodecSpecificDataUtil { public final class CodecSpecificDataUtil {
@ -40,6 +47,24 @@ public final class CodecSpecificDataUtil {
private static final int EXTENDED_PAR = 0x0F; private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00; 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 <a * Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>). * href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
@ -234,6 +259,96 @@ public final class CodecSpecificDataUtil {
return builder.toString(); 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<Integer, Integer> 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<Integer, Integer> 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. * 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; return true;
} }
@Nullable
private static Pair<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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() {} private CodecSpecificDataUtil() {}
} }

View File

@ -15,9 +15,16 @@
*/ */
package androidx.media3.common.util; package androidx.media3.common.util;
import static androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.media.MediaCodecInfo;
import android.util.Pair; 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 androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -45,4 +52,154 @@ public class CodecSpecificDataUtilTest {
assertThat(sampleRateAndChannelCount.first).isEqualTo(96000); assertThat(sampleRateAndChannelCount.first).isEqualTo(96000);
assertThat(sampleRateAndChannelCount.second).isEqualTo(2); 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<Integer, Integer> codecProfileAndLevel = getCodecProfileAndLevel(format);
assertThat(codecProfileAndLevel).isNotNull();
assertThat(codecProfileAndLevel.first).isEqualTo(profile);
assertThat(codecProfileAndLevel.second).isEqualTo(level);
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer.mediacodec; package androidx.media3.exoplayer.mediacodec;
import static androidx.media3.common.util.CodecSpecificDataUtil.getHevcProfileAndLevel;
import static java.lang.Math.max; import static java.lang.Math.max;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -28,22 +29,20 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.CodecSpecificDataUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.NalUnitUtil; import androidx.media3.container.NalUnitUtil;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.InlineMe;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; 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.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -66,25 +65,10 @@ public final class MediaCodecUtil {
} }
private static final String TAG = "MediaCodecUtil"; private static final String TAG = "MediaCodecUtil";
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
@GuardedBy("MediaCodecUtil.class") @GuardedBy("MediaCodecUtil.class")
private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>(); private static final HashMap<CodecKey, List<MediaCodecInfo>> 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. // Lazily initialized.
private static int maxH264DecodableFrameSize = -1; 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 * @deprecated Use {@link CodecSpecificDataUtil#getCodecProfileAndLevel(Format)}.
* 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.
*/ */
@InlineMe(
replacement = "CodecSpecificDataUtil.getCodecProfileAndLevel(format)",
imports = {"androidx.media3.common.util.CodecSpecificDataUtil"})
@Deprecated
@Nullable @Nullable
public static Pair<Integer, Integer> getCodecProfileAndLevel(Format format) { public static Pair<Integer, Integer> getCodecProfileAndLevel(Format format) {
if (format.codecs == null) { return CodecSpecificDataUtil.getCodecProfileAndLevel(format);
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;
}
} }
/** /**
@ -716,196 +676,6 @@ public final class MediaCodecUtil {
return codecInfo.isVendor(); return codecInfo.isVendor();
} }
@Nullable
private static Pair<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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. * Conversion values taken from ISO 14496-10 Table A-1.
* *
@ -950,31 +720,6 @@ public final class MediaCodecUtil {
} }
} }
@Nullable
private static Pair<Integer, Integer> 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. */ /** Stably sorts the provided {@code list} in-place, in order of decreasing score. */
private static <T> void sortByScore(List<T> list, ScoreProvider<T> scoreProvider) { private static <T> void sortByScore(List<T> list, ScoreProvider<T> scoreProvider) {
Collections.sort(list, (a, b) -> scoreProvider.getScore(b) - scoreProvider.getScore(a)); Collections.sort(list, (a, b) -> scoreProvider.getScore(b) - scoreProvider.getScore(a));
@ -1127,335 +872,4 @@ public final class MediaCodecUtil {
&& tunneling == other.tunneling; && 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;
}
}
} }

View File

@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -204,142 +202,6 @@ public final class MediaCodecUtilTest {
5 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 @Test
public void getHevcBaseLayerCodecProfileAndLevel_handlesFallbackFromMvHevc() { public void getHevcBaseLayerCodecProfileAndLevel_handlesFallbackFromMvHevc() {
Format format = Format format =
@ -364,21 +226,6 @@ public final class MediaCodecUtilTest {
assertThat(MediaCodecUtil.getHevcBaseLayerCodecProfileAndLevel(format)).isNull(); 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<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
assertThat(codecProfileAndLevel).isNotNull();
assertThat(codecProfileAndLevel.first).isEqualTo(profile);
assertThat(codecProfileAndLevel.second).isEqualTo(level);
}
private static void assertHevcBaseLayerCodecProfileAndLevelForFormat( private static void assertHevcBaseLayerCodecProfileAndLevelForFormat(
Format format, int profile, int level) { Format format, int profile, int level) {
@Nullable @Nullable