video-segment-stream.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * Constructs a single-track, ISO BMFF media segment from H264 data
  3. * events. The output of this stream can be fed to a SourceBuffer
  4. * configured with a suitable initialization segment.
  5. * @param track {object} track metadata configuration
  6. * @param options {object} transmuxer options object
  7. * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
  8. * gopsToAlignWith list when attempting to align gop pts
  9. */
  10. 'use strict';
  11. var Stream = require('../utils/stream.js');
  12. var mp4 = require('../mp4/mp4-generator.js');
  13. var trackInfo = require('../mp4/track-decode-info.js');
  14. var frameUtils = require('../mp4/frame-utils');
  15. var VIDEO_PROPERTIES = require('../constants/video-properties.js');
  16. var VideoSegmentStream = function VideoSegmentStream(track, options) {
  17. var sequenceNumber = 0,
  18. nalUnits = [],
  19. frameCache = [],
  20. // gopsToAlignWith = [],
  21. config,
  22. pps,
  23. segmentStartPts = null,
  24. segmentEndPts = null,
  25. gops,
  26. ensureNextFrameIsKeyFrame = true;
  27. options = options || {};
  28. VideoSegmentStream.prototype.init.call(this);
  29. this.push = function (nalUnit) {
  30. trackInfo.collectDtsInfo(track, nalUnit);
  31. if (typeof track.timelineStartInfo.dts === 'undefined') {
  32. track.timelineStartInfo.dts = nalUnit.dts;
  33. } // record the track config
  34. if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
  35. config = nalUnit.config;
  36. track.sps = [nalUnit.data];
  37. VIDEO_PROPERTIES.forEach(function (prop) {
  38. track[prop] = config[prop];
  39. }, this);
  40. }
  41. if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
  42. pps = nalUnit.data;
  43. track.pps = [nalUnit.data];
  44. } // buffer video until flush() is called
  45. nalUnits.push(nalUnit);
  46. };
  47. this.processNals_ = function (cacheLastFrame) {
  48. var i;
  49. nalUnits = frameCache.concat(nalUnits); // Throw away nalUnits at the start of the byte stream until
  50. // we find the first AUD
  51. while (nalUnits.length) {
  52. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  53. break;
  54. }
  55. nalUnits.shift();
  56. } // Return early if no video data has been observed
  57. if (nalUnits.length === 0) {
  58. return;
  59. }
  60. var frames = frameUtils.groupNalsIntoFrames(nalUnits);
  61. if (!frames.length) {
  62. return;
  63. } // note that the frame cache may also protect us from cases where we haven't
  64. // pushed data for the entire first or last frame yet
  65. frameCache = frames[frames.length - 1];
  66. if (cacheLastFrame) {
  67. frames.pop();
  68. frames.duration -= frameCache.duration;
  69. frames.nalCount -= frameCache.length;
  70. frames.byteLength -= frameCache.byteLength;
  71. }
  72. if (!frames.length) {
  73. nalUnits = [];
  74. return;
  75. }
  76. this.trigger('timelineStartInfo', track.timelineStartInfo);
  77. if (ensureNextFrameIsKeyFrame) {
  78. gops = frameUtils.groupFramesIntoGops(frames);
  79. if (!gops[0][0].keyFrame) {
  80. gops = frameUtils.extendFirstKeyFrame(gops);
  81. if (!gops[0][0].keyFrame) {
  82. // we haven't yet gotten a key frame, so reset nal units to wait for more nal
  83. // units
  84. nalUnits = [].concat.apply([], frames).concat(frameCache);
  85. frameCache = [];
  86. return;
  87. }
  88. frames = [].concat.apply([], gops);
  89. frames.duration = gops.duration;
  90. }
  91. ensureNextFrameIsKeyFrame = false;
  92. }
  93. if (segmentStartPts === null) {
  94. segmentStartPts = frames[0].pts;
  95. segmentEndPts = segmentStartPts;
  96. }
  97. segmentEndPts += frames.duration;
  98. this.trigger('timingInfo', {
  99. start: segmentStartPts,
  100. end: segmentEndPts
  101. });
  102. for (i = 0; i < frames.length; i++) {
  103. var frame = frames[i];
  104. track.samples = frameUtils.generateSampleTableForFrame(frame);
  105. var mdat = mp4.mdat(frameUtils.concatenateNalDataForFrame(frame));
  106. trackInfo.clearDtsInfo(track);
  107. trackInfo.collectDtsInfo(track, frame);
  108. track.baseMediaDecodeTime = trackInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
  109. var moof = mp4.moof(sequenceNumber, [track]);
  110. sequenceNumber++;
  111. track.initSegment = mp4.initSegment([track]);
  112. var boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
  113. boxes.set(moof);
  114. boxes.set(mdat, moof.byteLength);
  115. this.trigger('data', {
  116. track: track,
  117. boxes: boxes,
  118. sequence: sequenceNumber,
  119. videoFrameDts: frame.dts,
  120. videoFramePts: frame.pts
  121. });
  122. }
  123. nalUnits = [];
  124. };
  125. this.resetTimingAndConfig_ = function () {
  126. config = undefined;
  127. pps = undefined;
  128. segmentStartPts = null;
  129. segmentEndPts = null;
  130. };
  131. this.partialFlush = function () {
  132. this.processNals_(true);
  133. this.trigger('partialdone', 'VideoSegmentStream');
  134. };
  135. this.flush = function () {
  136. this.processNals_(false); // reset config and pps because they may differ across segments
  137. // for instance, when we are rendition switching
  138. this.resetTimingAndConfig_();
  139. this.trigger('done', 'VideoSegmentStream');
  140. };
  141. this.endTimeline = function () {
  142. this.flush();
  143. this.trigger('endedtimeline', 'VideoSegmentStream');
  144. };
  145. this.reset = function () {
  146. this.resetTimingAndConfig_();
  147. frameCache = [];
  148. nalUnits = [];
  149. ensureNextFrameIsKeyFrame = true;
  150. this.trigger('reset');
  151. };
  152. };
  153. VideoSegmentStream.prototype = new Stream();
  154. module.exports = VideoSegmentStream;