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:
parent
a1357befff
commit
327b1c8ad8
@ -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:
|
||||||
|
@ -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() {}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user