transmuxer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. */
  7. 'use strict';
  8. var Stream = require('../utils/stream.js');
  9. var FlvTag = require('./flv-tag.js');
  10. var m2ts = require('../m2ts/m2ts.js');
  11. var AdtsStream = require('../codecs/adts.js');
  12. var H264Stream = require('../codecs/h264').H264Stream;
  13. var CoalesceStream = require('./coalesce-stream.js');
  14. var TagList = require('./tag-list.js');
  15. var _Transmuxer, _VideoSegmentStream, _AudioSegmentStream, collectTimelineInfo, metaDataTag, extraDataTag;
  16. /**
  17. * Store information about the start and end of the tracka and the
  18. * duration for each frame/sample we process in order to calculate
  19. * the baseMediaDecodeTime
  20. */
  21. collectTimelineInfo = function collectTimelineInfo(track, data) {
  22. if (typeof data.pts === 'number') {
  23. if (track.timelineStartInfo.pts === undefined) {
  24. track.timelineStartInfo.pts = data.pts;
  25. } else {
  26. track.timelineStartInfo.pts = Math.min(track.timelineStartInfo.pts, data.pts);
  27. }
  28. }
  29. if (typeof data.dts === 'number') {
  30. if (track.timelineStartInfo.dts === undefined) {
  31. track.timelineStartInfo.dts = data.dts;
  32. } else {
  33. track.timelineStartInfo.dts = Math.min(track.timelineStartInfo.dts, data.dts);
  34. }
  35. }
  36. };
  37. metaDataTag = function metaDataTag(track, pts) {
  38. var tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag
  39. tag.dts = pts;
  40. tag.pts = pts;
  41. tag.writeMetaDataDouble('videocodecid', 7);
  42. tag.writeMetaDataDouble('width', track.width);
  43. tag.writeMetaDataDouble('height', track.height);
  44. return tag;
  45. };
  46. extraDataTag = function extraDataTag(track, pts) {
  47. var i,
  48. tag = new FlvTag(FlvTag.VIDEO_TAG, true);
  49. tag.dts = pts;
  50. tag.pts = pts;
  51. tag.writeByte(0x01); // version
  52. tag.writeByte(track.profileIdc); // profile
  53. tag.writeByte(track.profileCompatibility); // compatibility
  54. tag.writeByte(track.levelIdc); // level
  55. tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
  56. tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits)
  57. tag.writeShort(track.sps[0].length); // data of SPS
  58. tag.writeBytes(track.sps[0]); // SPS
  59. tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?)
  60. for (i = 0; i < track.pps.length; ++i) {
  61. tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS
  62. tag.writeBytes(track.pps[i]); // data of PPS
  63. }
  64. return tag;
  65. };
  66. /**
  67. * Constructs a single-track, media segment from AAC data
  68. * events. The output of this stream can be fed to flash.
  69. */
  70. _AudioSegmentStream = function AudioSegmentStream(track) {
  71. var adtsFrames = [],
  72. videoKeyFrames = [],
  73. oldExtraData;
  74. _AudioSegmentStream.prototype.init.call(this);
  75. this.push = function (data) {
  76. collectTimelineInfo(track, data);
  77. if (track) {
  78. track.audioobjecttype = data.audioobjecttype;
  79. track.channelcount = data.channelcount;
  80. track.samplerate = data.samplerate;
  81. track.samplingfrequencyindex = data.samplingfrequencyindex;
  82. track.samplesize = data.samplesize;
  83. track.extraData = track.audioobjecttype << 11 | track.samplingfrequencyindex << 7 | track.channelcount << 3;
  84. }
  85. data.pts = Math.round(data.pts / 90);
  86. data.dts = Math.round(data.dts / 90); // buffer audio data until end() is called
  87. adtsFrames.push(data);
  88. };
  89. this.flush = function () {
  90. var currentFrame,
  91. adtsFrame,
  92. lastMetaPts,
  93. tags = new TagList(); // return early if no audio data has been observed
  94. if (adtsFrames.length === 0) {
  95. this.trigger('done', 'AudioSegmentStream');
  96. return;
  97. }
  98. lastMetaPts = -Infinity;
  99. while (adtsFrames.length) {
  100. currentFrame = adtsFrames.shift(); // write out a metadata frame at every video key frame
  101. if (videoKeyFrames.length && currentFrame.pts >= videoKeyFrames[0]) {
  102. lastMetaPts = videoKeyFrames.shift();
  103. this.writeMetaDataTags(tags, lastMetaPts);
  104. } // also write out metadata tags every 1 second so that the decoder
  105. // is re-initialized quickly after seeking into a different
  106. // audio configuration.
  107. if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) {
  108. this.writeMetaDataTags(tags, currentFrame.pts);
  109. oldExtraData = track.extraData;
  110. lastMetaPts = currentFrame.pts;
  111. }
  112. adtsFrame = new FlvTag(FlvTag.AUDIO_TAG);
  113. adtsFrame.pts = currentFrame.pts;
  114. adtsFrame.dts = currentFrame.dts;
  115. adtsFrame.writeBytes(currentFrame.data);
  116. tags.push(adtsFrame.finalize());
  117. }
  118. videoKeyFrames.length = 0;
  119. oldExtraData = null;
  120. this.trigger('data', {
  121. track: track,
  122. tags: tags.list
  123. });
  124. this.trigger('done', 'AudioSegmentStream');
  125. };
  126. this.writeMetaDataTags = function (tags, pts) {
  127. var adtsFrame;
  128. adtsFrame = new FlvTag(FlvTag.METADATA_TAG); // For audio, DTS is always the same as PTS. We want to set the DTS
  129. // however so we can compare with video DTS to determine approximate
  130. // packet order
  131. adtsFrame.pts = pts;
  132. adtsFrame.dts = pts; // AAC is always 10
  133. adtsFrame.writeMetaDataDouble('audiocodecid', 10);
  134. adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2);
  135. adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); // Is AAC always 16 bit?
  136. adtsFrame.writeMetaDataDouble('audiosamplesize', 16);
  137. tags.push(adtsFrame.finalize());
  138. adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true); // For audio, DTS is always the same as PTS. We want to set the DTS
  139. // however so we can compare with video DTS to determine approximate
  140. // packet order
  141. adtsFrame.pts = pts;
  142. adtsFrame.dts = pts;
  143. adtsFrame.view.setUint16(adtsFrame.position, track.extraData);
  144. adtsFrame.position += 2;
  145. adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position);
  146. tags.push(adtsFrame.finalize());
  147. };
  148. this.onVideoKeyFrame = function (pts) {
  149. videoKeyFrames.push(pts);
  150. };
  151. };
  152. _AudioSegmentStream.prototype = new Stream();
  153. /**
  154. * Store FlvTags for the h264 stream
  155. * @param track {object} track metadata configuration
  156. */
  157. _VideoSegmentStream = function VideoSegmentStream(track) {
  158. var nalUnits = [],
  159. config,
  160. h264Frame;
  161. _VideoSegmentStream.prototype.init.call(this);
  162. this.finishFrame = function (tags, frame) {
  163. if (!frame) {
  164. return;
  165. } // Check if keyframe and the length of tags.
  166. // This makes sure we write metadata on the first frame of a segment.
  167. if (config && track && track.newMetadata && (frame.keyFrame || tags.length === 0)) {
  168. // Push extra data on every IDR frame in case we did a stream change + seek
  169. var metaTag = metaDataTag(config, frame.dts).finalize();
  170. var extraTag = extraDataTag(track, frame.dts).finalize();
  171. metaTag.metaDataTag = extraTag.metaDataTag = true;
  172. tags.push(metaTag);
  173. tags.push(extraTag);
  174. track.newMetadata = false;
  175. this.trigger('keyframe', frame.dts);
  176. }
  177. frame.endNalUnit();
  178. tags.push(frame.finalize());
  179. h264Frame = null;
  180. };
  181. this.push = function (data) {
  182. collectTimelineInfo(track, data);
  183. data.pts = Math.round(data.pts / 90);
  184. data.dts = Math.round(data.dts / 90); // buffer video until flush() is called
  185. nalUnits.push(data);
  186. };
  187. this.flush = function () {
  188. var currentNal,
  189. tags = new TagList(); // Throw away nalUnits at the start of the byte stream until we find
  190. // the first AUD
  191. while (nalUnits.length) {
  192. if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
  193. break;
  194. }
  195. nalUnits.shift();
  196. } // return early if no video data has been observed
  197. if (nalUnits.length === 0) {
  198. this.trigger('done', 'VideoSegmentStream');
  199. return;
  200. }
  201. while (nalUnits.length) {
  202. currentNal = nalUnits.shift(); // record the track config
  203. if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') {
  204. track.newMetadata = true;
  205. config = currentNal.config;
  206. track.width = config.width;
  207. track.height = config.height;
  208. track.sps = [currentNal.data];
  209. track.profileIdc = config.profileIdc;
  210. track.levelIdc = config.levelIdc;
  211. track.profileCompatibility = config.profileCompatibility;
  212. h264Frame.endNalUnit();
  213. } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') {
  214. track.newMetadata = true;
  215. track.pps = [currentNal.data];
  216. h264Frame.endNalUnit();
  217. } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  218. if (h264Frame) {
  219. this.finishFrame(tags, h264Frame);
  220. }
  221. h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
  222. h264Frame.pts = currentNal.pts;
  223. h264Frame.dts = currentNal.dts;
  224. } else {
  225. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  226. // the current sample is a key frame
  227. h264Frame.keyFrame = true;
  228. }
  229. h264Frame.endNalUnit();
  230. }
  231. h264Frame.startNalUnit();
  232. h264Frame.writeBytes(currentNal.data);
  233. }
  234. if (h264Frame) {
  235. this.finishFrame(tags, h264Frame);
  236. }
  237. this.trigger('data', {
  238. track: track,
  239. tags: tags.list
  240. }); // Continue with the flush process now
  241. this.trigger('done', 'VideoSegmentStream');
  242. };
  243. };
  244. _VideoSegmentStream.prototype = new Stream();
  245. /**
  246. * An object that incrementally transmuxes MPEG2 Trasport Stream
  247. * chunks into an FLV.
  248. */
  249. _Transmuxer = function Transmuxer(options) {
  250. var self = this,
  251. packetStream,
  252. parseStream,
  253. elementaryStream,
  254. videoTimestampRolloverStream,
  255. audioTimestampRolloverStream,
  256. timedMetadataTimestampRolloverStream,
  257. adtsStream,
  258. h264Stream,
  259. videoSegmentStream,
  260. audioSegmentStream,
  261. captionStream,
  262. coalesceStream;
  263. _Transmuxer.prototype.init.call(this);
  264. options = options || {}; // expose the metadata stream
  265. this.metadataStream = new m2ts.MetadataStream();
  266. options.metadataStream = this.metadataStream; // set up the parsing pipeline
  267. packetStream = new m2ts.TransportPacketStream();
  268. parseStream = new m2ts.TransportParseStream();
  269. elementaryStream = new m2ts.ElementaryStream();
  270. videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video');
  271. audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio');
  272. timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata');
  273. adtsStream = new AdtsStream();
  274. h264Stream = new H264Stream();
  275. coalesceStream = new CoalesceStream(options); // disassemble MPEG2-TS packets into elementary streams
  276. packetStream.pipe(parseStream).pipe(elementaryStream); // !!THIS ORDER IS IMPORTANT!!
  277. // demux the streams
  278. elementaryStream.pipe(videoTimestampRolloverStream).pipe(h264Stream);
  279. elementaryStream.pipe(audioTimestampRolloverStream).pipe(adtsStream);
  280. elementaryStream.pipe(timedMetadataTimestampRolloverStream).pipe(this.metadataStream).pipe(coalesceStream); // if CEA-708 parsing is available, hook up a caption stream
  281. captionStream = new m2ts.CaptionStream(options);
  282. h264Stream.pipe(captionStream).pipe(coalesceStream); // hook up the segment streams once track metadata is delivered
  283. elementaryStream.on('data', function (data) {
  284. var i, videoTrack, audioTrack;
  285. if (data.type === 'metadata') {
  286. i = data.tracks.length; // scan the tracks listed in the metadata
  287. while (i--) {
  288. if (data.tracks[i].type === 'video') {
  289. videoTrack = data.tracks[i];
  290. } else if (data.tracks[i].type === 'audio') {
  291. audioTrack = data.tracks[i];
  292. }
  293. } // hook up the video segment stream to the first track with h264 data
  294. if (videoTrack && !videoSegmentStream) {
  295. coalesceStream.numberOfTracks++;
  296. videoSegmentStream = new _VideoSegmentStream(videoTrack); // Set up the final part of the video pipeline
  297. h264Stream.pipe(videoSegmentStream).pipe(coalesceStream);
  298. }
  299. if (audioTrack && !audioSegmentStream) {
  300. // hook up the audio segment stream to the first track with aac data
  301. coalesceStream.numberOfTracks++;
  302. audioSegmentStream = new _AudioSegmentStream(audioTrack); // Set up the final part of the audio pipeline
  303. adtsStream.pipe(audioSegmentStream).pipe(coalesceStream);
  304. if (videoSegmentStream) {
  305. videoSegmentStream.on('keyframe', audioSegmentStream.onVideoKeyFrame);
  306. }
  307. }
  308. }
  309. }); // feed incoming data to the front of the parsing pipeline
  310. this.push = function (data) {
  311. packetStream.push(data);
  312. }; // flush any buffered data
  313. this.flush = function () {
  314. // Start at the top of the pipeline and flush all pending work
  315. packetStream.flush();
  316. }; // Caption data has to be reset when seeking outside buffered range
  317. this.resetCaptions = function () {
  318. captionStream.reset();
  319. }; // Re-emit any data coming from the coalesce stream to the outside world
  320. coalesceStream.on('data', function (event) {
  321. self.trigger('data', event);
  322. }); // Let the consumer know we have finished flushing the entire pipeline
  323. coalesceStream.on('done', function () {
  324. self.trigger('done');
  325. });
  326. };
  327. _Transmuxer.prototype = new Stream(); // forward compatibility
  328. module.exports = _Transmuxer;