Merge pull request #1785 from DolbyLaboratories:dlb/dovi-supplemental-codecs/dev

PiperOrigin-RevId: 712867412
This commit is contained in:
Copybara-Service 2025-01-07 04:58:21 -08:00
commit 38363acc8d
5 changed files with 129 additions and 0 deletions

View File

@ -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.):

View File

@ -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.

View File

@ -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.
*

View File

@ -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<BaseUrl> 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<Descriptor> roleDescriptors,
List<Descriptor> accessibilityDescriptors,
@Nullable String codecs,
@Nullable String supplementalCodecs,
@Nullable String supplementalProfiles,
List<Descriptor> essentialProperties,
List<Descriptor> 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);

View File

@ -146,7 +146,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\"");
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("[^-]BANDWIDTH=(\\d+)\\b");
private static final Pattern REGEX_CHANNELS = Pattern.compile("CHANNELS=\"(.+?)\"");
private static final Pattern REGEX_VIDEO_RANGE = Pattern.compile("VIDEO-RANGE=(SDR|PQ|HLG)");
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
private static final Pattern REGEX_SUPPLEMENTAL_CODECS =
Pattern.compile("SUPPLEMENTAL-CODECS=\"(.+?)\"");
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b");
private static final Pattern REGEX_TARGET_DURATION =
@ -353,6 +356,30 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return c;
}
private static boolean isDolbyVisionFormat(
@Nullable String videoRange,
@Nullable String codecs,
@Nullable String supplementalCodecs,
@Nullable String supplementalProfiles) {
if (!MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) {
return false;
}
if (supplementalCodecs == null) {
// Dolby Vision profile 5 that doesn't define supplemental codecs.
return true;
}
if (videoRange == null || supplementalProfiles == null) {
// Video range and supplemental profiles need to be defined for a full validity check.
return false;
}
if ((videoRange.equals("PQ") && !supplementalProfiles.equals("db1p"))
|| (videoRange.equals("SDR") && !supplementalProfiles.equals("db2g"))
|| (videoRange.equals("HLG") && !supplementalProfiles.startsWith("db4"))) { // db4g or db4h
return false;
}
return true;
}
private static HlsMultivariantPlaylist parseMultivariantPlaylist(
LineIterator iterator, String baseUri) throws IOException {
HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>();
@ -404,7 +431,29 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int roleFlags = isIFrameOnlyVariant ? C.ROLE_FLAG_TRICK_PLAY : 0;
int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
String videoRange = parseOptionalStringAttr(line, REGEX_VIDEO_RANGE, variableDefinitions);
String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions);
String supplementalCodecsStrings =
parseOptionalStringAttr(line, REGEX_SUPPLEMENTAL_CODECS, variableDefinitions);
String supplementalCodecs = null;
String supplementalProfiles = null; // i.e. Compatibility brand
if (supplementalCodecsStrings != null) {
String[] supplementalCodecsString = Util.splitAtFirst(supplementalCodecsStrings, ",");
// TODO: Support more than one element
String[] codecsAndProfiles = Util.split(supplementalCodecsString[0], "/");
supplementalCodecs = codecsAndProfiles[0];
if (codecsAndProfiles.length > 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;