123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756 |
- /**
- * mux.js
- *
- * Copyright (c) Brightcove
- * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
- *
- * Parse the internal MP4 structure into an equivalent javascript
- * object.
- */
- 'use strict';
- var numberHelpers = require('../utils/numbers.js');
- var MAX_UINT32 = numberHelpers.MAX_UINT32;
- var getUint64 = numberHelpers.getUint64;
- var inspectMp4,
- _textifyMp,
- parseMp4Date = function parseMp4Date(seconds) {
- return new Date(seconds * 1000 - 2082844800000);
- },
- parseType = require('../mp4/parse-type'),
- findBox = require('../mp4/find-box'),
- nalParse = function nalParse(avcStream) {
- var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
- result = [],
- i,
- length;
- for (i = 0; i + 4 < avcStream.length; i += length) {
- length = avcView.getUint32(i);
- i += 4; // bail if this doesn't appear to be an H264 stream
- if (length <= 0) {
- result.push('<span style=\'color:red;\'>MALFORMED DATA</span>');
- continue;
- }
- switch (avcStream[i] & 0x1F) {
- case 0x01:
- result.push('slice_layer_without_partitioning_rbsp');
- break;
- case 0x05:
- result.push('slice_layer_without_partitioning_rbsp_idr');
- break;
- case 0x06:
- result.push('sei_rbsp');
- break;
- case 0x07:
- result.push('seq_parameter_set_rbsp');
- break;
- case 0x08:
- result.push('pic_parameter_set_rbsp');
- break;
- case 0x09:
- result.push('access_unit_delimiter_rbsp');
- break;
- default:
- result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F);
- break;
- }
- }
- return result;
- },
- // registry of handlers for individual mp4 box types
- parse = {
- // codingname, not a first-class box type. stsd entries share the
- // same format as real boxes so the parsing infrastructure can be
- // shared
- avc1: function avc1(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- dataReferenceIndex: view.getUint16(6),
- width: view.getUint16(24),
- height: view.getUint16(26),
- horizresolution: view.getUint16(28) + view.getUint16(30) / 16,
- vertresolution: view.getUint16(32) + view.getUint16(34) / 16,
- frameCount: view.getUint16(40),
- depth: view.getUint16(74),
- config: inspectMp4(data.subarray(78, data.byteLength))
- };
- },
- avcC: function avcC(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- configurationVersion: data[0],
- avcProfileIndication: data[1],
- profileCompatibility: data[2],
- avcLevelIndication: data[3],
- lengthSizeMinusOne: data[4] & 0x03,
- sps: [],
- pps: []
- },
- numOfSequenceParameterSets = data[5] & 0x1f,
- numOfPictureParameterSets,
- nalSize,
- offset,
- i; // iterate past any SPSs
- offset = 6;
- for (i = 0; i < numOfSequenceParameterSets; i++) {
- nalSize = view.getUint16(offset);
- offset += 2;
- result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
- offset += nalSize;
- } // iterate past any PPSs
- numOfPictureParameterSets = data[offset];
- offset++;
- for (i = 0; i < numOfPictureParameterSets; i++) {
- nalSize = view.getUint16(offset);
- offset += 2;
- result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
- offset += nalSize;
- }
- return result;
- },
- btrt: function btrt(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- bufferSizeDB: view.getUint32(0),
- maxBitrate: view.getUint32(4),
- avgBitrate: view.getUint32(8)
- };
- },
- edts: function edts(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- elst: function elst(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- edits: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; entryCount--) {
- if (result.version === 0) {
- result.edits.push({
- segmentDuration: view.getUint32(i),
- mediaTime: view.getInt32(i + 4),
- mediaRate: view.getUint16(i + 8) + view.getUint16(i + 10) / (256 * 256)
- });
- i += 12;
- } else {
- result.edits.push({
- segmentDuration: getUint64(data.subarray(i)),
- mediaTime: getUint64(data.subarray(i + 8)),
- mediaRate: view.getUint16(i + 16) + view.getUint16(i + 18) / (256 * 256)
- });
- i += 20;
- }
- }
- return result;
- },
- esds: function esds(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- esId: data[6] << 8 | data[7],
- streamPriority: data[8] & 0x1f,
- decoderConfig: {
- objectProfileIndication: data[11],
- streamType: data[12] >>> 2 & 0x3f,
- bufferSize: data[13] << 16 | data[14] << 8 | data[15],
- maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19],
- avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23],
- decoderConfigDescriptor: {
- tag: data[24],
- length: data[25],
- audioObjectType: data[26] >>> 3 & 0x1f,
- samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01,
- channelConfiguration: data[27] >>> 3 & 0x0f
- }
- }
- };
- },
- ftyp: function ftyp(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- majorBrand: parseType(data.subarray(0, 4)),
- minorVersion: view.getUint32(4),
- compatibleBrands: []
- },
- i = 8;
- while (i < data.byteLength) {
- result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
- i += 4;
- }
- return result;
- },
- dinf: function dinf(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- dref: function dref(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- dataReferences: inspectMp4(data.subarray(8))
- };
- },
- hdlr: function hdlr(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- handlerType: parseType(data.subarray(8, 12)),
- name: ''
- },
- i = 8; // parse out the name field
- for (i = 24; i < data.byteLength; i++) {
- if (data[i] === 0x00) {
- // the name field is null-terminated
- i++;
- break;
- }
- result.name += String.fromCharCode(data[i]);
- } // decode UTF-8 to javascript's internal representation
- // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
- result.name = decodeURIComponent(escape(result.name));
- return result;
- },
- mdat: function mdat(data) {
- return {
- byteLength: data.byteLength,
- nals: nalParse(data)
- };
- },
- mdhd: function mdhd(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- language,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- language: ''
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.timescale = view.getUint32(i);
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.timescale = view.getUint32(i);
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4; // 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
- language = view.getUint16(i);
- result.language += String.fromCharCode((language >> 10) + 0x60);
- result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60);
- result.language += String.fromCharCode((language & 0x1f) + 0x60);
- return result;
- },
- mdia: function mdia(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mfhd: function mfhd(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
- };
- },
- minf: function minf(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- // codingname, not a first-class box type. stsd entries share the
- // same format as real boxes so the parsing infrastructure can be
- // shared
- mp4a: function mp4a(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- // 6 bytes reserved
- dataReferenceIndex: view.getUint16(6),
- // 4 + 4 bytes reserved
- channelcount: view.getUint16(16),
- samplesize: view.getUint16(18),
- // 2 bytes pre_defined
- // 2 bytes reserved
- samplerate: view.getUint16(24) + view.getUint16(26) / 65536
- }; // if there are more bytes to process, assume this is an ISO/IEC
- // 14496-14 MP4AudioSampleEntry and parse the ESDBox
- if (data.byteLength > 28) {
- result.streamDescriptor = inspectMp4(data.subarray(28))[0];
- }
- return result;
- },
- moof: function moof(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- moov: function moov(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mvex: function mvex(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- mvhd: function mvhd(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4))
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.timescale = view.getUint32(i);
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.timescale = view.getUint32(i);
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4; // convert fixed-point, base 16 back to a number
- result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16;
- i += 4;
- result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
- i += 2;
- i += 2;
- i += 2 * 4;
- result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
- i += 9 * 4;
- i += 6 * 4;
- result.nextTrackId = view.getUint32(i);
- return result;
- },
- pdin: function pdin(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- rate: view.getUint32(4),
- initialDelay: view.getUint32(8)
- };
- },
- sdtp: function sdtp(data) {
- var result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- samples: []
- },
- i;
- for (i = 4; i < data.byteLength; i++) {
- result.samples.push({
- dependsOn: (data[i] & 0x30) >> 4,
- isDependedOn: (data[i] & 0x0c) >> 2,
- hasRedundancy: data[i] & 0x03
- });
- }
- return result;
- },
- sidx: require('./parse-sidx.js'),
- smhd: function smhd(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- balance: data[4] + data[5] / 256
- };
- },
- stbl: function stbl(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- ctts: function ctts(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- compositionOffsets: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 8, entryCount--) {
- result.compositionOffsets.push({
- sampleCount: view.getUint32(i),
- sampleOffset: view[result.version === 0 ? 'getUint32' : 'getInt32'](i + 4)
- });
- }
- return result;
- },
- stss: function stss(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4)),
- syncSamples: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 4, entryCount--) {
- result.syncSamples.push(view.getUint32(i));
- }
- return result;
- },
- stco: function stco(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- chunkOffsets: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 4, entryCount--) {
- result.chunkOffsets.push(view.getUint32(i));
- }
- return result;
- },
- stsc: function stsc(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- entryCount = view.getUint32(4),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleToChunks: []
- },
- i;
- for (i = 8; entryCount; i += 12, entryCount--) {
- result.sampleToChunks.push({
- firstChunk: view.getUint32(i),
- samplesPerChunk: view.getUint32(i + 4),
- sampleDescriptionIndex: view.getUint32(i + 8)
- });
- }
- return result;
- },
- stsd: function stsd(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleDescriptions: inspectMp4(data.subarray(8))
- };
- },
- stsz: function stsz(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- sampleSize: view.getUint32(4),
- entries: []
- },
- i;
- for (i = 12; i < data.byteLength; i += 4) {
- result.entries.push(view.getUint32(i));
- }
- return result;
- },
- stts: function stts(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- result = {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- timeToSamples: []
- },
- entryCount = view.getUint32(4),
- i;
- for (i = 8; entryCount; i += 8, entryCount--) {
- result.timeToSamples.push({
- sampleCount: view.getUint32(i),
- sampleDelta: view.getUint32(i + 4)
- });
- }
- return result;
- },
- styp: function styp(data) {
- return parse.ftyp(data);
- },
- tfdt: require('./parse-tfdt.js'),
- tfhd: require('./parse-tfhd.js'),
- tkhd: function tkhd(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
- i = 4,
- result = {
- version: view.getUint8(0),
- flags: new Uint8Array(data.subarray(1, 4))
- };
- if (result.version === 1) {
- i += 4;
- result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 8;
- result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
- i += 4;
- result.trackId = view.getUint32(i);
- i += 4;
- i += 8;
- result.duration = view.getUint32(i); // truncating top 4 bytes
- } else {
- result.creationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.modificationTime = parseMp4Date(view.getUint32(i));
- i += 4;
- result.trackId = view.getUint32(i);
- i += 4;
- i += 4;
- result.duration = view.getUint32(i);
- }
- i += 4;
- i += 2 * 4;
- result.layer = view.getUint16(i);
- i += 2;
- result.alternateGroup = view.getUint16(i);
- i += 2; // convert fixed-point, base 16 back to a number
- result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8;
- i += 2;
- i += 2;
- result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4));
- i += 9 * 4;
- result.width = view.getUint16(i) + view.getUint16(i + 2) / 65536;
- i += 4;
- result.height = view.getUint16(i) + view.getUint16(i + 2) / 65536;
- return result;
- },
- traf: function traf(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- trak: function trak(data) {
- return {
- boxes: inspectMp4(data)
- };
- },
- trex: function trex(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- trackId: view.getUint32(4),
- defaultSampleDescriptionIndex: view.getUint32(8),
- defaultSampleDuration: view.getUint32(12),
- defaultSampleSize: view.getUint32(16),
- sampleDependsOn: data[20] & 0x03,
- sampleIsDependedOn: (data[21] & 0xc0) >> 6,
- sampleHasRedundancy: (data[21] & 0x30) >> 4,
- samplePaddingValue: (data[21] & 0x0e) >> 1,
- sampleIsDifferenceSample: !!(data[21] & 0x01),
- sampleDegradationPriority: view.getUint16(22)
- };
- },
- trun: require('./parse-trun.js'),
- 'url ': function url(data) {
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4))
- };
- },
- vmhd: function vmhd(data) {
- var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
- return {
- version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- graphicsmode: view.getUint16(4),
- opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)])
- };
- }
- };
- /**
- * Return a javascript array of box objects parsed from an ISO base
- * media file.
- * @param data {Uint8Array} the binary data of the media to be inspected
- * @return {array} a javascript array of potentially nested box objects
- */
- inspectMp4 = function inspectMp4(data) {
- var i = 0,
- result = [],
- view,
- size,
- type,
- end,
- box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API
- var ab = new ArrayBuffer(data.length);
- var v = new Uint8Array(ab);
- for (var z = 0; z < data.length; ++z) {
- v[z] = data[z];
- }
- view = new DataView(ab);
- while (i < data.byteLength) {
- // parse box data
- size = view.getUint32(i);
- type = parseType(data.subarray(i + 4, i + 8));
- end = size > 1 ? i + size : data.byteLength; // parse type-specific data
- box = (parse[type] || function (data) {
- return {
- data: data
- };
- })(data.subarray(i + 8, end));
- box.size = size;
- box.type = type; // store this box and move to the next
- result.push(box);
- i = end;
- }
- return result;
- };
- /**
- * Returns a textual representation of the javascript represtentation
- * of an MP4 file. You can use it as an alternative to
- * JSON.stringify() to compare inspected MP4s.
- * @param inspectedMp4 {array} the parsed array of boxes in an MP4
- * file
- * @param depth {number} (optional) the number of ancestor boxes of
- * the elements of inspectedMp4. Assumed to be zero if unspecified.
- * @return {string} a text representation of the parsed MP4
- */
- _textifyMp = function textifyMp4(inspectedMp4, depth) {
- var indent;
- depth = depth || 0;
- indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes
- return inspectedMp4.map(function (box, index) {
- // list the box type first at the current indentation level
- return indent + box.type + '\n' + // the type is already included and handle child boxes separately
- Object.keys(box).filter(function (key) {
- return key !== 'type' && key !== 'boxes'; // output all the box properties
- }).map(function (key) {
- var prefix = indent + ' ' + key + ': ',
- value = box[key]; // print out raw bytes as hexademical
- if (value instanceof Uint8Array || value instanceof Uint32Array) {
- var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (byte) {
- return ' ' + ('00' + byte.toString(16)).slice(-2);
- }).join('').match(/.{1,24}/g);
- if (!bytes) {
- return prefix + '<>';
- }
- if (bytes.length === 1) {
- return prefix + '<' + bytes.join('').slice(1) + '>';
- }
- return prefix + '<\n' + bytes.map(function (line) {
- return indent + ' ' + line;
- }).join('\n') + '\n' + indent + ' >';
- } // stringify generic objects
- return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) {
- if (index === 0) {
- return line;
- }
- return indent + ' ' + line;
- }).join('\n');
- }).join('\n') + ( // recursively textify the child boxes
- box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : '');
- }).join('\n');
- };
- module.exports = {
- inspect: inspectMp4,
- textify: _textifyMp,
- parseType: parseType,
- findBox: findBox,
- parseTraf: parse.traf,
- parseTfdt: parse.tfdt,
- parseHdlr: parse.hdlr,
- parseTfhd: parse.tfhd,
- parseTrun: parse.trun,
- parseSidx: parse.sidx
- };
|