Skip to content

Commit

Permalink
fix(CEA): CEA-608 is not rendered in some cases (multiples TRAF boxes) (
Browse files Browse the repository at this point in the history
#6878)

Fixes #5919
  • Loading branch information
avelad committed Jun 21, 2024
1 parent 0099449 commit 745b596
Showing 1 changed file with 67 additions and 31 deletions.
98 changes: 67 additions & 31 deletions lib/cea/mp4_cea_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,81 +164,100 @@ shaka.cea.Mp4CeaParser = class {
/** @type {!Array<!shaka.extern.ICeaParser.CaptionPacket>} **/
const captionPackets = [];

// Fields that are found in MOOF boxes
let defaultSampleDuration = this.defaultSampleDuration_;
let defaultSampleSize = this.defaultSampleSize_;
let moofOffset = 0;
/** @type {!Array<shaka.util.ParsedTRUNBox>} */
let parsedTRUNs = [];
let baseMediaDecodeTime = null;
let timescale = shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE;

/** @type {!Array<!shaka.cea.Mp4CeaParser.ParsedTRAF>} */
let parsedTRAFs = [];

new Mp4Parser()
.box('moof', (box) => {
moofOffset = box.start;
// trun box parsing is reset on each moof.
parsedTRUNs = [];
// traf box parsing is reset on each moof.
parsedTRAFs = [];
Mp4Parser.children(box);
})
.box('traf', (box) => {
parsedTRAFs.push({
baseMediaDecodeTime: null,
defaultSampleDuration: this.defaultSampleDuration_,
defaultSampleSize: this.defaultSampleSize_,
parsedTRUNs: [],
timescale: shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE,
});
Mp4Parser.children(box);
})
.box('traf', Mp4Parser.children)
.fullBox('trun', (box) => {
goog.asserts.assert(
box.version != null && box.flags!=null,
box.version != null && box.flags != null,
'TRUN is a full box and should have a valid version & flags.');

const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];

const parsedTRUN = shaka.util.Mp4BoxParsers.parseTRUN(
box.reader, box.version, box.flags);
parsedTRUNs.push(parsedTRUN);
lastTRAF.parsedTRUNs.push(parsedTRUN);
})
.fullBox('tfhd', (box) => {
goog.asserts.assert(
box.flags != null,
'TFHD is a full box and should have valid flags.');

const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];

const parsedTFHD = shaka.util.Mp4BoxParsers.parseTFHD(
box.reader, box.flags);

// If specified, defaultSampleDuration and defaultSampleSize
// override the ones specified in the TREX box
defaultSampleDuration = parsedTFHD.defaultSampleDuration ||
lastTRAF.defaultSampleDuration = parsedTFHD.defaultSampleDuration ||
this.defaultSampleDuration_;

defaultSampleSize = parsedTFHD.defaultSampleSize ||
lastTRAF.defaultSampleSize = parsedTFHD.defaultSampleSize ||
this.defaultSampleSize_;

const trackId = parsedTFHD.trackId;

// Get the timescale from the track Id
if (this.trackIdToTimescale_.has(trackId)) {
timescale = this.trackIdToTimescale_.get(trackId);
lastTRAF.timescale = this.trackIdToTimescale_.get(trackId);
}
})
.fullBox('tfdt', (box) => {
goog.asserts.assert(
box.version != null,
'TFDT is a full box and should have a valid version.');

const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];

const parsedTFDT = shaka.util.Mp4BoxParsers.parseTFDTInaccurate(
box.reader, box.version);

baseMediaDecodeTime = parsedTFDT.baseMediaDecodeTime;
lastTRAF.baseMediaDecodeTime = parsedTFDT.baseMediaDecodeTime;
})
.box('mdat', (box) => {
if (baseMediaDecodeTime === null) {
// This field should have been populated by the Base Media Decode
// Time in the tfdt box.
shaka.log.alwaysWarn(
'Unable to find base media decode time for CEA captions!');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_MP4_CEA);
}

const offset = moofOffset - box.start - 8;
this.parseMdat_(box.reader, baseMediaDecodeTime, timescale,
defaultSampleDuration, defaultSampleSize, offset, parsedTRUNs,
captionPackets);
const initialPosition = box.reader.getPosition();
for (const parsedTRAF of parsedTRAFs) {
if (parsedTRAF.baseMediaDecodeTime === null) {
// This field should have been populated by the Base Media Decode
// Time in the tfdt box.
shaka.log.alwaysWarn(
'Unable to find base media decode time for CEA captions!');
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_MP4_CEA);
}
box.reader.seek(initialPosition);
this.parseMdat_(box.reader,
parsedTRAF.baseMediaDecodeTime,
parsedTRAF.timescale,
parsedTRAF.defaultSampleDuration,
parsedTRAF.defaultSampleSize,
offset,
parsedTRAF.parsedTRUNs,
captionPackets);
}
})
.parse(mediaSegment, /* partialOkay= */ false);

Expand Down Expand Up @@ -315,7 +334,7 @@ shaka.cea.Mp4CeaParser = class {
timeOffset = sampleData[sampleIndex].sampleCompositionTimeOffset || 0;
}

const pts = (time + timeOffset)/timescale;
const pts = (time + timeOffset) / timescale;
for (const packet of this.seiProcessor_
.process(reader.readBytes(naluSize - naluHeaderSize))) {
captionPackets.push({
Expand Down Expand Up @@ -384,5 +403,22 @@ shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = {
'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
};

/**
* @typedef {{
* baseMediaDecodeTime: ?number,
* defaultSampleDuration: number,
* defaultSampleSize: number,
* parsedTRUNs: !Array<shaka.util.ParsedTRUNBox>,
* timescale: number
* }}
*
* @property {?number} baseMediaDecodeTime
* @property {number} defaultSampleDuration
* @property {number} defaultSampleSize
* @property {!Array<shaka.util.ParsedTRUNBox>} parsedTRUNs
* @property {?number} timescale
*/
shaka.cea.Mp4CeaParser.ParsedTRAF;

shaka.media.ClosedCaptionParser.registerParser('video/mp4',
() => new shaka.cea.Mp4CeaParser());

0 comments on commit 745b596

Please sign in to comment.