audio-segment-stream.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. 'use strict';
  2. var Stream = require('../utils/stream.js');
  3. var mp4 = require('../mp4/mp4-generator.js');
  4. var audioFrameUtils = require('../mp4/audio-frame-utils');
  5. var trackInfo = require('../mp4/track-decode-info.js');
  6. var ONE_SECOND_IN_TS = require('../utils/clock').ONE_SECOND_IN_TS;
  7. var AUDIO_PROPERTIES = require('../constants/audio-properties.js');
  8. /**
  9. * Constructs a single-track, ISO BMFF media segment from AAC data
  10. * events. The output of this stream can be fed to a SourceBuffer
  11. * configured with a suitable initialization segment.
  12. */
  13. var AudioSegmentStream = function(track, options) {
  14. var
  15. adtsFrames = [],
  16. sequenceNumber = 0,
  17. earliestAllowedDts = 0,
  18. audioAppendStartTs = 0,
  19. videoBaseMediaDecodeTime = Infinity,
  20. segmentStartPts = null,
  21. segmentEndPts = null;
  22. options = options || {};
  23. AudioSegmentStream.prototype.init.call(this);
  24. this.push = function(data) {
  25. trackInfo.collectDtsInfo(track, data);
  26. if (track) {
  27. AUDIO_PROPERTIES.forEach(function(prop) {
  28. track[prop] = data[prop];
  29. });
  30. }
  31. // buffer audio data until end() is called
  32. adtsFrames.push(data);
  33. };
  34. this.setEarliestDts = function(earliestDts) {
  35. earliestAllowedDts = earliestDts;
  36. };
  37. this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) {
  38. videoBaseMediaDecodeTime = baseMediaDecodeTime;
  39. };
  40. this.setAudioAppendStart = function(timestamp) {
  41. audioAppendStartTs = timestamp;
  42. };
  43. this.processFrames_ = function() {
  44. var
  45. frames,
  46. moof,
  47. mdat,
  48. boxes,
  49. timingInfo;
  50. // return early if no audio data has been observed
  51. if (adtsFrames.length === 0) {
  52. return;
  53. }
  54. frames = audioFrameUtils.trimAdtsFramesByEarliestDts(
  55. adtsFrames, track, earliestAllowedDts);
  56. if (frames.length === 0) {
  57. // return early if the frames are all after the earliest allowed DTS
  58. // TODO should we clear the adtsFrames?
  59. return;
  60. }
  61. track.baseMediaDecodeTime = trackInfo.calculateTrackBaseMediaDecodeTime(
  62. track, options.keepOriginalTimestamps);
  63. audioFrameUtils.prefixWithSilence(
  64. track, frames, audioAppendStartTs, videoBaseMediaDecodeTime);
  65. // we have to build the index from byte locations to
  66. // samples (that is, adts frames) in the audio data
  67. track.samples = audioFrameUtils.generateSampleTable(frames);
  68. // concatenate the audio data to constuct the mdat
  69. mdat = mp4.mdat(audioFrameUtils.concatenateFrameData(frames));
  70. adtsFrames = [];
  71. moof = mp4.moof(sequenceNumber, [track]);
  72. // bump the sequence number for next time
  73. sequenceNumber++;
  74. track.initSegment = mp4.initSegment([track]);
  75. // it would be great to allocate this array up front instead of
  76. // throwing away hundreds of media segment fragments
  77. boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
  78. boxes.set(moof);
  79. boxes.set(mdat, moof.byteLength);
  80. trackInfo.clearDtsInfo(track);
  81. if (segmentStartPts === null) {
  82. segmentEndPts = segmentStartPts = frames[0].pts;
  83. }
  84. segmentEndPts += frames.length * (ONE_SECOND_IN_TS * 1024 / track.samplerate);
  85. timingInfo = { start: segmentStartPts };
  86. this.trigger('timingInfo', timingInfo);
  87. this.trigger('data', {track: track, boxes: boxes});
  88. };
  89. this.flush = function() {
  90. this.processFrames_();
  91. // trigger final timing info
  92. this.trigger('timingInfo', {
  93. start: segmentStartPts,
  94. end: segmentEndPts
  95. });
  96. this.resetTiming_();
  97. this.trigger('done', 'AudioSegmentStream');
  98. };
  99. this.partialFlush = function() {
  100. this.processFrames_();
  101. this.trigger('partialdone', 'AudioSegmentStream');
  102. };
  103. this.endTimeline = function() {
  104. this.flush();
  105. this.trigger('endedtimeline', 'AudioSegmentStream');
  106. };
  107. this.resetTiming_ = function() {
  108. trackInfo.clearDtsInfo(track);
  109. segmentStartPts = null;
  110. segmentEndPts = null;
  111. };
  112. this.reset = function() {
  113. this.resetTiming_();
  114. adtsFrames = [];
  115. this.trigger('reset');
  116. };
  117. };
  118. AudioSegmentStream.prototype = new Stream();
  119. module.exports = AudioSegmentStream;