diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index f3936e5dc2..455ee6c034 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -81,10 +81,13 @@ public final class MediaCodecUtil { private static final Map DOLBY_VISION_STRING_TO_LEVEL; private static final String CODEC_ID_DVHE = "dvhe"; private static final String CODEC_ID_DVH1 = "dvh1"; + // AV1. + private static final SparseIntArray AV1_LEVEL_NUMBER_TO_CONST; + private static final String CODEC_ID_AV01 = "av01"; // MP4A AAC. private static final SparseIntArray MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE; private static final String CODEC_ID_MP4A = "mp4a"; - + // Lazily initialized. private static int maxH264DecodableFrameSize = -1; @@ -239,8 +242,6 @@ public final class MediaCodecUtil { if (codec == null) { return null; } - // TODO: Check codec profile/level for AV1 once targeting Android Q and [Internal: b/128552878] - // has been fixed. String[] parts = codec.split("\\."); switch (parts[0]) { case CODEC_ID_AVC1: @@ -254,6 +255,8 @@ public final class MediaCodecUtil { case CODEC_ID_DVHE: case CODEC_ID_DVH1: return getDolbyVisionProfileAndLevel(codec, parts); + case CODEC_ID_AV01: + return getAv1ProfileAndLevel(codec, parts); case CODEC_ID_MP4A: return getAacCodecProfileAndLevel(codec, parts); default: @@ -684,6 +687,48 @@ public final class MediaCodecUtil { return new Pair<>(profile, level); } + private static Pair getAv1ProfileAndLevel(String codec, String[] parts) { + 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; + } + + // TODO: Recognize HDR profiles. Currently, the profile is assumed to be either Main8 or Main10. + // See [Internal: b/124435216]. + 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 { + profile = CodecProfileLevel.AV1ProfileMain10; + } + + int level = AV1_LEVEL_NUMBER_TO_CONST.get(levelInteger, -1); + 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. * @@ -1010,6 +1055,32 @@ public final class MediaCodecUtil { DOLBY_VISION_STRING_TO_LEVEL.put("08", CodecProfileLevel.DolbyVisionLevelUhd48); DOLBY_VISION_STRING_TO_LEVEL.put("09", CodecProfileLevel.DolbyVisionLevelUhd60); + AV1_LEVEL_NUMBER_TO_CONST = new SparseIntArray(); + AV1_LEVEL_NUMBER_TO_CONST.put(0, CodecProfileLevel.AV1Level2); + AV1_LEVEL_NUMBER_TO_CONST.put(1, CodecProfileLevel.AV1Level21); + AV1_LEVEL_NUMBER_TO_CONST.put(2, CodecProfileLevel.AV1Level22); + AV1_LEVEL_NUMBER_TO_CONST.put(3, CodecProfileLevel.AV1Level23); + AV1_LEVEL_NUMBER_TO_CONST.put(4, CodecProfileLevel.AV1Level3); + AV1_LEVEL_NUMBER_TO_CONST.put(5, CodecProfileLevel.AV1Level31); + AV1_LEVEL_NUMBER_TO_CONST.put(6, CodecProfileLevel.AV1Level32); + AV1_LEVEL_NUMBER_TO_CONST.put(7, CodecProfileLevel.AV1Level33); + AV1_LEVEL_NUMBER_TO_CONST.put(8, CodecProfileLevel.AV1Level4); + AV1_LEVEL_NUMBER_TO_CONST.put(9, CodecProfileLevel.AV1Level41); + AV1_LEVEL_NUMBER_TO_CONST.put(10, CodecProfileLevel.AV1Level42); + AV1_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AV1Level43); + AV1_LEVEL_NUMBER_TO_CONST.put(12, CodecProfileLevel.AV1Level5); + AV1_LEVEL_NUMBER_TO_CONST.put(13, CodecProfileLevel.AV1Level51); + AV1_LEVEL_NUMBER_TO_CONST.put(14, CodecProfileLevel.AV1Level52); + AV1_LEVEL_NUMBER_TO_CONST.put(15, CodecProfileLevel.AV1Level53); + AV1_LEVEL_NUMBER_TO_CONST.put(16, CodecProfileLevel.AV1Level6); + AV1_LEVEL_NUMBER_TO_CONST.put(17, CodecProfileLevel.AV1Level61); + AV1_LEVEL_NUMBER_TO_CONST.put(18, CodecProfileLevel.AV1Level62); + AV1_LEVEL_NUMBER_TO_CONST.put(19, CodecProfileLevel.AV1Level63); + AV1_LEVEL_NUMBER_TO_CONST.put(20, CodecProfileLevel.AV1Level7); + AV1_LEVEL_NUMBER_TO_CONST.put(21, CodecProfileLevel.AV1Level71); + AV1_LEVEL_NUMBER_TO_CONST.put(22, CodecProfileLevel.AV1Level72); + AV1_LEVEL_NUMBER_TO_CONST.put(23, CodecProfileLevel.AV1Level73); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE = new SparseIntArray(); MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(1, CodecProfileLevel.AACObjectMain); MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(2, CodecProfileLevel.AACObjectLC); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java index a84c6f5d7b..05d92e0783 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtilTest.java @@ -60,6 +60,31 @@ public final class MediaCodecUtilTest { MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60); } + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain8CodecString() { + assertCodecProfileAndLevelForCodecsString( + "av01.0.10M.08", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8, + MediaCodecInfo.CodecProfileLevel.AV1Level42); + } + + @Test + public void getCodecProfileAndLevel_handlesAv1ProfileMain10CodecString() { + assertCodecProfileAndLevelForCodecsString( + "av01.0.20M.10", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, + MediaCodecInfo.CodecProfileLevel.AV1Level7); + } + + @Test + public void getCodecProfileAndLevel_handlesFullAv1CodecString() { + // Example from https://aomediacodec.github.io/av1-isobmff/#codecsparam. + assertCodecProfileAndLevelForCodecsString( + "av01.0.04M.10.0.112.09.16.09.0", + MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, + MediaCodecInfo.CodecProfileLevel.AV1Level3); + } + @Test public void getCodecProfileAndLevel_rejectsNullCodecString() { assertThat(MediaCodecUtil.getCodecProfileAndLevel(/* codec= */ null)).isNull();