transmuxer.js 13 KB

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