video-segment-stream.js 5.4 KB

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