Skip to content

Commit

Permalink
feat: Parse avcC, hvcC and dvcC boxes (#6146)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Jan 23, 2024
1 parent e1cd031 commit b8520ed
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 30 deletions.
143 changes: 121 additions & 22 deletions lib/media/segment_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ goog.provide('shaka.media.SegmentUtils');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.media.Capabilities');
goog.require('shaka.media.ClosedCaptionParser');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.MimeUtils');
Expand Down Expand Up @@ -124,7 +125,8 @@ shaka.media.SegmentUtils = class {
const Mp4Parser = shaka.util.Mp4Parser;
const SegmentUtils = shaka.media.SegmentUtils;

const codecs = [];
const audioCodecs = [];
const videoCodecs = [];

let hasAudio = false;
let hasVideo = false;
Expand All @@ -134,38 +136,38 @@ shaka.media.SegmentUtils = class {
switch (codecLC) {
case 'avc1':
case 'avc3':
codecs.push(codecLC + '.42E01E');
videoCodecs.push(codecLC + '.42E01E');
hasVideo = true;
break;
case 'hev1':
case 'hvc1':
codecs.push(codecLC + '.1.6.L93.90');
videoCodecs.push(codecLC + '.1.6.L93.90');
hasVideo = true;
break;
case 'dvh1':
case 'dvhe':
codecs.push(codecLC + '.05.04');
videoCodecs.push(codecLC + '.05.04');
hasVideo = true;
break;
case 'vp09':
codecs.push(codecLC + '.00.10.08');
videoCodecs.push(codecLC + '.00.10.08');
hasVideo = true;
break;
case 'av01':
codecs.push(codecLC + '.0.01M.08');
videoCodecs.push(codecLC + '.0.01M.08');
hasVideo = true;
break;
case 'mp4a':
// We assume AAC, but this can be wrong since mp4a supports
// others codecs
codecs.push('mp4a.40.2');
audioCodecs.push('mp4a.40.2');
hasAudio = true;
break;
case 'ac-3':
case 'ec-3':
case 'opus':
case 'flac':
codecs.push(codecLC);
audioCodecs.push(codecLC);
hasAudio = true;
break;
}
Expand All @@ -184,6 +186,9 @@ shaka.media.SegmentUtils = class {
/** @type {?number} */
let sampleRate = null;

/** @type {?string} */
let baseBox;

new Mp4Parser()
.box('moov', Mp4Parser.children)
.box('trak', Mp4Parser.children)
Expand Down Expand Up @@ -223,7 +228,7 @@ shaka.media.SegmentUtils = class {
})
.box('esds', (box) => {
const parsedESDSBox = shaka.util.Mp4BoxParsers.parseESDS(box.reader);
codecs.push(parsedESDSBox.codec);
audioCodecs.push(parsedESDSBox.codec);
hasAudio = true;
})
.box('ac-3', codecBoxParser)
Expand All @@ -235,23 +240,90 @@ shaka.media.SegmentUtils = class {
// VIDEO
// These are the various boxes that signal a codec.
.box('avc1', (box) => {
const parsedAVCBox =
shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
codecs.push(parsedAVCBox.codec);
hasVideo = true;
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('avc3', (box) => {
const parsedAVCBox =
shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
codecs.push(parsedAVCBox.codec);
hasVideo = true;
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('hev1', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('hvc1', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('dva1', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('dvav', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('dvh1', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('dvhe', (box) => {
baseBox = box.name;
Mp4Parser.visualSampleEntry(box);
})
.box('hev1', codecBoxParser)
.box('hvc1', codecBoxParser)
.box('dvh1', codecBoxParser)
.box('dvhe', codecBoxParser)
.box('vp09', codecBoxParser)
.box('av01', codecBoxParser)
.box('avcC', (box) => {
let codecBase = baseBox || '';
switch (baseBox) {
case 'dvav':
codecBase = 'avc3';
break;
case 'dva1':
codecBase = 'avc1';
break;
}
const parsedAVCCBox = shaka.util.Mp4BoxParsers.parseAVCC(
codecBase, box.reader, box.name);
videoCodecs.push(parsedAVCCBox.codec);
hasVideo = true;
})
.box('hvcC', (box) => {
let codecBase = baseBox || '';
switch (baseBox) {
case 'dvh1':
codecBase = 'hvc1';
break;
case 'dvhe':
codecBase = 'hev1';
break;
}
const parsedHVCCBox = shaka.util.Mp4BoxParsers.parseHVCC(
codecBase, box.reader, box.name);
videoCodecs.push(parsedHVCCBox.codec);
hasVideo = true;
})
.box('dvcC', (box) => {
let codecBase = baseBox || '';
switch (baseBox) {
case 'hvc1':
codecBase = 'dvh1';
break;
case 'hev1':
codecBase = 'dvhe';
break;
case 'avc1':
codecBase = 'dva1';
break;
case 'avc3':
codecBase = 'dvav';
break;
}
const parsedDVCCBox = shaka.util.Mp4BoxParsers.parseDVCC(
codecBase, box.reader, box.name);
videoCodecs.push(parsedDVCCBox.codec);
hasVideo = true;
})

// This signals an encrypted sample, which we can go inside of to
// find the codec used.
Expand All @@ -265,7 +337,7 @@ shaka.media.SegmentUtils = class {
})

.parse(initData || data, /* partialOkay= */ true);
if (!codecs.length) {
if (!audioCodecs.length && !videoCodecs.length) {
return null;
}
const onlyAudio = hasAudio && !hasVideo;
Expand All @@ -281,6 +353,8 @@ shaka.media.SegmentUtils = class {
}
captionParser.reset();
}
const codecs = audioCodecs.concat(
SegmentUtils.chooseBetterCodecs_(videoCodecs));
return {
type: onlyAudio ? 'audio' : 'video',
mimeType: onlyAudio ? 'audio/mp4' : 'video/mp4',
Expand Down Expand Up @@ -314,6 +388,31 @@ shaka.media.SegmentUtils = class {
}
return ret;
}

/**
* Prioritizes Dolby Vision if supported. This is necessary because with
* Dolby Vision we could have hvcC and dvcC boxes at the same time.
*
* @param {!Array.<string>} codecs
* @return {!Array.<string>} codecs
* @private
*/
static chooseBetterCodecs_(codecs) {
if (codecs.length <= 1) {
return codecs;
}
const dolbyVision = codecs.find((codec) => {
return codec.startsWith('dvh1.') || codec.startsWith('dvhe.');
});
if (!dolbyVision) {
return codecs.slice(0, 1);
}
const type = `video/mp4; codecs="${dolbyVision}"'`;
if (shaka.media.Capabilities.isTypeSupported(type)) {
return [dolbyVision];
}
return codecs.slice(0, 1);
}
};


Expand Down
Loading

0 comments on commit b8520ed

Please sign in to comment.