diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c9b3bb1a32..b7c4f7a322 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -42,10 +42,15 @@ * Cronet Extension: * RTMP Extension: * HLS Extension: + * Parse `SUPPLEMENTAL-CODECS` tag from HLS playlist to detect Dolby Vision + formats ([#1785](https://github.com/androidx/media/pull/1785)). * DASH Extension: * Fix issue when calculating the update interval for ad insertion in multi-period live streams ([#1698](https://github.com/androidx/media/issues/1698)). + * Parse `scte214:supplementalCodecs` attribute from DASH manifest to + detect Dolby Vision formats + ([#1785](https://github.com/androidx/media/pull/1785)). * Smooth Streaming Extension: * RTSP Extension: * Decoder Extensions (FFmpeg, VP9, AV1, etc.): 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 57fd61010e..1c1e0524d1 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java +++ b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java @@ -580,6 +580,36 @@ public final class MimeTypes { } } + /** + * Returns whether the given {@code codecs} and {@code supplementalCodecs} correspond to a valid + * Dolby Vision codec. + * + * @param codecs An RFC 6381 codecs string for the base codec. may be null. + * @param supplementalCodecs An optional RFC 6381 codecs string for supplemental codecs. + * @return Whether the given {@code codecs} and {@code supplementalCodecs} correspond to a valid + * Dolby Vision codec. + */ + @UnstableApi + public static boolean isDolbyVisionCodec( + @Nullable String codecs, @Nullable String supplementalCodecs) { + if (codecs == null) { + return false; + } + if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1")) { + // profile 5 + return true; + } + if (supplementalCodecs == null) { + return false; + } + // profiles 8, 9 and 10 + return (supplementalCodecs.startsWith("dvhe") && codecs.startsWith("hev1")) + || (supplementalCodecs.startsWith("dvh1") && codecs.startsWith("hvc1")) + || (supplementalCodecs.startsWith("dvav") && codecs.startsWith("avc3")) + || (supplementalCodecs.startsWith("dva1") && codecs.startsWith("avc1")) + || (supplementalCodecs.startsWith("dav1") && codecs.startsWith("av01")); + } + /** * Returns the {@link C.TrackType track type} constant corresponding to a specified MIME type, * which may be {@link C#TRACK_TYPE_UNKNOWN} if it could not be determined. diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index d96dcf71e7..1b9e00935f 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -2125,6 +2125,33 @@ public final class Util { return builder.length() > 0 ? builder.toString() : null; } + /** + * Returns a copy of {@code codecs} without the codecs whose track type matches {@code trackType}. + * + * @param codecs A codec sequence string, as defined in RFC 6381. + * @param trackType The {@link C.TrackType track type}. + * @return A copy of {@code codecs} without the codecs whose track type matches {@code trackType}. + * If this ends up empty, or {@code codecs} is null, returns null. + */ + @UnstableApi + @Nullable + public static String getCodecsWithoutType(@Nullable String codecs, @C.TrackType int trackType) { + String[] codecArray = splitCodecs(codecs); + if (codecArray.length == 0) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (String codec : codecArray) { + if (trackType != MimeTypes.getTrackTypeOfCodec(codec)) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); + } + } + return builder.length() > 0 ? builder.toString() : null; + } + /** * Splits a codecs sequence string, as defined in RFC 6381, into individual codec strings. * 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 11eddae3d8..09d9282aa9 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 @@ -414,6 +414,8 @@ public class DashManifestParser extends DefaultHandler String mimeType = xpp.getAttributeValue(null, "mimeType"); String codecs = xpp.getAttributeValue(null, "codecs"); + String supplementalCodecs = xpp.getAttributeValue(null, "scte214:supplementalCodecs"); + String supplementalProfiles = xpp.getAttributeValue(null, "scte214:supplementalProfiles"); int width = parseInt(xpp, "width", Format.NO_VALUE); int height = parseInt(xpp, "height", Format.NO_VALUE); float frameRate = parseFrameRate(xpp, Format.NO_VALUE); @@ -470,6 +472,8 @@ public class DashManifestParser extends DefaultHandler !baseUrls.isEmpty() ? baseUrls : parentBaseUrls, mimeType, codecs, + supplementalCodecs, + supplementalProfiles, width, height, frameRate, @@ -688,6 +692,8 @@ public class DashManifestParser extends DefaultHandler List parentBaseUrls, @Nullable String adaptationSetMimeType, @Nullable String adaptationSetCodecs, + @Nullable String adaptationSetSupplementalCodecs, + @Nullable String adaptationSetSupplementalProfiles, int adaptationSetWidth, int adaptationSetHeight, float adaptationSetFrameRate, @@ -711,6 +717,10 @@ public class DashManifestParser extends DefaultHandler String mimeType = parseString(xpp, "mimeType", adaptationSetMimeType); String codecs = parseString(xpp, "codecs", adaptationSetCodecs); + String supplementalCodecs = + parseString(xpp, "scte214:supplementalCodecs", adaptationSetSupplementalCodecs); + String supplementalProfiles = + parseString(xpp, "scte214:supplementalProfiles", adaptationSetSupplementalProfiles); int width = parseInt(xpp, "width", adaptationSetWidth); int height = parseInt(xpp, "height", adaptationSetHeight); float frameRate = parseFrameRate(xpp, adaptationSetFrameRate); @@ -796,6 +806,8 @@ public class DashManifestParser extends DefaultHandler adaptationSetRoleDescriptors, adaptationSetAccessibilityDescriptors, codecs, + supplementalCodecs, + supplementalProfiles, essentialProperties, supplementalProperties); segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(); @@ -825,6 +837,8 @@ public class DashManifestParser extends DefaultHandler List roleDescriptors, List accessibilityDescriptors, @Nullable String codecs, + @Nullable String supplementalCodecs, + @Nullable String supplementalProfiles, List essentialProperties, List supplementalProperties) { @Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs); @@ -834,6 +848,10 @@ public class DashManifestParser extends DefaultHandler codecs = MimeTypes.CODEC_E_AC3_JOC; } } + if (MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) { + sampleMimeType = MimeTypes.VIDEO_DOLBY_VISION; + codecs = supplementalCodecs != null ? supplementalCodecs : codecs; + } @C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors); @C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors); roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors); 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 44db35b62c..d61e4db559 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 @@ -146,7 +146,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser> urlToVariantInfos = new HashMap<>(); @@ -404,7 +431,29 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser 1) { + supplementalProfiles = codecsAndProfiles[1]; + } + } + String videoCodecs = Util.getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); + if (isDolbyVisionFormat( + videoRange, videoCodecs, supplementalCodecs, supplementalProfiles)) { + videoCodecs = supplementalCodecs != null ? supplementalCodecs : videoCodecs; + String nonVideoCodecs = Util.getCodecsWithoutType(codecs, C.TRACK_TYPE_VIDEO); + codecs = nonVideoCodecs != null ? videoCodecs + "," + nonVideoCodecs : videoCodecs; + } + String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions); int width;