From d6001097a9751bd9211eb52f940e282ead026a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Thu, 1 Dec 2022 22:34:34 +0100 Subject: [PATCH] fix(cea): Fix not rendering CEA-608 on encrypted mp4 segments (#4756) Fixes https://github.com/shaka-project/shaka-player/issues/4605 Co-authored-by: Joey Parrish --- externs/shaka/mp4_parser.js | 3 +++ lib/cea/mp4_cea_parser.js | 53 ++++++++++++++++++++++++++----------- lib/util/mp4_box_parsers.js | 24 +++++++++++++++++ lib/util/mp4_parser.js | 35 ++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 16 deletions(-) diff --git a/externs/shaka/mp4_parser.js b/externs/shaka/mp4_parser.js index 3545bcdf18..ecc300b899 100644 --- a/externs/shaka/mp4_parser.js +++ b/externs/shaka/mp4_parser.js @@ -11,6 +11,7 @@ /** * @typedef {{ + * name: string, * parser: !shaka.util.Mp4Parser, * partialOkay: boolean, * start: number, @@ -21,6 +22,8 @@ * has64BitSize: boolean * }} * + * @property {string} name + * The box name, a 4-character string (fourcc). * @property {!shaka.util.Mp4Parser} parser * The parser that parsed this box. The parser can be used to parse child * boxes where the configuration of the current parser is needed to parsed diff --git a/lib/cea/mp4_cea_parser.js b/lib/cea/mp4_cea_parser.js index 4869500da7..553407813a 100644 --- a/lib/cea/mp4_cea_parser.js +++ b/lib/cea/mp4_cea_parser.js @@ -66,14 +66,7 @@ shaka.cea.Mp4CeaParser = class { const trackIds = []; const timescales = []; - // This generates a box-parser callback that simply sets a particular - // bitstream format. It makes the list of codec-signalling boxes below - // more compact and readable. - const bitstreamFormatSetter = (format) => { - return (box) => { - this.bitstreamFormat_ = format; - }; - }; + const codecBoxParser = (box) => this.setBitstreamFormat_(box.name); new Mp4Parser() .box('moov', Mp4Parser.children) @@ -108,14 +101,21 @@ shaka.cea.Mp4CeaParser = class { .fullBox('stsd', Mp4Parser.sampleDescription) // These are the various boxes that signal a codec. - // Use these to set bistreamFormat_. - .box('avc1', bitstreamFormatSetter(BitstreamFormat.H264)) - .box('avc3', bitstreamFormatSetter(BitstreamFormat.H264)) - .box('hev1', bitstreamFormatSetter(BitstreamFormat.H265)) - .box('hvc1', bitstreamFormatSetter(BitstreamFormat.H265)) - // Dobly vision is also H265. - .box('dvh1', bitstreamFormatSetter(BitstreamFormat.H265)) - .box('dvhe', bitstreamFormatSetter(BitstreamFormat.H265)) + .box('avc1', codecBoxParser) + .box('avc3', codecBoxParser) + .box('hev1', codecBoxParser) + .box('hvc1', codecBoxParser) + .box('dvh1', codecBoxParser) + .box('dvhe', codecBoxParser) + + // This signals an encrypted sample, which we can go inside of to find + // the codec used. + .box('encv', Mp4Parser.visualSampleEntry) + .box('sinf', Mp4Parser.children) + .box('frma', (box) => { + const {codec} = shaka.util.Mp4BoxParsers.parseFRMA(box.reader); + this.setBitstreamFormat_(codec); + }) .parse(initSegment, /* partialOkay= */ true); @@ -338,6 +338,16 @@ shaka.cea.Mp4CeaParser = class { } } } + + /** + * @param {string} codec A fourcc for a codec. + * @private + */ + setBitstreamFormat_(codec) { + if (codec in shaka.cea.Mp4CeaParser.CodecBitstreamMap_) { + this.bitstreamFormat_ = shaka.cea.Mp4CeaParser.CodecBitstreamMap_[codec]; + } + } }; /** @enum {number} */ @@ -346,3 +356,14 @@ shaka.cea.Mp4CeaParser.BitstreamFormat = { H264: 1, H265: 2, }; + +/** @private {Object.} */ +shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = { + 'avc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H264, + 'avc3': shaka.cea.Mp4CeaParser.BitstreamFormat.H264, + 'hev1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, + 'hvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, + // Dobly vision is also H265. + 'dvh1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, + 'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, +}; diff --git a/lib/util/mp4_box_parsers.js b/lib/util/mp4_box_parsers.js index 204393c129..6fc41db21f 100644 --- a/lib/util/mp4_box_parsers.js +++ b/lib/util/mp4_box_parsers.js @@ -7,6 +7,7 @@ goog.provide('shaka.util.Mp4BoxParsers'); goog.require('shaka.util.DataViewReader'); +goog.require('shaka.util.Mp4Parser'); shaka.util.Mp4BoxParsers = class { /** @@ -188,6 +189,17 @@ shaka.util.Mp4BoxParsers = class { trackId, }; } + + /** + * Parses a FRMA box. + * @param {!shaka.util.DataViewReader} reader + * @return {!shaka.util.ParsedFRMABox} + */ + static parseFRMA(reader) { + const fourcc = reader.readUint32(); + const codec = shaka.util.Mp4Parser.typeToString(fourcc); + return {codec}; + } }; @@ -306,3 +318,15 @@ shaka.util.ParsedTRUNSample; * @exportDoc */ shaka.util.ParsedTKHDBox; + +/** + * @typedef {{ + * codec: string + * }} + * + * @property {string} codec + * A fourcc for a codec + * + * @exportDoc + */ +shaka.util.ParsedFRMABox; diff --git a/lib/util/mp4_parser.js b/lib/util/mp4_parser.js index 7d0ebf0818..b21221de3b 100644 --- a/lib/util/mp4_parser.js +++ b/lib/util/mp4_parser.js @@ -172,6 +172,7 @@ shaka.util.Mp4Parser = class { /** @type {shaka.extern.ParsedBox} */ const box = { + name, parser: this, partialOkay: partialOkay || false, version, @@ -236,6 +237,40 @@ shaka.util.Mp4Parser = class { } + /** + * A callback that tells the Mp4 parser to treat the body of a box as a visual + * sample entry. A visual sample entry has some fixed-sized fields + * describing the video codec parameters, followed by an arbitrary number of + * appended children. Each child is a box. + * + * @param {!shaka.extern.ParsedBox} box + * @export + */ + static visualSampleEntry(box) { + // The "reader" starts at the payload, so we need to add the header to the + // start position. The header size varies. + const headerSize = shaka.util.Mp4Parser.headerSize(box); + + // Skip 6 reserved bytes. + // Skip 2-byte data reference index. + // Skip 16 more reserved bytes. + // Skip 4 bytes for width/height. + // Skip 8 bytes for horizontal/vertical resolution. + // Skip 4 more reserved bytes (0) + // Skip 2-byte frame count. + // Skip 32-byte compressor name (length byte, then name, then 0-padding). + // Skip 2-byte depth. + // Skip 2 more reserved bytes (0xff) + // 78 bytes total. + // See also https://github.com/shaka-project/shaka-packager/blob/d5ca6e84/packager/media/formats/mp4/box_definitions.cc#L1544 + box.reader.skip(78); + + while (box.reader.hasMoreData() && !box.parser.done_) { + box.parser.parseNext(box.start + headerSize, box.reader, box.partialOkay); + } + } + + /** * Create a callback that tells the Mp4 parser to treat the body of a box as a * binary blob and to parse the body's contents using the provided callback.