UNPKG

shaka-player

Version:
561 lines (500 loc) 13.8 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.Mp4BoxParsers'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Mp4Parser'); shaka.util.Mp4BoxParsers = class { /** * Parses a TFHD Box. * @param {!shaka.util.DataViewReader} reader * @param {number} flags * @return {!shaka.util.ParsedTFHDBox} */ static parseTFHD(reader, flags) { let defaultSampleDuration = null; let defaultSampleSize = null; let baseDataOffset = null; const trackId = reader.readUint32(); // Read "track_ID" // Skip "base_data_offset" if present. if (flags & 0x000001) { baseDataOffset = reader.readUint64(); } // Skip "sample_description_index" if present. if (flags & 0x000002) { reader.skip(4); } // Read "default_sample_duration" if present. if (flags & 0x000008) { defaultSampleDuration = reader.readUint32(); } // Read "default_sample_size" if present. if (flags & 0x000010) { defaultSampleSize = reader.readUint32(); } return { trackId, defaultSampleDuration, defaultSampleSize, baseDataOffset, }; } /** * Parses a TFDT Box. * @param {!shaka.util.DataViewReader} reader * @param {number} version * @return {!shaka.util.ParsedTFDTBox} */ static parseTFDT(reader, version) { const baseMediaDecodeTime = version == 1 ? reader.readUint64() : reader.readUint32(); return { baseMediaDecodeTime, }; } /** * Parses a TFDT Box, with a loss of precision beyond 53 bits. * Use only when exact integers are not required, e.g. when * dividing by the timescale. * * @param {!shaka.util.DataViewReader} reader * @param {number} version * @return {!shaka.util.ParsedTFDTBox} */ static parseTFDTInaccurate(reader, version) { if (version == 1) { const high = reader.readUint32(); const low = reader.readUint32(); return { baseMediaDecodeTime: (high * Math.pow(2, 32)) + low, }; } else { return { baseMediaDecodeTime: reader.readUint32(), }; } } /** * Parses a PRFT Box, with a loss of precision beyond 53 bits. * Use only when exact integers are not required, e.g. when * dividing by the timescale. * * @param {!shaka.util.DataViewReader} reader * @param {number} version * @return {!shaka.util.ParsedPRFTBox} */ static parsePRFTInaccurate(reader, version) { reader.readUint32(); // Ignore referenceTrackId const ntpTimestampSec = reader.readUint32(); const ntpTimestampFrac = reader.readUint32(); const ntpTimestamp = ntpTimestampSec * 1000 + ntpTimestampFrac / 2**32 * 1000; let mediaTime; if (version === 0) { mediaTime = reader.readUint32(); } else { const high = reader.readUint32(); const low = reader.readUint32(); mediaTime = (high * Math.pow(2, 32)) + low; } return { mediaTime, ntpTimestamp, }; } /** * Parses a MDHD Box. * @param {!shaka.util.DataViewReader} reader * @param {number} version * @return {!shaka.util.ParsedMDHDBox} */ static parseMDHD(reader, version) { if (version == 1) { reader.skip(8); // Skip "creation_time" reader.skip(8); // Skip "modification_time" } else { reader.skip(4); // Skip "creation_time" reader.skip(4); // Skip "modification_time" } const timescale = reader.readUint32(); reader.skip(4); // Skip "duration" const language = reader.readUint16(); // language is stored as an ISO-639-2/T code in an array of three // 5-bit fields each field is the packed difference between its ASCII // value and 0x60 const languageString = String.fromCharCode((language >> 10) + 0x60) + String.fromCharCode(((language & 0x03c0) >> 5) + 0x60) + String.fromCharCode((language & 0x1f) + 0x60); return { timescale, language: languageString, }; } /** * Parses a TREX Box. * @param {!shaka.util.DataViewReader} reader * @return {!shaka.util.ParsedTREXBox} */ static parseTREX(reader) { reader.skip(4); // Skip "track_ID" reader.skip(4); // Skip "default_sample_description_index" const defaultSampleDuration = reader.readUint32(); const defaultSampleSize = reader.readUint32(); return { defaultSampleDuration, defaultSampleSize, }; } /** * Parses a TRUN Box. * @param {!shaka.util.DataViewReader} reader * @param {number} version * @param {number} flags * @return {!shaka.util.ParsedTRUNBox} */ static parseTRUN(reader, version, flags) { const sampleCount = reader.readUint32(); const sampleData = []; let dataOffset = null; // "data_offset" if (flags & 0x000001) { dataOffset = reader.readUint32(); } // Skip "first_sample_flags" if present. if (flags & 0x000004) { reader.skip(4); } for (let i = 0; i < sampleCount; i++) { /** @type {shaka.util.ParsedTRUNSample} */ const sample = { sampleDuration: null, sampleSize: null, sampleCompositionTimeOffset: null, }; // Read "sample duration" if present. if (flags & 0x000100) { sample.sampleDuration = reader.readUint32(); } // Read "sample_size" if present. if (flags & 0x000200) { sample.sampleSize = reader.readUint32(); } // Skip "sample_flags" if present. if (flags & 0x000400) { reader.skip(4); } // Read "sample_time_offset" if present. if (flags & 0x000800) { sample.sampleCompositionTimeOffset = version == 0 ? reader.readUint32() : reader.readInt32(); } sampleData.push(sample); } return { sampleCount, sampleData, dataOffset, }; } /** * Parses a TKHD Box. * @param {!shaka.util.DataViewReader} reader * @param {number} version * @return {!shaka.util.ParsedTKHDBox} */ static parseTKHD(reader, version) { if (version == 1) { reader.skip(8); // Skip "creation_time" reader.skip(8); // Skip "modification_time" } else { reader.skip(4); // Skip "creation_time" reader.skip(4); // Skip "modification_time" } const trackId = reader.readUint32(); if (version == 1) { reader.skip(8); // Skip "reserved" } else { reader.skip(4); // Skip "reserved" } reader.skip(4); // Skip "duration" reader.skip(8); // Skip "reserved" reader.skip(2); // Skip "layer" reader.skip(2); // Skip "alternate_group" reader.skip(2); // Skip "volume" reader.skip(2); // Skip "reserved" reader.skip(36); // Skip "matrix_structure" const width = reader.readUint16() + (reader.readUint16() / 16); const height = reader.readUint16() + (reader.readUint16() / 16); return { trackId, width, height, }; } /** * Parses a MP4A box. * @param {!shaka.util.DataViewReader} reader * @return {!shaka.util.ParsedMP4ABox} */ static parseMP4A(reader) { reader.skip(6); // Skip "reserved" reader.skip(2); // Skip "data_reference_index" reader.skip(8); // Skip "reserved" const channelCount = reader.readUint16(); reader.skip(2); // Skip "samplesize" reader.skip(2); // Skip "pre_defined" reader.skip(2); // Skip "reserved" const sampleRate = reader.readUint16() + (reader.readUint16() / 65536); return { channelCount, sampleRate, }; } /** * Parses a ESDS box. * @param {!shaka.util.DataViewReader} reader * @return {!shaka.util.ParsedESDSBox} */ static parseESDS(reader) { let codec = 'mp4a'; reader.skip(11); codec += '.' + this.toHex_(reader.readUint8()); // this value is only a single digit codec += '.' + this.toHex_((reader.readUint8() >>> 2) & 0x3f).replace(/^0/, ''); return {codec}; } /** * Parses a AVC box. * @param {!shaka.util.DataViewReader} reader * @param {string} boxName * @return {!shaka.util.ParsedAVCBox} */ static parseAVC(reader, boxName) { reader.skip(87); const codec = boxName + '.' + this.toHex_(reader.readUint8()) + this.toHex_(reader.readUint8()) + this.toHex_(reader.readUint8()); return {codec}; } /** * 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}; } /** * Parses a HDLR box. * @param {!shaka.util.DataViewReader} reader * @return {!shaka.util.ParsedHDLRBox} */ static parseHDLR(reader) { reader.skip(8); // Skip "pre_defined" const data = reader.readBytes(4); let handlerType = ''; handlerType += String.fromCharCode(data[0]); handlerType += String.fromCharCode(data[1]); handlerType += String.fromCharCode(data[2]); handlerType += String.fromCharCode(data[3]); return {handlerType}; } /** * Convert a number to hex * @param {number} x * @return {string} * @private */ static toHex_(x) { return ('0' + x.toString(16).toUpperCase()).slice(-2); } }; /** * @typedef {{ * trackId: number, * defaultSampleDuration: ?number, * defaultSampleSize: ?number, * baseDataOffset: ?number * }} * * @property {number} trackId * As per the spec: an integer that uniquely identifies this * track over the entire life‐time of this presentation * @property {?number} defaultSampleDuration * If specified via flags, this overrides the default sample * duration in the Track Extends Box for this fragment * @property {?number} defaultSampleSize * If specified via flags, this overrides the default sample * size in the Track Extends Box for this fragment * @property {?number} baseDataOffset * If specified via flags, this indicate the base data offset * * @exportDoc */ shaka.util.ParsedTFHDBox; /** * @typedef {{ * baseMediaDecodeTime: number * }} * * @property {number} baseMediaDecodeTime * As per the spec: the absolute decode time, measured on the media * timeline, of the first sample in decode order in the track fragment * * @exportDoc */ shaka.util.ParsedTFDTBox; /** * @typedef {{ * mediaTime: number, * ntpTimestamp: number * }} * * @exportDoc */ shaka.util.ParsedPRFTBox; /** * @typedef {{ * timescale: number, * language: string * }} * * @property {number} timescale * As per the spec: an integer that specifies the time‐scale for this media; * this is the number of time units that pass in one second * @property {string} language * Language code for this media * * @exportDoc */ shaka.util.ParsedMDHDBox; /** * @typedef {{ * defaultSampleDuration: number, * defaultSampleSize: number * }} * * @property {number} defaultSampleDuration * The default sample duration to be used in track fragments * @property {number} defaultSampleSize * The default sample size to be used in track fragments * * @exportDoc */ shaka.util.ParsedTREXBox; /** * @typedef {{ * sampleCount: number, * sampleData: !Array.<shaka.util.ParsedTRUNSample>, * dataOffset: ?number * }} * * @property {number} sampleCount * As per the spec: the number of samples being added in this run; * @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData * An array of size <sampleCount> containing data for each sample * @property {?number} dataOffset * If specified via flags, this indicate the offset of the sample in bytes. * * @exportDoc */ shaka.util.ParsedTRUNBox; /** * @typedef {{ * sampleDuration: ?number, * sampleSize: ?number, * sampleCompositionTimeOffset: ?number * }} * * @property {?number} sampleDuration * The length of the sample in timescale units. * @property {?number} sampleSize * The size of the sample in bytes. * @property {?number} sampleCompositionTimeOffset * The time since the start of the sample in timescale units. Time * offset is based of the start of the sample. If this value is * missing, the accumulated durations preceeding this time sample will * be used to create the start time. * * @exportDoc */ shaka.util.ParsedTRUNSample; /** * @typedef {{ * trackId: number, * width: number, * height: number * }} * * @property {number} trackId * Unique ID indicative of this track * @property {number} width * Width of this track in pixels * @property {number} height * Height of this track in pixels. * * @exportDoc */ shaka.util.ParsedTKHDBox; /** * @typedef {{ * codec: string * }} * * @property {string} codec * A fourcc for a codec * * @exportDoc */ shaka.util.ParsedFRMABox; /** * @typedef {{ * channelCount: number, * sampleRate: number * }} * * @property {number} channelCount * @property {number} sampleRate * * @exportDoc */ shaka.util.ParsedMP4ABox; /** * @typedef {{ * codec: string * }} * * @property {string} codec * * @exportDoc */ shaka.util.ParsedESDSBox; /** * @typedef {{ * codec: string * }} * * @property {string} codec * * @exportDoc */ shaka.util.ParsedAVCBox; /** * @typedef {{ * handlerType: string * }} * * @property {string} handlerType * A four-character code that identifies the type of the media handler or * data handler. For media handlers, this field defines the type of * data—for example, 'vide' for video data, 'soun' for sound data. * * @exportDoc */ shaka.util.ParsedHDLRBox;