audio-segment-stream.js 4.0 KB

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