323 lines
12 KiB
JavaScript
323 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
|
|
* MP4 demuxer
|
|
*/
|
|
//import {logger} from '../utils/logger';
|
|
|
|
|
|
var _events = require('../events');
|
|
|
|
var _events2 = _interopRequireDefault(_events);
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var UINT32_MAX = Math.pow(2, 32) - 1;
|
|
|
|
var MP4Demuxer = function () {
|
|
function MP4Demuxer(observer, remuxer) {
|
|
_classCallCheck(this, MP4Demuxer);
|
|
|
|
this.observer = observer;
|
|
this.remuxer = remuxer;
|
|
}
|
|
|
|
_createClass(MP4Demuxer, [{
|
|
key: 'resetTimeStamp',
|
|
value: function resetTimeStamp(initPTS) {
|
|
this.initPTS = initPTS;
|
|
}
|
|
}, {
|
|
key: 'resetInitSegment',
|
|
value: function resetInitSegment(initSegment, audioCodec, videoCodec, duration) {
|
|
//jshint unused:false
|
|
if (initSegment && initSegment.byteLength) {
|
|
var initData = this.initData = MP4Demuxer.parseInitSegment(initSegment);
|
|
var tracks = {};
|
|
if (initData.audio) {
|
|
tracks.audio = { container: 'audio/mp4', codec: audioCodec, initSegment: duration ? initSegment : null };
|
|
}
|
|
if (initData.video) {
|
|
tracks.video = { container: 'video/mp4', codec: videoCodec, initSegment: duration ? initSegment : null };
|
|
}
|
|
this.observer.trigger(_events2.default.FRAG_PARSING_INIT_SEGMENT, { tracks: tracks });
|
|
} else {
|
|
if (audioCodec) {
|
|
this.audioCodec = audioCodec;
|
|
}
|
|
if (videoCodec) {
|
|
this.videoCodec = videoCodec;
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: 'append',
|
|
|
|
|
|
// feed incoming data to the front of the parsing pipeline
|
|
value: function append(data, timeOffset, contiguous, accurateTimeOffset) {
|
|
var initData = this.initData;
|
|
if (!initData) {
|
|
this.resetInitSegment(data, this.audioCodec, this.videoCodec);
|
|
initData = this.initData;
|
|
}
|
|
var startDTS = void 0,
|
|
initPTS = this.initPTS;
|
|
if (initPTS === undefined) {
|
|
var _startDTS = MP4Demuxer.getStartDTS(initData, data);
|
|
this.initPTS = initPTS = _startDTS - timeOffset;
|
|
this.observer.trigger(_events2.default.INIT_PTS_FOUND, { initPTS: initPTS });
|
|
}
|
|
MP4Demuxer.offsetStartDTS(initData, data, initPTS);
|
|
startDTS = MP4Demuxer.getStartDTS(initData, data);
|
|
this.remuxer.remux(initData.audio, initData.video, null, null, startDTS, contiguous, accurateTimeOffset, data);
|
|
}
|
|
}, {
|
|
key: 'destroy',
|
|
value: function destroy() {}
|
|
}], [{
|
|
key: 'probe',
|
|
value: function probe(data) {
|
|
if (data.length >= 8) {
|
|
var dataType = MP4Demuxer.bin2str(data.subarray(4, 8));
|
|
return ['moof', 'ftyp', 'styp'].indexOf(dataType) >= 0;
|
|
}
|
|
return false;
|
|
}
|
|
}, {
|
|
key: 'bin2str',
|
|
value: function bin2str(buffer) {
|
|
return String.fromCharCode.apply(null, buffer);
|
|
}
|
|
}, {
|
|
key: 'readUint32',
|
|
value: function readUint32(buffer, offset) {
|
|
if (buffer.data) {
|
|
offset += buffer.start;
|
|
buffer = buffer.data;
|
|
}
|
|
|
|
var val = buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
|
|
return val < 0 ? 4294967296 + val : val;
|
|
}
|
|
}, {
|
|
key: 'writeUint32',
|
|
value: function writeUint32(buffer, offset, value) {
|
|
if (buffer.data) {
|
|
offset += buffer.start;
|
|
buffer = buffer.data;
|
|
}
|
|
buffer[offset] = value >> 24;
|
|
buffer[offset + 1] = value >> 16 & 0xff;
|
|
buffer[offset + 2] = value >> 8 & 0xff;
|
|
buffer[offset + 3] = value & 0xff;
|
|
}
|
|
|
|
// Find the data for a box specified by its path
|
|
|
|
}, {
|
|
key: 'findBox',
|
|
value: function findBox(data, path) {
|
|
var results = [],
|
|
i,
|
|
size,
|
|
type,
|
|
end,
|
|
subresults,
|
|
start,
|
|
endbox;
|
|
|
|
if (data.data) {
|
|
start = data.start;
|
|
end = data.end;
|
|
data = data.data;
|
|
} else {
|
|
start = 0;
|
|
end = data.byteLength;
|
|
}
|
|
|
|
if (!path.length) {
|
|
// short-circuit the search for empty paths
|
|
return null;
|
|
}
|
|
|
|
for (i = start; i < end;) {
|
|
size = MP4Demuxer.readUint32(data, i);
|
|
type = MP4Demuxer.bin2str(data.subarray(i + 4, i + 8));
|
|
endbox = size > 1 ? i + size : end;
|
|
|
|
if (type === path[0]) {
|
|
|
|
if (path.length === 1) {
|
|
// this is the end of the path and we've found the box we were
|
|
// looking for
|
|
results.push({ data: data, start: i + 8, end: endbox });
|
|
} else {
|
|
// recursively search for the next box along the path
|
|
subresults = MP4Demuxer.findBox({ data: data, start: i + 8, end: endbox }, path.slice(1));
|
|
if (subresults.length) {
|
|
results = results.concat(subresults);
|
|
}
|
|
}
|
|
}
|
|
i = endbox;
|
|
}
|
|
|
|
// we've finished searching all of data
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Parses an MP4 initialization segment and extracts stream type and
|
|
* timescale values for any declared tracks. Timescale values indicate the
|
|
* number of clock ticks per second to assume for time-based values
|
|
* elsewhere in the MP4.
|
|
*
|
|
* To determine the start time of an MP4, you need two pieces of
|
|
* information: the timescale unit and the earliest base media decode
|
|
* time. Multiple timescales can be specified within an MP4 but the
|
|
* base media decode time is always expressed in the timescale from
|
|
* the media header box for the track:
|
|
* ```
|
|
* moov > trak > mdia > mdhd.timescale
|
|
* moov > trak > mdia > hdlr
|
|
* ```
|
|
* @param init {Uint8Array} the bytes of the init segment
|
|
* @return {object} a hash of track type to timescale values or null if
|
|
* the init segment is malformed.
|
|
*/
|
|
|
|
}, {
|
|
key: 'parseInitSegment',
|
|
value: function parseInitSegment(initSegment) {
|
|
var result = [];
|
|
var traks = MP4Demuxer.findBox(initSegment, ['moov', 'trak']);
|
|
|
|
traks.forEach(function (trak) {
|
|
var tkhd = MP4Demuxer.findBox(trak, ['tkhd'])[0];
|
|
if (tkhd) {
|
|
var version = tkhd.data[tkhd.start];
|
|
var index = version === 0 ? 12 : 20;
|
|
var trackId = MP4Demuxer.readUint32(tkhd, index);
|
|
|
|
var mdhd = MP4Demuxer.findBox(trak, ['mdia', 'mdhd'])[0];
|
|
if (mdhd) {
|
|
version = mdhd.data[mdhd.start];
|
|
index = version === 0 ? 12 : 20;
|
|
var timescale = MP4Demuxer.readUint32(mdhd, index);
|
|
|
|
var hdlr = MP4Demuxer.findBox(trak, ['mdia', 'hdlr'])[0];
|
|
if (hdlr) {
|
|
var hdlrType = MP4Demuxer.bin2str(hdlr.data.subarray(hdlr.start + 8, hdlr.start + 12));
|
|
var type = { 'soun': 'audio', 'vide': 'video' }[hdlrType];
|
|
if (type) {
|
|
result[trackId] = { timescale: timescale, type: type };
|
|
result[type] = { timescale: timescale, id: trackId };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Determine the base media decode start time, in seconds, for an MP4
|
|
* fragment. If multiple fragments are specified, the earliest time is
|
|
* returned.
|
|
*
|
|
* The base media decode time can be parsed from track fragment
|
|
* metadata:
|
|
* ```
|
|
* moof > traf > tfdt.baseMediaDecodeTime
|
|
* ```
|
|
* It requires the timescale value from the mdhd to interpret.
|
|
*
|
|
* @param timescale {object} a hash of track ids to timescale values.
|
|
* @return {number} the earliest base media decode start time for the
|
|
* fragment, in seconds
|
|
*/
|
|
|
|
}, {
|
|
key: 'getStartDTS',
|
|
value: function getStartDTS(initData, fragment) {
|
|
var trafs, baseTimes, result;
|
|
|
|
// we need info from two childrend of each track fragment box
|
|
trafs = MP4Demuxer.findBox(fragment, ['moof', 'traf']);
|
|
|
|
// determine the start times for each track
|
|
baseTimes = [].concat.apply([], trafs.map(function (traf) {
|
|
return MP4Demuxer.findBox(traf, ['tfhd']).map(function (tfhd) {
|
|
var id, scale, baseTime;
|
|
|
|
// get the track id from the tfhd
|
|
id = MP4Demuxer.readUint32(tfhd, 4);
|
|
// assume a 90kHz clock if no timescale was specified
|
|
scale = initData[id].timescale || 90e3;
|
|
|
|
// get the base media decode time from the tfdt
|
|
baseTime = MP4Demuxer.findBox(traf, ['tfdt']).map(function (tfdt) {
|
|
var version, result;
|
|
|
|
version = tfdt.data[tfdt.start];
|
|
result = MP4Demuxer.readUint32(tfdt, 4);
|
|
if (version === 1) {
|
|
result *= Math.pow(2, 32);
|
|
|
|
result += MP4Demuxer.readUint32(tfdt, 8);
|
|
}
|
|
return result;
|
|
})[0];
|
|
baseTime = baseTime || Infinity;
|
|
|
|
// convert base time to seconds
|
|
return baseTime / scale;
|
|
});
|
|
}));
|
|
|
|
// return the minimum
|
|
result = Math.min.apply(null, baseTimes);
|
|
return isFinite(result) ? result : 0;
|
|
}
|
|
}, {
|
|
key: 'offsetStartDTS',
|
|
value: function offsetStartDTS(initData, fragment, timeOffset) {
|
|
MP4Demuxer.findBox(fragment, ['moof', 'traf']).map(function (traf) {
|
|
return MP4Demuxer.findBox(traf, ['tfhd']).map(function (tfhd) {
|
|
// get the track id from the tfhd
|
|
var id = MP4Demuxer.readUint32(tfhd, 4);
|
|
// assume a 90kHz clock if no timescale was specified
|
|
var timescale = initData[id].timescale || 90e3;
|
|
|
|
// get the base media decode time from the tfdt
|
|
MP4Demuxer.findBox(traf, ['tfdt']).map(function (tfdt) {
|
|
var version = tfdt.data[tfdt.start];
|
|
var baseMediaDecodeTime = MP4Demuxer.readUint32(tfdt, 4);
|
|
if (version === 0) {
|
|
MP4Demuxer.writeUint32(tfdt, 4, baseMediaDecodeTime - timeOffset * timescale);
|
|
} else {
|
|
baseMediaDecodeTime *= Math.pow(2, 32);
|
|
baseMediaDecodeTime += MP4Demuxer.readUint32(tfdt, 8);
|
|
baseMediaDecodeTime -= timeOffset * timescale;
|
|
var upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
|
|
var lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
|
|
MP4Demuxer.writeUint32(tfdt, 4, upper);
|
|
MP4Demuxer.writeUint32(tfdt, 8, lower);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return MP4Demuxer;
|
|
}();
|
|
|
|
exports.default = MP4Demuxer; |