diff --git a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java index d64755d6dfd..56ce77407f7 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java +++ b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java @@ -444,7 +444,8 @@ public static String getMediaMimeType(@Nullable String codec) { } else if (codec.startsWith("dvav") || codec.startsWith("dva1") || codec.startsWith("dvhe") - || codec.startsWith("dvh1")) { + || codec.startsWith("dvh1") + || codec.startsWith("dav1")) { return MimeTypes.VIDEO_DOLBY_VISION; } else if (codec.startsWith("av01")) { return MimeTypes.VIDEO_AV1; @@ -599,8 +600,8 @@ public static boolean isDolbyVisionCodec( if (codecs == null) { return false; } - if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1")) { - // profile 5 + if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1") || codecs.startsWith("dav1")) { + // profiles 5, 10.0 and 20.0 return true; } if (supplementalCodecs == null) { diff --git a/libraries/container/src/main/java/androidx/media3/container/Mp4Box.java b/libraries/container/src/main/java/androidx/media3/container/Mp4Box.java index 937d7a470f9..08e6d17d33e 100644 --- a/libraries/container/src/main/java/androidx/media3/container/Mp4Box.java +++ b/libraries/container/src/main/java/androidx/media3/container/Mp4Box.java @@ -117,6 +117,9 @@ public abstract class Mp4Box { @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_dvvC = 0x64767643; + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_dav1 = 0x64617631; + @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_dvwC = 0x64767743; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index 5d2dfc4f45e..b76d73329f8 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -1682,7 +1682,10 @@ protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) // * b/229399008#comment9 // * https://github.com/androidx/media/issues/2408 if ((Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_AV1) - || Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_VP9)) + || Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_VP9) + || (Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_DOLBY_VISION) + && Objects.equals(MediaCodecUtil.getDolbyVisionBlMimeType(newFormat), + MimeTypes.VIDEO_AV1))) && !newFormat.initializationData.isEmpty()) { newFormat = newFormat.buildUpon().setInitializationData(null).build(); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java index 887bfbf63f3..ed4d9220769 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java @@ -31,6 +31,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.CodecSpecificDataUtil; @@ -365,6 +366,33 @@ public static Pair getHevcBaseLayerCodecProfileAndLevel(Format return getHevcProfileAndLevel(codecs, parts, format.colorInfo); } + /** + * Returns a Dolby Vision base layer codec MIME type of the provided {@link Format}. + * + * @param format The media format. + * @return A Dolby Vision base layer MIME type, or null if a Dolby Vision profile is not + * identified. + */ + @Nullable + public static String getDolbyVisionBlMimeType(Format format) { + + if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { + @Nullable Pair codecProfileAndLevel = getCodecProfileAndLevel(format); + if (codecProfileAndLevel != null) { + int profile = codecProfileAndLevel.first; + if (profile == CodecProfileLevel.DolbyVisionProfileDvheDtr + || profile == CodecProfileLevel.DolbyVisionProfileDvheSt) { + return MimeTypes.VIDEO_H265; + } else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) { + return MimeTypes.VIDEO_H264; + } else if (profile == CodecProfileLevel.DolbyVisionProfileDvav110) { + return MimeTypes.VIDEO_AV1; + } + } + } + return null; + } + /** * Returns an alternative codec MIME type (besides the default {@link Format#sampleMimeType}) that * can be used to decode samples of the provided {@link Format}. @@ -394,6 +422,11 @@ public static String getAlternativeCodecMimeType(Format format) { } else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) { return MimeTypes.VIDEO_H264; } else if (profile == CodecProfileLevel.DolbyVisionProfileDvav110) { + if (format.colorInfo != null + && format.colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084 + && format.colorInfo.colorRange == C.COLOR_RANGE_FULL) { + return null; + } return MimeTypes.VIDEO_AV1; } } diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java index abd50fee484..6b73e64411d 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java @@ -27,6 +27,7 @@ import android.util.Xml; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.common.DrmInitData; import androidx.media3.common.DrmInitData.SchemeData; import androidx.media3.common.Format; @@ -848,6 +849,9 @@ protected Format buildFormat( codecs = MimeTypes.CODEC_E_AC3_JOC; } } + + ColorInfo colorInfo = getColorInfoForFormat(codecs, supplementalCodecs, supplementalProfiles); + if (MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) { sampleMimeType = MimeTypes.VIDEO_DOLBY_VISION; codecs = supplementalCodecs != null ? supplementalCodecs : codecs; @@ -868,6 +872,7 @@ protected Format buildFormat( .setPeakBitrate(bitrate) .setSelectionFlags(selectionFlags) .setRoleFlags(roleFlags) + .setColorInfo(colorInfo) .setLanguage(language) .setTileCountHorizontal(tileCounts != null ? tileCounts.first : Format.NO_VALUE) .setTileCountVertical(tileCounts != null ? tileCounts.second : Format.NO_VALUE); @@ -2174,6 +2179,47 @@ private boolean isDvbProfileDeclared(String[] profiles) { return false; } + private static ColorInfo getColorInfoForFormat( + @Nullable String codecs, + @Nullable String supplementalCodecs, + @Nullable String supplementalProfiles) { + + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; + + if (MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) { + if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1") || codecs.startsWith("dav1")) { + // profiles 5, 10.0 and 20.0 + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_ST2084; + colorRange = C.COLOR_RANGE_FULL; + } else if (supplementalProfiles != null) { + if (supplementalProfiles.equals("db1p")) { + //BL signal cross-compatibility ID = 1 (e.g profile 8.1) + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_ST2084; + colorRange = C.COLOR_RANGE_LIMITED; + } else if (supplementalProfiles.startsWith("db4")) { // db4g or db4h + //BL signal cross-compatibility ID = 4 (e.g profile 8.4) + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_HLG; + colorRange = C.COLOR_RANGE_LIMITED; + } + } + } + + if (colorSpace == Format.NO_VALUE) { + return null; + } + + return new ColorInfo.Builder() + .setColorSpace(colorSpace) + .setColorRange(colorRange) + .setColorTransfer(colorTransfer) + .build(); + } + /** A parsed Representation element. */ protected static final class RepresentationInfo { diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java index 40b38a32189..e7308320c09 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsMediaPeriod.java @@ -913,6 +913,7 @@ private static Format deriveVideoFormat(Format variantFormat) { .setFrameRate(variantFormat.frameRate) .setSelectionFlags(variantFormat.selectionFlags) .setRoleFlags(variantFormat.roleFlags) + .setColorInfo(variantFormat.colorInfo) .build(); } diff --git a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java index 9b214868325..45ac43fd672 100644 --- a/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java +++ b/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/playlist/HlsPlaylistParser.java @@ -29,6 +29,7 @@ import android.util.Base64; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.common.DrmInitData; import androidx.media3.common.DrmInitData.SchemeData; import androidx.media3.common.Format; @@ -394,6 +395,47 @@ private static int skipIgnorableWhitespace(BufferedReader reader, boolean skipLi return c; } + private static ColorInfo getColorInfoForFormat( + @Nullable String codecs, + @Nullable String supplementalCodecs, + @Nullable String supplementalProfiles) { + + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; + + if (MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) { + if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1") || codecs.startsWith("dav1")) { + // profiles 5, 10.0 and 20.0 + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_ST2084; + colorRange = C.COLOR_RANGE_FULL; + } else if (supplementalProfiles != null) { + if (supplementalProfiles.equals("db1p")) { + //BL signal cross-compatibility ID = 1 (e.g profile 8.1) + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_ST2084; + colorRange = C.COLOR_RANGE_LIMITED; + } else if (supplementalProfiles.startsWith("db4")) { // db4g or db4h + //BL signal cross-compatibility ID = 4 (e.g profile 8.4) + colorSpace = C.COLOR_SPACE_BT2020; + colorTransfer = C.COLOR_TRANSFER_HLG; + colorRange = C.COLOR_RANGE_LIMITED; + } + } + } + + if (colorSpace == Format.NO_VALUE) { + return null; + } + + return new ColorInfo.Builder() + .setColorSpace(colorSpace) + .setColorRange(colorRange) + .setColorTransfer(colorTransfer) + .build(); + } + private static boolean isDolbyVisionFormat( @Nullable String videoRange, @Nullable String codecs, @@ -484,6 +526,10 @@ private static HlsMultivariantPlaylist parseMultivariantPlaylist( supplementalProfiles = codecsAndProfiles[1]; } } + + ColorInfo colorInfo = + getColorInfoForFormat(codecs, supplementalCodecs, supplementalProfiles); + String videoCodecs = Util.getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); if (isDolbyVisionFormat( videoRange, videoCodecs, supplementalCodecs, supplementalProfiles)) { @@ -545,6 +591,7 @@ private static HlsMultivariantPlaylist parseMultivariantPlaylist( .setHeight(height) .setFrameRate(frameRate) .setRoleFlags(roleFlags) + .setColorInfo(colorInfo) .build(); Variant variant = new Variant( diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java index b6eddbb3917..fd99894e71c 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java @@ -1228,7 +1228,8 @@ private static StsdData parseStsd( || childAtomType == Mp4Box.TYPE_dva1 || childAtomType == Mp4Box.TYPE_dvhe || childAtomType == Mp4Box.TYPE_dvh1 - || childAtomType == Mp4Box.TYPE_apv1) { + || childAtomType == Mp4Box.TYPE_apv1 + || childAtomType == Mp4Box.TYPE_dav1) { parseVideoSampleEntry( stsd, childAtomType,