You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.4 KiB

4 years ago
/**
* Constructs a single-track, ISO BMFF media segment from H264 data
* events. The output of this stream can be fed to a SourceBuffer
* configured with a suitable initialization segment.
* @param track {object} track metadata configuration
* @param options {object} transmuxer options object
* @param options.alignGopsAtEnd {boolean} If true, start from the end of the
* gopsToAlignWith list when attempting to align gop pts
*/
'use strict';
var Stream = require('../utils/stream.js');
var mp4 = require('../mp4/mp4-generator.js');
var trackInfo = require('../mp4/track-decode-info.js');
var frameUtils = require('../mp4/frame-utils');
var VIDEO_PROPERTIES = require('../constants/video-properties.js');
var VideoSegmentStream = function(track, options) {
var
sequenceNumber = 0,
nalUnits = [],
frameCache = [],
// gopsToAlignWith = [],
config,
pps,
segmentStartPts = null,
segmentEndPts = null,
gops,
ensureNextFrameIsKeyFrame = true;
options = options || {};
VideoSegmentStream.prototype.init.call(this);
this.push = function(nalUnit) {
trackInfo.collectDtsInfo(track, nalUnit);
if (typeof track.timelineStartInfo.dts === 'undefined') {
track.timelineStartInfo.dts = nalUnit.dts;
}
// record the track config
if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
config = nalUnit.config;
track.sps = [nalUnit.data];
VIDEO_PROPERTIES.forEach(function(prop) {
track[prop] = config[prop];
}, this);
}
if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
!pps) {
pps = nalUnit.data;
track.pps = [nalUnit.data];
}
// buffer video until flush() is called
nalUnits.push(nalUnit);
};
this.processNals_ = function(cacheLastFrame) {
var i;
nalUnits = frameCache.concat(nalUnits);
// Throw away nalUnits at the start of the byte stream until
// we find the first AUD
while (nalUnits.length) {
if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
break;
}
nalUnits.shift();
}
// Return early if no video data has been observed
if (nalUnits.length === 0) {
return;
}
var frames = frameUtils.groupNalsIntoFrames(nalUnits);
if (!frames.length) {
return;
}
// note that the frame cache may also protect us from cases where we haven't
// pushed data for the entire first or last frame yet
frameCache = frames[frames.length - 1];
if (cacheLastFrame) {
frames.pop();
frames.duration -= frameCache.duration;
frames.nalCount -= frameCache.length;
frames.byteLength -= frameCache.byteLength;
}
if (!frames.length) {
nalUnits = [];
return;
}
this.trigger('timelineStartInfo', track.timelineStartInfo);
if (ensureNextFrameIsKeyFrame) {
gops = frameUtils.groupFramesIntoGops(frames);
if (!gops[0][0].keyFrame) {
gops = frameUtils.extendFirstKeyFrame(gops);
if (!gops[0][0].keyFrame) {
// we haven't yet gotten a key frame, so reset nal units to wait for more nal
// units
nalUnits = ([].concat.apply([], frames)).concat(frameCache);
frameCache = [];
return;
}
frames = [].concat.apply([], gops);
frames.duration = gops.duration;
}
ensureNextFrameIsKeyFrame = false;
}
if (segmentStartPts === null) {
segmentStartPts = frames[0].pts;
segmentEndPts = segmentStartPts;
}
segmentEndPts += frames.duration;
this.trigger('timingInfo', {
start: segmentStartPts,
end: segmentEndPts
});
for (i = 0; i < frames.length; i++) {
var frame = frames[i];
track.samples = frameUtils.generateSampleTableForFrame(frame);
var mdat = mp4.mdat(frameUtils.concatenateNalDataForFrame(frame));
trackInfo.clearDtsInfo(track);
trackInfo.collectDtsInfo(track, frame);
track.baseMediaDecodeTime = trackInfo.calculateTrackBaseMediaDecodeTime(
track, options.keepOriginalTimestamps);
var moof = mp4.moof(sequenceNumber, [track]);
sequenceNumber++;
track.initSegment = mp4.initSegment([track]);
var boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
boxes.set(moof);
boxes.set(mdat, moof.byteLength);
this.trigger('data', {
track: track,
boxes: boxes,
sequence: sequenceNumber,
videoFrameDts: frame.dts,
videoFramePts: frame.pts
});
}
nalUnits = [];
};
this.resetTimingAndConfig_ = function() {
config = undefined;
pps = undefined;
segmentStartPts = null;
segmentEndPts = null;
};
this.partialFlush = function() {
this.processNals_(true);
this.trigger('partialdone', 'VideoSegmentStream');
};
this.flush = function() {
this.processNals_(false);
// reset config and pps because they may differ across segments
// for instance, when we are rendition switching
this.resetTimingAndConfig_();
this.trigger('done', 'VideoSegmentStream');
};
this.endTimeline = function() {
this.flush();
this.trigger('endedtimeline', 'VideoSegmentStream');
};
this.reset = function() {
this.resetTimingAndConfig_();
frameCache = [];
nalUnits = [];
ensureNextFrameIsKeyFrame = true;
this.trigger('reset');
};
};
VideoSegmentStream.prototype = new Stream();
module.exports = VideoSegmentStream;