m2ts.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) 2015 Brightcove
  5. * All rights reserved.
  6. *
  7. * A stream-based mp2t to mp4 converter. This utility can be used to
  8. * deliver mp4s to a SourceBuffer on platforms that support native
  9. * Media Source Extensions.
  10. */
  11. 'use strict';
  12. var Stream = require('../utils/stream.js'),
  13. CaptionStream = require('./caption-stream'),
  14. StreamTypes = require('./stream-types'),
  15. TimestampRolloverStream = require('./timestamp-rollover-stream').TimestampRolloverStream;
  16. var m2tsStreamTypes = require('./stream-types.js');
  17. // object types
  18. var TransportPacketStream, TransportParseStream, ElementaryStream;
  19. // constants
  20. var
  21. MP2T_PACKET_LENGTH = 188, // bytes
  22. SYNC_BYTE = 0x47;
  23. /**
  24. * Splits an incoming stream of binary data into MPEG-2 Transport
  25. * Stream packets.
  26. */
  27. TransportPacketStream = function() {
  28. var
  29. buffer = new Uint8Array(MP2T_PACKET_LENGTH),
  30. bytesInBuffer = 0;
  31. TransportPacketStream.prototype.init.call(this);
  32. // Deliver new bytes to the stream.
  33. this.push = function(bytes) {
  34. var
  35. startIndex = 0,
  36. endIndex = MP2T_PACKET_LENGTH,
  37. everything;
  38. // If there are bytes remaining from the last segment, prepend them to the
  39. // bytes that were pushed in
  40. if (bytesInBuffer) {
  41. everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
  42. everything.set(buffer.subarray(0, bytesInBuffer));
  43. everything.set(bytes, bytesInBuffer);
  44. bytesInBuffer = 0;
  45. } else {
  46. everything = bytes;
  47. }
  48. // While we have enough data for a packet
  49. while (endIndex < everything.byteLength) {
  50. // Look for a pair of start and end sync bytes in the data..
  51. if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
  52. // We found a packet so emit it and jump one whole packet forward in
  53. // the stream
  54. this.trigger('data', everything.subarray(startIndex, endIndex));
  55. startIndex += MP2T_PACKET_LENGTH;
  56. endIndex += MP2T_PACKET_LENGTH;
  57. continue;
  58. }
  59. // If we get here, we have somehow become de-synchronized and we need to step
  60. // forward one byte at a time until we find a pair of sync bytes that denote
  61. // a packet
  62. startIndex++;
  63. endIndex++;
  64. }
  65. // If there was some data left over at the end of the segment that couldn't
  66. // possibly be a whole packet, keep it because it might be the start of a packet
  67. // that continues in the next segment
  68. if (startIndex < everything.byteLength) {
  69. buffer.set(everything.subarray(startIndex), 0);
  70. bytesInBuffer = everything.byteLength - startIndex;
  71. }
  72. };
  73. this.flush = function() {
  74. // If the buffer contains a whole packet when we are being flushed, emit it
  75. // and empty the buffer. Otherwise hold onto the data because it may be
  76. // important for decoding the next segment
  77. if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
  78. this.trigger('data', buffer);
  79. bytesInBuffer = 0;
  80. }
  81. this.trigger('done');
  82. };
  83. };
  84. TransportPacketStream.prototype = new Stream();
  85. /**
  86. * Accepts an MP2T TransportPacketStream and emits data events with parsed
  87. * forms of the individual transport stream packets.
  88. */
  89. TransportParseStream = function() {
  90. var parsePsi, parsePat, parsePmt, self;
  91. TransportParseStream.prototype.init.call(this);
  92. self = this;
  93. this.packetsWaitingForPmt = [];
  94. this.programMapTable = undefined;
  95. parsePsi = function(payload, psi) {
  96. var offset = 0;
  97. // PSI packets may be split into multiple sections and those
  98. // sections may be split into multiple packets. If a PSI
  99. // section starts in this packet, the payload_unit_start_indicator
  100. // will be true and the first byte of the payload will indicate
  101. // the offset from the current position to the start of the
  102. // section.
  103. if (psi.payloadUnitStartIndicator) {
  104. offset += payload[offset] + 1;
  105. }
  106. if (psi.type === 'pat') {
  107. parsePat(payload.subarray(offset), psi);
  108. } else {
  109. parsePmt(payload.subarray(offset), psi);
  110. }
  111. };
  112. parsePat = function(payload, pat) {
  113. pat.section_number = payload[7]; // eslint-disable-line camelcase
  114. pat.last_section_number = payload[8]; // eslint-disable-line camelcase
  115. // skip the PSI header and parse the first PMT entry
  116. self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
  117. pat.pmtPid = self.pmtPid;
  118. };
  119. /**
  120. * Parse out the relevant fields of a Program Map Table (PMT).
  121. * @param payload {Uint8Array} the PMT-specific portion of an MP2T
  122. * packet. The first byte in this array should be the table_id
  123. * field.
  124. * @param pmt {object} the object that should be decorated with
  125. * fields parsed from the PMT.
  126. */
  127. parsePmt = function(payload, pmt) {
  128. var sectionLength, tableEnd, programInfoLength, offset;
  129. // PMTs can be sent ahead of the time when they should actually
  130. // take effect. We don't believe this should ever be the case
  131. // for HLS but we'll ignore "forward" PMT declarations if we see
  132. // them. Future PMT declarations have the current_next_indicator
  133. // set to zero.
  134. if (!(payload[5] & 0x01)) {
  135. return;
  136. }
  137. // overwrite any existing program map table
  138. self.programMapTable = {
  139. video: null,
  140. audio: null,
  141. 'timed-metadata': {}
  142. };
  143. // the mapping table ends at the end of the current section
  144. sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
  145. tableEnd = 3 + sectionLength - 4;
  146. // to determine where the table is, we have to figure out how
  147. // long the program info descriptors are
  148. programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
  149. // advance the offset to the first entry in the mapping table
  150. offset = 12 + programInfoLength;
  151. while (offset < tableEnd) {
  152. var streamType = payload[offset];
  153. var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2];
  154. // only map a single elementary_pid for audio and video stream types
  155. // TODO: should this be done for metadata too? for now maintain behavior of
  156. // multiple metadata streams
  157. if (streamType === StreamTypes.H264_STREAM_TYPE &&
  158. self.programMapTable.video === null) {
  159. self.programMapTable.video = pid;
  160. } else if (streamType === StreamTypes.ADTS_STREAM_TYPE &&
  161. self.programMapTable.audio === null) {
  162. self.programMapTable.audio = pid;
  163. } else if (streamType === StreamTypes.METADATA_STREAM_TYPE) {
  164. // map pid to stream type for metadata streams
  165. self.programMapTable['timed-metadata'][pid] = streamType;
  166. }
  167. // move to the next table entry
  168. // skip past the elementary stream descriptors, if present
  169. offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
  170. }
  171. // record the map on the packet as well
  172. pmt.programMapTable = self.programMapTable;
  173. };
  174. /**
  175. * Deliver a new MP2T packet to the stream.
  176. */
  177. this.push = function(packet) {
  178. var
  179. result = {},
  180. offset = 4;
  181. result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
  182. // pid is a 13-bit field starting at the last bit of packet[1]
  183. result.pid = packet[1] & 0x1f;
  184. result.pid <<= 8;
  185. result.pid |= packet[2];
  186. // if an adaption field is present, its length is specified by the
  187. // fifth byte of the TS packet header. The adaptation field is
  188. // used to add stuffing to PES packets that don't fill a complete
  189. // TS packet, and to specify some forms of timing and control data
  190. // that we do not currently use.
  191. if (((packet[3] & 0x30) >>> 4) > 0x01) {
  192. offset += packet[offset] + 1;
  193. }
  194. // parse the rest of the packet based on the type
  195. if (result.pid === 0) {
  196. result.type = 'pat';
  197. parsePsi(packet.subarray(offset), result);
  198. this.trigger('data', result);
  199. } else if (result.pid === this.pmtPid) {
  200. result.type = 'pmt';
  201. parsePsi(packet.subarray(offset), result);
  202. this.trigger('data', result);
  203. // if there are any packets waiting for a PMT to be found, process them now
  204. while (this.packetsWaitingForPmt.length) {
  205. this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
  206. }
  207. } else if (this.programMapTable === undefined) {
  208. // When we have not seen a PMT yet, defer further processing of
  209. // PES packets until one has been parsed
  210. this.packetsWaitingForPmt.push([packet, offset, result]);
  211. } else {
  212. this.processPes_(packet, offset, result);
  213. }
  214. };
  215. this.processPes_ = function(packet, offset, result) {
  216. // set the appropriate stream type
  217. if (result.pid === this.programMapTable.video) {
  218. result.streamType = StreamTypes.H264_STREAM_TYPE;
  219. } else if (result.pid === this.programMapTable.audio) {
  220. result.streamType = StreamTypes.ADTS_STREAM_TYPE;
  221. } else {
  222. // if not video or audio, it is timed-metadata or unknown
  223. // if unknown, streamType will be undefined
  224. result.streamType = this.programMapTable['timed-metadata'][result.pid];
  225. }
  226. result.type = 'pes';
  227. result.data = packet.subarray(offset);
  228. this.trigger('data', result);
  229. };
  230. };
  231. TransportParseStream.prototype = new Stream();
  232. TransportParseStream.STREAM_TYPES = {
  233. h264: 0x1b,
  234. adts: 0x0f
  235. };
  236. /**
  237. * Reconsistutes program elementary stream (PES) packets from parsed
  238. * transport stream packets. That is, if you pipe an
  239. * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
  240. * events will be events which capture the bytes for individual PES
  241. * packets plus relevant metadata that has been extracted from the
  242. * container.
  243. */
  244. ElementaryStream = function() {
  245. var
  246. self = this,
  247. // PES packet fragments
  248. video = {
  249. data: [],
  250. size: 0
  251. },
  252. audio = {
  253. data: [],
  254. size: 0
  255. },
  256. timedMetadata = {
  257. data: [],
  258. size: 0
  259. },
  260. parsePes = function(payload, pes) {
  261. var ptsDtsFlags;
  262. // get the packet length, this will be 0 for video
  263. pes.packetLength = 6 + ((payload[4] << 8) | payload[5]);
  264. // find out if this packets starts a new keyframe
  265. pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
  266. // PES packets may be annotated with a PTS value, or a PTS value
  267. // and a DTS value. Determine what combination of values is
  268. // available to work with.
  269. ptsDtsFlags = payload[7];
  270. // PTS and DTS are normally stored as a 33-bit number. Javascript
  271. // performs all bitwise operations on 32-bit integers but javascript
  272. // supports a much greater range (52-bits) of integer using standard
  273. // mathematical operations.
  274. // We construct a 31-bit value using bitwise operators over the 31
  275. // most significant bits and then multiply by 4 (equal to a left-shift
  276. // of 2) before we add the final 2 least significant bits of the
  277. // timestamp (equal to an OR.)
  278. if (ptsDtsFlags & 0xC0) {
  279. // the PTS and DTS are not written out directly. For information
  280. // on how they are encoded, see
  281. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  282. pes.pts = (payload[9] & 0x0E) << 27 |
  283. (payload[10] & 0xFF) << 20 |
  284. (payload[11] & 0xFE) << 12 |
  285. (payload[12] & 0xFF) << 5 |
  286. (payload[13] & 0xFE) >>> 3;
  287. pes.pts *= 4; // Left shift by 2
  288. pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
  289. pes.dts = pes.pts;
  290. if (ptsDtsFlags & 0x40) {
  291. pes.dts = (payload[14] & 0x0E) << 27 |
  292. (payload[15] & 0xFF) << 20 |
  293. (payload[16] & 0xFE) << 12 |
  294. (payload[17] & 0xFF) << 5 |
  295. (payload[18] & 0xFE) >>> 3;
  296. pes.dts *= 4; // Left shift by 2
  297. pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
  298. }
  299. }
  300. // the data section starts immediately after the PES header.
  301. // pes_header_data_length specifies the number of header bytes
  302. // that follow the last byte of the field.
  303. pes.data = payload.subarray(9 + payload[8]);
  304. },
  305. flushStream = function(stream, type, forceFlush) {
  306. var
  307. packetData = new Uint8Array(stream.size),
  308. event = {
  309. type: type
  310. },
  311. i = 0,
  312. offset = 0,
  313. packetFlushable = false,
  314. fragment;
  315. // do nothing if there is not enough buffered data for a complete
  316. // PES header
  317. if (!stream.data.length || stream.size < 9) {
  318. return;
  319. }
  320. event.trackId = stream.data[0].pid;
  321. // reassemble the packet
  322. for (i = 0; i < stream.data.length; i++) {
  323. fragment = stream.data[i];
  324. packetData.set(fragment.data, offset);
  325. offset += fragment.data.byteLength;
  326. }
  327. // parse assembled packet's PES header
  328. parsePes(packetData, event);
  329. // non-video PES packets MUST have a non-zero PES_packet_length
  330. // check that there is enough stream data to fill the packet
  331. packetFlushable = type === 'video' || event.packetLength <= stream.size;
  332. // flush pending packets if the conditions are right
  333. if (forceFlush || packetFlushable) {
  334. stream.size = 0;
  335. stream.data.length = 0;
  336. }
  337. // only emit packets that are complete. this is to avoid assembling
  338. // incomplete PES packets due to poor segmentation
  339. if (packetFlushable) {
  340. self.trigger('data', event);
  341. }
  342. };
  343. ElementaryStream.prototype.init.call(this);
  344. this.push = function(data) {
  345. ({
  346. pat: function() {
  347. // we have to wait for the PMT to arrive as well before we
  348. // have any meaningful metadata
  349. },
  350. pes: function() {
  351. var stream, streamType;
  352. switch (data.streamType) {
  353. case StreamTypes.H264_STREAM_TYPE:
  354. case m2tsStreamTypes.H264_STREAM_TYPE:
  355. stream = video;
  356. streamType = 'video';
  357. break;
  358. case StreamTypes.ADTS_STREAM_TYPE:
  359. stream = audio;
  360. streamType = 'audio';
  361. break;
  362. case StreamTypes.METADATA_STREAM_TYPE:
  363. stream = timedMetadata;
  364. streamType = 'timed-metadata';
  365. break;
  366. default:
  367. // ignore unknown stream types
  368. return;
  369. }
  370. // if a new packet is starting, we can flush the completed
  371. // packet
  372. if (data.payloadUnitStartIndicator) {
  373. flushStream(stream, streamType, true);
  374. }
  375. // buffer this fragment until we are sure we've received the
  376. // complete payload
  377. stream.data.push(data);
  378. stream.size += data.data.byteLength;
  379. },
  380. pmt: function() {
  381. var
  382. event = {
  383. type: 'metadata',
  384. tracks: []
  385. },
  386. programMapTable = data.programMapTable;
  387. // translate audio and video streams to tracks
  388. if (programMapTable.video !== null) {
  389. event.tracks.push({
  390. timelineStartInfo: {
  391. baseMediaDecodeTime: 0
  392. },
  393. id: +programMapTable.video,
  394. codec: 'avc',
  395. type: 'video'
  396. });
  397. }
  398. if (programMapTable.audio !== null) {
  399. event.tracks.push({
  400. timelineStartInfo: {
  401. baseMediaDecodeTime: 0
  402. },
  403. id: +programMapTable.audio,
  404. codec: 'adts',
  405. type: 'audio'
  406. });
  407. }
  408. self.trigger('data', event);
  409. }
  410. })[data.type]();
  411. };
  412. /**
  413. * Flush any remaining input. Video PES packets may be of variable
  414. * length. Normally, the start of a new video packet can trigger the
  415. * finalization of the previous packet. That is not possible if no
  416. * more video is forthcoming, however. In that case, some other
  417. * mechanism (like the end of the file) has to be employed. When it is
  418. * clear that no additional data is forthcoming, calling this method
  419. * will flush the buffered packets.
  420. */
  421. this.flush = function() {
  422. // !!THIS ORDER IS IMPORTANT!!
  423. // video first then audio
  424. flushStream(video, 'video');
  425. flushStream(audio, 'audio');
  426. flushStream(timedMetadata, 'timed-metadata');
  427. this.trigger('done');
  428. };
  429. };
  430. ElementaryStream.prototype = new Stream();
  431. var m2ts = {
  432. PAT_PID: 0x0000,
  433. MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
  434. TransportPacketStream: TransportPacketStream,
  435. TransportParseStream: TransportParseStream,
  436. ElementaryStream: ElementaryStream,
  437. TimestampRolloverStream: TimestampRolloverStream,
  438. CaptionStream: CaptionStream.CaptionStream,
  439. Cea608Stream: CaptionStream.Cea608Stream,
  440. MetadataStream: require('./metadata-stream')
  441. };
  442. for (var type in StreamTypes) {
  443. if (StreamTypes.hasOwnProperty(type)) {
  444. m2ts[type] = StreamTypes[type];
  445. }
  446. }
  447. module.exports = m2ts;