Skip to content

Commit eef8a41

Browse files
microkatzcopybara-github
authored andcommitted
Add support for Dolby Vision profile 10 (dav1).
This change adds support for Dolby Vision profile 10, which uses AV1 as the base layer. The "dav1" codec string is now recognized as Dolby Vision. In addition, colorInfo is parsed from Dolby Vision codec strings and supplemental profiles in DASH and HLS manifests. The colorInfo is utilized to prevent fallback to the base AV1 codec for non-backwards compatible Dolby profiles. Issue: #2830 PiperOrigin-RevId: 869296253
1 parent b6a45cc commit eef8a41

File tree

14 files changed

+231
-6
lines changed

14 files changed

+231
-6
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
* Fix bug where transitions from on-demand to live content may cause
1313
re-buffers at the end of the on-demand content
1414
([#3052](https://github.com/androidx/media/issues/3052)).
15+
* Add support for Dolby Vision Profile 10
16+
([#2830](https://github.com/androidx/media/pull/2830)).
1517
* CompositionPlayer:
1618
* Transformer:
1719
* Track selection:

libraries/common/src/main/java/androidx/media3/common/MimeTypes.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ public static String getMediaMimeType(@Nullable String codec) {
462462
} else if (codec.startsWith("dvav")
463463
|| codec.startsWith("dva1")
464464
|| codec.startsWith("dvhe")
465-
|| codec.startsWith("dvh1")) {
465+
|| codec.startsWith("dvh1")
466+
|| codec.startsWith("dav1")) {
466467
return MimeTypes.VIDEO_DOLBY_VISION;
467468
} else if (codec.startsWith("av01")) {
468469
return MimeTypes.VIDEO_AV1;
@@ -617,8 +618,8 @@ public static boolean isDolbyVisionCodec(
617618
if (codecs == null) {
618619
return false;
619620
}
620-
if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1")) {
621-
// profile 5
621+
if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1") || codecs.startsWith("dav1")) {
622+
// profiles 5, 10.0 and 20.0
622623
return true;
623624
}
624625
if (supplementalCodecs == null) {

libraries/common/src/main/java/androidx/media3/common/util/Util.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import androidx.media3.common.AudioAttributes;
9595
import androidx.media3.common.C;
9696
import androidx.media3.common.C.ContentType;
97+
import androidx.media3.common.ColorInfo;
9798
import androidx.media3.common.Format;
9899
import androidx.media3.common.MediaItem;
99100
import androidx.media3.common.MediaLibraryInfo;
@@ -2247,6 +2248,70 @@ public static String getCodecsOfType(@Nullable String codecs, @C.TrackType int t
22472248
return builder.length() > 0 ? builder.toString() : null;
22482249
}
22492250

2251+
/**
2252+
* Returns the {@link ColorInfo} for specific Dolby Vision codecs and profiles.
2253+
*
2254+
* <p>This method only supports providing {@link ColorInfo} for the following Dolby Vision codecs
2255+
* and profiles:
2256+
*
2257+
* <ul>
2258+
* <li>Dolby Vision profiles 5, 10.0, and 20.0
2259+
* <li>Dolby Vision profiles 8.1 and 8.4 when providing supplemental profile values
2260+
* </ul>
2261+
*
2262+
* @param codecs A codec sequence string, as defined in RFC 6381.
2263+
* @param supplementalCodecs An optional RFC 6381 codecs string for supplemental codecs.
2264+
* @param supplementalProfiles Optional supplemental profile info.
2265+
* @return The {@link ColorInfo} for specific Dolby Vision codecs and profiles and otherwise null.
2266+
*/
2267+
@UnstableApi
2268+
@Nullable
2269+
public static ColorInfo getColorInfoForDolbyVision(
2270+
@Nullable String codecs,
2271+
@Nullable String supplementalCodecs,
2272+
@Nullable String supplementalProfiles) {
2273+
if (codecs == null) {
2274+
return null;
2275+
}
2276+
2277+
@C.ColorSpace int colorSpace = Format.NO_VALUE;
2278+
@C.ColorRange int colorRange = Format.NO_VALUE;
2279+
@C.ColorTransfer int colorTransfer = Format.NO_VALUE;
2280+
2281+
if (!MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) {
2282+
return null;
2283+
}
2284+
2285+
if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1") || codecs.startsWith("dav1")) {
2286+
// profiles 5, 10.0 and 20.0
2287+
colorSpace = C.COLOR_SPACE_BT2020;
2288+
colorTransfer = C.COLOR_TRANSFER_ST2084;
2289+
colorRange = C.COLOR_RANGE_FULL;
2290+
} else if (supplementalProfiles != null) {
2291+
if (supplementalProfiles.equals("db1p")) {
2292+
// BL signal cross-compatibility ID = 1 (e.g profile 8.1)
2293+
colorSpace = C.COLOR_SPACE_BT2020;
2294+
colorTransfer = C.COLOR_TRANSFER_ST2084;
2295+
colorRange = C.COLOR_RANGE_LIMITED;
2296+
} else if (supplementalProfiles.startsWith("db4")) { // db4g or db4h
2297+
// BL signal cross-compatibility ID = 4 (e.g profile 8.4)
2298+
colorSpace = C.COLOR_SPACE_BT2020;
2299+
colorTransfer = C.COLOR_TRANSFER_HLG;
2300+
colorRange = C.COLOR_RANGE_LIMITED;
2301+
}
2302+
}
2303+
2304+
if (colorSpace == Format.NO_VALUE) {
2305+
return null;
2306+
}
2307+
2308+
return new ColorInfo.Builder()
2309+
.setColorSpace(colorSpace)
2310+
.setColorRange(colorRange)
2311+
.setColorTransfer(colorTransfer)
2312+
.build();
2313+
}
2314+
22502315
/**
22512316
* Returns a copy of {@code codecs} without the codecs whose track type matches {@code trackType}.
22522317
*

libraries/container/src/main/java/androidx/media3/container/Mp4Box.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ public abstract class Mp4Box {
123123
@SuppressWarnings("ConstantCaseForConstants")
124124
public static final int TYPE_dvcC = 0x64766343;
125125

126+
@SuppressWarnings("ConstantCaseForConstants")
127+
public static final int TYPE_dav1 = 0x64617631;
128+
126129
@SuppressWarnings("ConstantCaseForConstants")
127130
public static final int TYPE_dvvC = 0x64767643;
128131

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1770,7 +1770,10 @@ protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
17701770
// * b/229399008#comment9
17711771
// * https://github.com/androidx/media/issues/2408
17721772
if ((Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_AV1)
1773-
|| Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_VP9))
1773+
|| Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_VP9)
1774+
|| (Objects.equals(newFormat.sampleMimeType, MimeTypes.VIDEO_DOLBY_VISION)
1775+
&& Objects.equals(
1776+
MediaCodecUtil.getAlternativeCodecMimeType(newFormat), MimeTypes.VIDEO_AV1)))
17741777
&& !newFormat.initializationData.isEmpty()) {
17751778
newFormat = newFormat.buildUpon().setInitializationData(null).build();
17761779
}

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecUtil.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import androidx.annotation.Nullable;
3333
import androidx.annotation.RequiresApi;
3434
import androidx.annotation.VisibleForTesting;
35+
import androidx.media3.common.C;
3536
import androidx.media3.common.Format;
3637
import androidx.media3.common.MimeTypes;
3738
import androidx.media3.common.util.CodecSpecificDataUtil;
@@ -394,6 +395,11 @@ public static String getAlternativeCodecMimeType(Format format) {
394395
} else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) {
395396
return MimeTypes.VIDEO_H264;
396397
} else if (profile == CodecProfileLevel.DolbyVisionProfileDvav110) {
398+
if (format.colorInfo != null
399+
&& format.colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084
400+
&& format.colorInfo.colorRange == C.COLOR_RANGE_FULL) {
401+
return null;
402+
}
397403
return MimeTypes.VIDEO_AV1;
398404
}
399405
}

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package androidx.media3.exoplayer.video;
1717

1818
import static android.media.MediaCodec.INFO_TRY_AGAIN_LATER;
19+
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1Level2;
20+
import static android.media.MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10;
1921
import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel42;
2022
import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline;
2123
import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
@@ -59,6 +61,7 @@
5961
import android.view.Surface;
6062
import androidx.annotation.Nullable;
6163
import androidx.media3.common.C;
64+
import androidx.media3.common.ColorInfo;
6265
import androidx.media3.common.Format;
6366
import androidx.media3.common.MimeTypes;
6467
import androidx.media3.common.TrackGroup;
@@ -4058,7 +4061,7 @@ public void resetPosition_toBeforeOriginalStartPosition_rendersFirstFrame() thro
40584061
}
40594062

40604063
@Test
4061-
public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH264Allowed()
4064+
public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToAv1H265orH264Allowed()
40624065
throws Exception {
40634066
// Create Dolby media formats that could fall back to H265 or H264.
40644067
Format formatDvheDtrFallbackToH265 =
@@ -4076,6 +4079,30 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH
40764079
.setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION)
40774080
.setCodecs("dvav.09.01")
40784081
.build();
4082+
// Profile 10.1 (Limited Range PQ) which allows fallback to AV1.
4083+
Format formatDav1FallbackToAv1 =
4084+
new Format.Builder()
4085+
.setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION)
4086+
.setCodecs("dav1.10.01")
4087+
.setColorInfo(
4088+
new ColorInfo.Builder()
4089+
.setColorSpace(C.COLOR_SPACE_BT2020)
4090+
.setColorTransfer(C.COLOR_TRANSFER_ST2084)
4091+
.setColorRange(C.COLOR_RANGE_LIMITED)
4092+
.build())
4093+
.build();
4094+
// Profile 10.0 (Full Range PQ) which does NOT allow fallback.
4095+
Format formatDav1NoFallbackPossible =
4096+
new Format.Builder()
4097+
.setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION)
4098+
.setCodecs("dav1.10.01")
4099+
.setColorInfo(
4100+
new ColorInfo.Builder()
4101+
.setColorSpace(C.COLOR_SPACE_BT2020)
4102+
.setColorTransfer(C.COLOR_TRANSFER_ST2084)
4103+
.setColorRange(C.COLOR_RANGE_FULL)
4104+
.build())
4105+
.build();
40794106
Format formatNoFallbackPossible =
40804107
new Format.Builder()
40814108
.setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION)
@@ -4121,6 +4148,21 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH
41214148
/* vendor= */ false,
41224149
/* forceDisableAdaptive= */ false,
41234150
/* forceSecure= */ false));
4151+
case MimeTypes.VIDEO_AV1:
4152+
CodecCapabilities capabilitiesAv1 = new CodecCapabilities();
4153+
capabilitiesAv1.profileLevels =
4154+
new CodecProfileLevel[] {createCodecProfileLevel(AV1ProfileMain10, AV1Level2)};
4155+
return ImmutableList.of(
4156+
MediaCodecInfo.newInstance(
4157+
/* name= */ "av1-codec",
4158+
/* mimeType= */ mimeType,
4159+
/* codecMimeType= */ mimeType,
4160+
/* capabilities= */ capabilitiesAv1,
4161+
/* hardwareAccelerated= */ false,
4162+
/* softwareOnly= */ true,
4163+
/* vendor= */ false,
4164+
/* forceDisableAdaptive= */ false,
4165+
/* forceSecure= */ false));
41244166
default:
41254167
return ImmutableList.of();
41264168
}
@@ -4142,6 +4184,10 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH
41424184
@Capabilities
41434185
int capabilitiesDvavSeFallbackToH264 = renderer.supportsFormat(formatDvavSeFallbackToH264);
41444186
@Capabilities
4187+
int capabilitiesDav1FallbackToAv1 = renderer.supportsFormat(formatDav1FallbackToAv1);
4188+
@Capabilities
4189+
int capabilitiesDav1NoFallbackToAv1 = renderer.supportsFormat(formatDav1NoFallbackPossible);
4190+
@Capabilities
41454191
int capabilitiesNoFallbackPossible = renderer.supportsFormat(formatNoFallbackPossible);
41464192

41474193
assertThat(RendererCapabilities.getFormatSupport(capabilitiesDvheDtrFallbackToH265))
@@ -4150,6 +4196,10 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH
41504196
.isEqualTo(C.FORMAT_HANDLED);
41514197
assertThat(RendererCapabilities.getFormatSupport(capabilitiesDvavSeFallbackToH264))
41524198
.isEqualTo(C.FORMAT_HANDLED);
4199+
assertThat(RendererCapabilities.getFormatSupport(capabilitiesDav1FallbackToAv1))
4200+
.isEqualTo(C.FORMAT_HANDLED);
4201+
assertThat(RendererCapabilities.getFormatSupport(capabilitiesDav1NoFallbackToAv1))
4202+
.isEqualTo(C.FORMAT_UNSUPPORTED_SUBTYPE);
41534203
assertThat(RendererCapabilities.getFormatSupport(capabilitiesNoFallbackPossible))
41544204
.isEqualTo(C.FORMAT_UNSUPPORTED_SUBTYPE);
41554205
}

libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import android.util.Xml;
2828
import androidx.annotation.Nullable;
2929
import androidx.media3.common.C;
30+
import androidx.media3.common.ColorInfo;
3031
import androidx.media3.common.DrmInitData;
3132
import androidx.media3.common.DrmInitData.SchemeData;
3233
import androidx.media3.common.Format;
@@ -852,8 +853,11 @@ protected Format buildFormat(
852853
codecs = MimeTypes.CODEC_E_AC3_JOC;
853854
}
854855
}
856+
857+
@Nullable ColorInfo colorInfo = null;
855858
if (MimeTypes.isDolbyVisionCodec(codecs, supplementalCodecs)) {
856859
sampleMimeType = MimeTypes.VIDEO_DOLBY_VISION;
860+
colorInfo = Util.getColorInfoForDolbyVision(codecs, supplementalCodecs, supplementalProfiles);
857861
codecs = supplementalCodecs != null ? supplementalCodecs : codecs;
858862
}
859863
@C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors);
@@ -872,6 +876,7 @@ protected Format buildFormat(
872876
.setPeakBitrate(bitrate)
873877
.setSelectionFlags(selectionFlags)
874878
.setRoleFlags(roleFlags)
879+
.setColorInfo(colorInfo)
875880
.setLanguage(language)
876881
.setTileCountHorizontal(tileCounts != null ? tileCounts.first : Format.NO_VALUE)
877882
.setTileCountVertical(tileCounts != null ? tileCounts.second : Format.NO_VALUE);

libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public class DashManifestParserTest {
9191
"media/mpd/sample_mpd_clear_key_license_url";
9292
private static final String SAMPLE_MPD_DASHIF_LICENSE_URL =
9393
"media/mpd/sample_mpd_dashif_license_url";
94+
private static final String SAMPLE_MPD_DOLBY_VISION = "media/mpd/sample_mpd_dolby";
9495

9596
private static final String NEXT_TAG_NAME = "Next";
9697
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
@@ -404,6 +405,31 @@ public void parseMediaPresentationDescription_trickPlay() throws IOException {
404405
.isEqualTo(C.ROLE_FLAG_TRICK_PLAY);
405406
}
406407

408+
@Test
409+
public void parseMediaPresentationDescription_dolbyVisionProfile10() throws IOException {
410+
DashManifestParser parser = new DashManifestParser();
411+
DashManifest manifest =
412+
parser.parse(
413+
Uri.parse("https://example.com/test.mpd"),
414+
TestUtil.getInputStream(
415+
ApplicationProvider.getApplicationContext(), SAMPLE_MPD_DOLBY_VISION));
416+
417+
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
418+
419+
AdaptationSet adaptationSet = adaptationSets.get(0);
420+
assertThat(adaptationSet.representations).hasSize(2);
421+
Representation representation = adaptationSet.representations.get(0);
422+
assertThat(representation).isNotNull();
423+
assertThat(representation.format.colorInfo).isNotNull();
424+
assertThat(representation.format.colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT2020);
425+
assertThat(representation.format.colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_ST2084);
426+
assertThat(representation.format.colorInfo.colorRange).isEqualTo(C.COLOR_RANGE_FULL);
427+
assertThat(adaptationSet.supplementalProperties).hasSize(1);
428+
assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri)
429+
.isEqualTo("urn:dolby:dash:dolby-vision:2018");
430+
assertThat(adaptationSet.supplementalProperties.get(0).value).isEqualTo("10.1");
431+
}
432+
407433
@Test
408434
public void parseSegmentTimeline_repeatCount() throws Exception {
409435
DashManifestParser parser = new DashManifestParser();

libraries/exoplayer_hls/src/androidTest/java/androidx/media3/exoplayer/hls/playlist/HlsMultivariantPlaylistParserTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ public class HlsMultivariantPlaylistParserTest {
7676
+ "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
7777
+ "http://example.com/spaces_in_codecs.m3u8\n";
7878

79+
private static final String PLAYLIST_WITH_DOLBY_VISION =
80+
" #EXTM3U \n"
81+
+ "\n"
82+
+ "#EXT-X-STREAM-INF:BANDWIDTH=8500000,AVERAGE-BANDWIDTH=6000000,"
83+
+ "CODECS=\"dvh1.10.05\",RESOLUTION=1920x1080,VIDEO-RANGE=PQ\n"
84+
+ "http://example.com/high_hdr.m3u8\n";
85+
7986
private static final String PLAYLIST_WITH_PATHWAY_ID_AND_STABLE_VARIANT_ID =
8087
" #EXTM3U \n"
8188
+ "\n"
@@ -366,6 +373,19 @@ public void parseMultivariantPlaylist_withAverageBandwidth_success() throws IOEx
366373
assertThat(variants.get(1).format.bitrate).isEqualTo(1280000);
367374
}
368375

376+
@Test
377+
public void parseMultivariantPlaylist_withDolbyVisionProfile10_success() throws IOException {
378+
HlsMultivariantPlaylist multivariantPlaylist =
379+
parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_DOLBY_VISION);
380+
381+
List<HlsMultivariantPlaylist.Variant> variants = multivariantPlaylist.variants;
382+
383+
assertThat(variants.get(0).format.colorInfo).isNotNull();
384+
assertThat(variants.get(0).format.colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT2020);
385+
assertThat(variants.get(0).format.colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_ST2084);
386+
assertThat(variants.get(0).format.colorInfo.colorRange).isEqualTo(C.COLOR_RANGE_FULL);
387+
}
388+
369389
@Test
370390
public void parseMultivariantPlaylist_withPathwayIdAndStableVariantId_success()
371391
throws IOException {

0 commit comments

Comments
 (0)