m2ts.js 18 KB

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