diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 434f4f2c74..ae37ecd6ca 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -67,6 +67,7 @@ * Add `uid` to `Timeline.Window` to uniquely identify window instances. * Fix decoder selection for E-AC3 JOC streams ([#6398](https://github.com/google/ExoPlayer/issues/6398)). +* Fix Dolby Vision fallback to AVC and HEVC. ### 2.10.4 ### 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 1293f14d08..3ec0094e60 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,8 +81,6 @@ public final class MediaCodecUtil { // Dolby Vision. private static final Map DOLBY_VISION_STRING_TO_PROFILE; 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"; @@ -245,6 +243,10 @@ public final class MediaCodecUtil { 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: @@ -254,9 +256,6 @@ public final class MediaCodecUtil { case CODEC_ID_HEV1: case CODEC_ID_HVC1: return getHevcProfileAndLevel(format.codecs, parts); - case CODEC_ID_DVHE: - case CODEC_ID_DVH1: - return getDolbyVisionProfileAndLevel(format.codecs, parts); case CODEC_ID_AV01: return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo); case CODEC_ID_MP4A: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index f73dde58c3..a9f571c085 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.Point; import android.media.MediaCodec; import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; @@ -393,15 +394,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { format.sampleMimeType, requiresSecureDecoder, requiresTunnelingDecoder); decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format); if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { - // Fallback to primary decoders for H.265/HEVC or H.264/AVC for the relevant DV profiles. + // Fall back to H.264/AVC or H.265/HEVC for the relevant DV profiles. + @Nullable Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel != null) { int profile = codecProfileAndLevel.first; - if (profile == 4 || profile == 8) { + if (profile == CodecProfileLevel.DolbyVisionProfileDvheDtr + || profile == CodecProfileLevel.DolbyVisionProfileDvheSt) { decoderInfos.addAll( mediaCodecSelector.getDecoderInfos( MimeTypes.VIDEO_H265, requiresSecureDecoder, requiresTunnelingDecoder)); - } else if (profile == 9) { + } else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) { decoderInfos.addAll( mediaCodecSelector.getDecoderInfos( MimeTypes.VIDEO_H264, requiresSecureDecoder, requiresTunnelingDecoder)); 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 e8d65255c3..3693e494d4 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 @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import android.media.MediaCodecInfo; import android.util.Pair; +import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -34,6 +35,7 @@ public final class MediaCodecUtilTest { @Test public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() { assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_VP9, "vp09.01.51", MediaCodecInfo.CodecProfileLevel.VP9Profile1, MediaCodecInfo.CodecProfileLevel.VP9Level51); @@ -42,6 +44,7 @@ public final class MediaCodecUtilTest { @Test public void getCodecProfileAndLevel_handlesVp9Profile2CodecString() { assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_VP9, "vp09.02.10", MediaCodecInfo.CodecProfileLevel.VP9Profile2, MediaCodecInfo.CodecProfileLevel.VP9Level1); @@ -51,6 +54,7 @@ public final class MediaCodecUtilTest { 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); @@ -59,6 +63,7 @@ public final class MediaCodecUtilTest { @Test public void getCodecProfileAndLevel_handlesDolbyVisionCodecString() { assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_DOLBY_VISION, "dvh1.05.05", MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn, MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60); @@ -67,6 +72,7 @@ public final class MediaCodecUtilTest { @Test public void getCodecProfileAndLevel_handlesAv1ProfileMain8CodecString() { assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_AV1, "av01.0.10M.08", MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8, MediaCodecInfo.CodecProfileLevel.AV1Level42); @@ -75,6 +81,7 @@ public final class MediaCodecUtilTest { @Test public void getCodecProfileAndLevel_handlesAv1ProfileMain10CodecString() { assertCodecProfileAndLevelForCodecsString( + MimeTypes.VIDEO_AV1, "av01.0.20M.10", MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10, MediaCodecInfo.CodecProfileLevel.AV1Level7); @@ -91,7 +98,7 @@ public final class MediaCodecUtilTest { Format format = Format.createVideoSampleFormat( /* id= */ null, - /* sampleMimeType= */ MimeTypes.VIDEO_UNKNOWN, + MimeTypes.VIDEO_AV1, /* codecs= */ "av01.0.21M.10", /* bitrate= */ Format.NO_VALUE, /* maxInputSize= */ Format.NO_VALUE, @@ -122,7 +129,7 @@ public final class MediaCodecUtilTest { Format format = Format.createVideoSampleFormat( /* id= */ null, - /* sampleMimeType= */ MimeTypes.VIDEO_UNKNOWN, + MimeTypes.VIDEO_AV1, /* codecs= */ "av01.0.21M.10", /* bitrate= */ Format.NO_VALUE, /* maxInputSize= */ Format.NO_VALUE, @@ -146,6 +153,7 @@ public final class MediaCodecUtilTest { 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); @@ -186,11 +194,11 @@ public final class MediaCodecUtilTest { } private static void assertCodecProfileAndLevelForCodecsString( - String codecs, int profile, int level) { + String mimeType, String codecs, int profile, int level) { Format format = Format.createVideoSampleFormat( /* id= */ null, - /* sampleMimeType= */ MimeTypes.VIDEO_UNKNOWN, + mimeType, /* codecs= */ codecs, /* bitrate= */ Format.NO_VALUE, /* maxInputSize= */ Format.NO_VALUE, @@ -203,6 +211,7 @@ public final class MediaCodecUtilTest { } private static void assertCodecProfileAndLevelForFormat(Format format, int profile, int level) { + @Nullable Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); assertThat(codecProfileAndLevel).isNotNull(); assertThat(codecProfileAndLevel.first).isEqualTo(profile);