Skip to content

Commit

Permalink
feat: Extract HDR metadata from HLS manifests (#3116)
Browse files Browse the repository at this point in the history
In our isTypeSupported polyfill for Cast, we use the HEVC profile ("hevc1.2") as
an indication of the HDR transfer function, which is not always accurate.
The polyfill should stop assuming that 10-bit color HEVC means HDR, and remove
the extra eotf="smpte2084" parameter. Instead, the manifest parser should
extract HDR profile information from the DASH manifest and pass that info along
through the Stream object.

Issue #3116 .
  • Loading branch information
Álvaro Velad Galván authored Feb 1, 2021
1 parent d3640d1 commit 7137286
Show file tree
Hide file tree
Showing 14 changed files with 110 additions and 4 deletions.
4 changes: 4 additions & 0 deletions externs/shaka/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ shaka.extern.CreateSegmentIndexFunction;
* codecs: string,
* frameRate: (number|undefined),
* pixelAspectRatio: (string|undefined),
* hdr: (string|undefined),
* bandwidth: (number|undefined),
* width: (number|undefined),
* height: (number|undefined),
Expand Down Expand Up @@ -276,6 +277,9 @@ shaka.extern.CreateSegmentIndexFunction;
* @property {(string|undefined)} pixelAspectRatio
* <i>Video streams only.</i> <br>
* The Stream's pixel aspect ratio
* @property {(string|undefined)} hdr
* <i>Video streams only.</i> <br>
* The Stream's HDR info
* @property {(number|undefined)} bandwidth
* <i>Audio and video streams only.</i> <br>
* The stream's required bandwidth in bits per second.
Expand Down
3 changes: 3 additions & 0 deletions externs/shaka/offline.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ shaka.extern.ManifestDB;
* codecs: string,
* frameRate: (number|undefined),
* pixelAspectRatio: (string|undefined),
* hdr: (string|undefined),
* kind: (string|undefined),
* language: string,
* label: ?string,
Expand Down Expand Up @@ -138,6 +139,8 @@ shaka.extern.ManifestDB;
* The Stream's framerate in frames per second.
* @property {(string|undefined)} pixelAspectRatio
* The Stream's pixel aspect ratio
* @property {(string|undefined)} hdr
* The Stream's HDR info
* @property {(string|undefined)} kind
* The kind of text stream; undefined for audio/video.
* @property {string} language
Expand Down
3 changes: 3 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ shaka.extern.BufferedInfo;
* height: ?number,
* frameRate: ?number,
* pixelAspectRatio: ?string,
* hdr: ?string,
* mimeType: ?string,
* codecs: ?string,
* audioCodec: ?string,
Expand Down Expand Up @@ -258,6 +259,8 @@ shaka.extern.BufferedInfo;
* The video framerate provided in the manifest, if present.
* @property {?string} pixelAspectRatio
* The video pixel aspect ratio provided in the manifest, if present.
* @property {?string} hdr
* The video HDR provided in the manifest, if present.
* @property {?string} mimeType
* The MIME type of the content provided in the manifest.
* @property {?string} codecs
Expand Down
16 changes: 12 additions & 4 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,8 @@ shaka.hls.HlsParser = class {
const resolution = tag.getAttributeValue('RESOLUTION');
const [width, height] = resolution ? resolution.split('x') : [null, null];

const videoRange = tag.getAttributeValue('VIDEO-RANGE');

const streamInfos = await this.createStreamInfosForVariantTag_(tag,
resolution, frameRate);

Expand All @@ -712,7 +714,8 @@ shaka.hls.HlsParser = class {
bandwidth,
width,
height,
frameRate);
frameRate,
videoRange);
}
// We do not support AES-128 encryption with HLS yet. If the streamInfos
// is null because of AES-128 encryption, do not create variants for that.
Expand Down Expand Up @@ -969,15 +972,18 @@ shaka.hls.HlsParser = class {
* @param {?string} width
* @param {?string} height
* @param {?string} frameRate
* @param {?string} videoRange
* @return {!Array.<!shaka.extern.Variant>}
* @private
*/
createVariants_(audioInfos, videoInfos, bandwidth, width, height, frameRate) {
createVariants_(
audioInfos, videoInfos, bandwidth, width, height, frameRate, videoRange) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const DrmEngine = shaka.media.DrmEngine;

for (const info of videoInfos) {
this.addVideoAttributes_(info.stream, width, height, frameRate);
this.addVideoAttributes_(
info.stream, width, height, frameRate, videoRange);
}

// In case of audio-only or video-only content or the audio/video is
Expand Down Expand Up @@ -2440,13 +2446,15 @@ shaka.hls.HlsParser = class {
* @param {?string} width
* @param {?string} height
* @param {?string} frameRate
* @param {?string} videoRange
* @private
*/
addVideoAttributes_(stream, width, height, frameRate) {
addVideoAttributes_(stream, width, height, frameRate, videoRange) {
if (stream) {
stream.width = Number(width) || undefined;
stream.height = Number(height) || undefined;
stream.frameRate = Number(frameRate) || undefined;
stream.hdr = videoRange || undefined;
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/offline/indexeddb/v1_storage_cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ shaka.offline.indexeddb.V1StorageCell = class
codecs: old.codecs,
frameRate: old.frameRate,
pixelAspectRatio: undefined,
hdr: undefined,
kind: old.kind,
language: old.language,
label: old.label,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/indexeddb/v2_storage_cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ shaka.offline.indexeddb.V2StorageCell = class
codecs: old.codecs,
frameRate: old.frameRate,
pixelAspectRatio: old.pixelAspectRatio,
hdr: undefined,
kind: old.kind,
language: old.language,
label: old.label,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/manifest_converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ shaka.offline.ManifestConverter = class {
height: streamDB.height || undefined,
frameRate: streamDB.frameRate,
pixelAspectRatio: streamDB.pixelAspectRatio,
hdr: streamDB.hdr,
kind: streamDB.kind,
encrypted: streamDB.encrypted,
drmInfos: [],
Expand Down
1 change: 1 addition & 0 deletions lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,7 @@ shaka.offline.Storage = class {
codecs: stream.codecs,
frameRate: stream.frameRate,
pixelAspectRatio: stream.pixelAspectRatio,
hdr: stream.hdr,
kind: stream.kind,
language: stream.language,
label: stream.label,
Expand Down
3 changes: 3 additions & 0 deletions lib/util/mime_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ shaka.util.MimeUtils = class {
components.push(mimeKey + '="' + value + '"');
}
});
if (stream.hdr == 'PQ') {
components.push('eotf="smpte2084"');
}

return components.join(';');
}
Expand Down
3 changes: 3 additions & 0 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ shaka.util.StreamUtils = class {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
mimeType: mimeType,
codecs: codecs.join(', '),
audioCodec: audioCodec,
Expand Down Expand Up @@ -422,6 +423,7 @@ shaka.util.StreamUtils = class {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
mimeType: stream.mimeType,
codecs: stream.codecs || null,
audioCodec: null,
Expand Down Expand Up @@ -536,6 +538,7 @@ shaka.util.StreamUtils = class {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
mimeType: null,
codecs: null,
audioCodec: null,
Expand Down
61 changes: 61 additions & 0 deletions test/hls/hls_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,67 @@ describe('HlsParser', () => {
await testHlsParser(master, media, manifest);
});

it('parses manifest with HDR metadata', async () => {
const master = [
'#EXTM3U\n',
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
'URI="audio"\n',
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
'FORCED=YES,URI="text"\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",VIDEO-RANGE=PQ,',
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
'video\n',
].join('');

const media = [
'#EXTM3U\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.mp4',
].join('');

const textMedia = [
'#EXTM3U\n',
'#EXT-X-PLAYLIST-TYPE:VOD\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.vtt',
].join('');

const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
manifest.addPartialVariant((variant) => {
variant.addPartialStream(ContentType.VIDEO, (stream) => {
stream.mime('video/mp4', 'avc1');
stream.hdr = 'PQ';
});
variant.addPartialStream(ContentType.AUDIO, (stream) => {
stream.mime('audio/mp4', 'mp4a');
});
});
manifest.addPartialTextStream((stream) => {
stream.language = 'en';
stream.forced = true;
stream.kind = TextStreamKind.SUBTITLE;
stream.mime('text/vtt', '');
});
});

fakeNetEngine
.setResponseText('test:/master', master)
.setResponseText('test:/audio', media)
.setResponseText('test:/video', media)
.setResponseText('test:/text', textMedia)
.setResponseText('test:/main.vtt', vttText)
.setResponseValue('test:/init.mp4', initSegmentData)
.setResponseValue('test:/main.mp4', segmentData);

const actual = await parser.start('test:/master', playerInterface);
expect(actual).toEqual(manifest);
});

it('parses manifest with SUBTITLES', async () => {
const master = [
'#EXTM3U\n',
Expand Down
4 changes: 4 additions & 0 deletions test/offline/manifest_convert_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ describe('ManifestConverter', () => {
codecs: 'avc1.42c01e',
frameRate: 22,
pixelAspectRatio: '59:54',
hdr: undefined,
kind: undefined,
language: '',
label: null,
Expand Down Expand Up @@ -381,6 +382,7 @@ describe('ManifestConverter', () => {
codecs: 'mp4a.40.2',
frameRate: undefined,
pixelAspectRatio: undefined,
hdr: undefined,
kind: undefined,
language: 'en',
label: null,
Expand Down Expand Up @@ -426,6 +428,7 @@ describe('ManifestConverter', () => {
codecs: '',
frameRate: undefined,
pixelAspectRatio: undefined,
hdr: undefined,
kind: undefined,
language: 'en',
label: null,
Expand Down Expand Up @@ -478,6 +481,7 @@ describe('ManifestConverter', () => {
codecs: streamDb.codecs,
frameRate: streamDb.frameRate,
pixelAspectRatio: streamDb.pixelAspectRatio,
hdr: streamDb.hdr,
width: streamDb.width || undefined,
height: streamDb.height || undefined,
kind: streamDb.kind,
Expand Down
2 changes: 2 additions & 0 deletions test/offline/storage_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,7 @@ filterDescribe('Storage', storageSupport, () => {
height: height,
frameRate: 30,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4,audio/mp4',
codecs: 'mp4,mp4',
audioCodec: 'mp4',
Expand Down Expand Up @@ -1333,6 +1334,7 @@ filterDescribe('Storage', storageSupport, () => {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
mimeType: 'text/vtt',
codecs: 'vtt',
audioCodec: null,
Expand Down
11 changes: 11 additions & 0 deletions test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,7 @@ describe('Player', () => {
height: 200,
frameRate: 1000000 / 42000,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1089,6 +1090,7 @@ describe('Player', () => {
height: 400,
frameRate: 24,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1119,6 +1121,7 @@ describe('Player', () => {
height: 200,
frameRate: 1000000 / 42000,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1149,6 +1152,7 @@ describe('Player', () => {
height: 400,
frameRate: 24,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1179,6 +1183,7 @@ describe('Player', () => {
height: 200,
frameRate: 1000000 / 42000,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1209,6 +1214,7 @@ describe('Player', () => {
height: 400,
frameRate: 24,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1239,6 +1245,7 @@ describe('Player', () => {
height: 200,
frameRate: 1000000 / 42000,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1269,6 +1276,7 @@ describe('Player', () => {
height: 400,
frameRate: 24,
pixelAspectRatio: '59:54',
hdr: null,
mimeType: 'video/mp4',
codecs: 'avc1.4d401f, mp4a.40.2',
audioCodec: 'mp4a.40.2',
Expand Down Expand Up @@ -1314,6 +1322,7 @@ describe('Player', () => {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
videoId: null,
audioId: null,
originalAudioId: null,
Expand Down Expand Up @@ -1344,6 +1353,7 @@ describe('Player', () => {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
videoId: null,
audioId: null,
originalAudioId: null,
Expand Down Expand Up @@ -1374,6 +1384,7 @@ describe('Player', () => {
height: null,
frameRate: null,
pixelAspectRatio: null,
hdr: null,
videoId: null,
audioId: null,
originalAudioId: null,
Expand Down

0 comments on commit 7137286

Please sign in to comment.