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
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;
|