probe.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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. * Utilities to detect basic properties and metadata about TS Segments.
  8. */
  9. 'use strict';
  10. var StreamTypes = require('./stream-types.js');
  11. var parsePid = function parsePid(packet) {
  12. var pid = packet[1] & 0x1f;
  13. pid <<= 8;
  14. pid |= packet[2];
  15. return pid;
  16. };
  17. var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
  18. return !!(packet[1] & 0x40);
  19. };
  20. var parseAdaptionField = function parseAdaptionField(packet) {
  21. var offset = 0; // if an adaption field is present, its length is specified by the
  22. // fifth byte of the TS packet header. The adaptation field is
  23. // used to add stuffing to PES packets that don't fill a complete
  24. // TS packet, and to specify some forms of timing and control data
  25. // that we do not currently use.
  26. if ((packet[3] & 0x30) >>> 4 > 0x01) {
  27. offset += packet[4] + 1;
  28. }
  29. return offset;
  30. };
  31. var parseType = function parseType(packet, pmtPid) {
  32. var pid = parsePid(packet);
  33. if (pid === 0) {
  34. return 'pat';
  35. } else if (pid === pmtPid) {
  36. return 'pmt';
  37. } else if (pmtPid) {
  38. return 'pes';
  39. }
  40. return null;
  41. };
  42. var parsePat = function parsePat(packet) {
  43. var pusi = parsePayloadUnitStartIndicator(packet);
  44. var offset = 4 + parseAdaptionField(packet);
  45. if (pusi) {
  46. offset += packet[offset] + 1;
  47. }
  48. return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
  49. };
  50. var parsePmt = function parsePmt(packet) {
  51. var programMapTable = {};
  52. var pusi = parsePayloadUnitStartIndicator(packet);
  53. var payloadOffset = 4 + parseAdaptionField(packet);
  54. if (pusi) {
  55. payloadOffset += packet[payloadOffset] + 1;
  56. } // PMTs can be sent ahead of the time when they should actually
  57. // take effect. We don't believe this should ever be the case
  58. // for HLS but we'll ignore "forward" PMT declarations if we see
  59. // them. Future PMT declarations have the current_next_indicator
  60. // set to zero.
  61. if (!(packet[payloadOffset + 5] & 0x01)) {
  62. return;
  63. }
  64. var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section
  65. sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
  66. tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how
  67. // long the program info descriptors are
  68. programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table
  69. var offset = 12 + programInfoLength;
  70. while (offset < tableEnd) {
  71. var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type
  72. programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry
  73. // skip past the elementary stream descriptors, if present
  74. offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
  75. }
  76. return programMapTable;
  77. };
  78. var parsePesType = function parsePesType(packet, programMapTable) {
  79. var pid = parsePid(packet);
  80. var type = programMapTable[pid];
  81. switch (type) {
  82. case StreamTypes.H264_STREAM_TYPE:
  83. return 'video';
  84. case StreamTypes.ADTS_STREAM_TYPE:
  85. return 'audio';
  86. case StreamTypes.METADATA_STREAM_TYPE:
  87. return 'timed-metadata';
  88. default:
  89. return null;
  90. }
  91. };
  92. var parsePesTime = function parsePesTime(packet) {
  93. var pusi = parsePayloadUnitStartIndicator(packet);
  94. if (!pusi) {
  95. return null;
  96. }
  97. var offset = 4 + parseAdaptionField(packet);
  98. if (offset >= packet.byteLength) {
  99. // From the H 222.0 MPEG-TS spec
  100. // "For transport stream packets carrying PES packets, stuffing is needed when there
  101. // is insufficient PES packet data to completely fill the transport stream packet
  102. // payload bytes. Stuffing is accomplished by defining an adaptation field longer than
  103. // the sum of the lengths of the data elements in it, so that the payload bytes
  104. // remaining after the adaptation field exactly accommodates the available PES packet
  105. // data."
  106. //
  107. // If the offset is >= the length of the packet, then the packet contains no data
  108. // and instead is just adaption field stuffing bytes
  109. return null;
  110. }
  111. var pes = null;
  112. var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value
  113. // and a DTS value. Determine what combination of values is
  114. // available to work with.
  115. ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript
  116. // performs all bitwise operations on 32-bit integers but javascript
  117. // supports a much greater range (52-bits) of integer using standard
  118. // mathematical operations.
  119. // We construct a 31-bit value using bitwise operators over the 31
  120. // most significant bits and then multiply by 4 (equal to a left-shift
  121. // of 2) before we add the final 2 least significant bits of the
  122. // timestamp (equal to an OR.)
  123. if (ptsDtsFlags & 0xC0) {
  124. pes = {}; // the PTS and DTS are not written out directly. For information
  125. // on how they are encoded, see
  126. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  127. pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
  128. pes.pts *= 4; // Left shift by 2
  129. pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
  130. pes.dts = pes.pts;
  131. if (ptsDtsFlags & 0x40) {
  132. pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
  133. pes.dts *= 4; // Left shift by 2
  134. pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
  135. }
  136. }
  137. return pes;
  138. };
  139. var parseNalUnitType = function parseNalUnitType(type) {
  140. switch (type) {
  141. case 0x05:
  142. return 'slice_layer_without_partitioning_rbsp_idr';
  143. case 0x06:
  144. return 'sei_rbsp';
  145. case 0x07:
  146. return 'seq_parameter_set_rbsp';
  147. case 0x08:
  148. return 'pic_parameter_set_rbsp';
  149. case 0x09:
  150. return 'access_unit_delimiter_rbsp';
  151. default:
  152. return null;
  153. }
  154. };
  155. var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
  156. var offset = 4 + parseAdaptionField(packet);
  157. var frameBuffer = packet.subarray(offset);
  158. var frameI = 0;
  159. var frameSyncPoint = 0;
  160. var foundKeyFrame = false;
  161. var nalType; // advance the sync point to a NAL start, if necessary
  162. for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
  163. if (frameBuffer[frameSyncPoint + 2] === 1) {
  164. // the sync point is properly aligned
  165. frameI = frameSyncPoint + 5;
  166. break;
  167. }
  168. }
  169. while (frameI < frameBuffer.byteLength) {
  170. // look at the current byte to determine if we've hit the end of
  171. // a NAL unit boundary
  172. switch (frameBuffer[frameI]) {
  173. case 0:
  174. // skip past non-sync sequences
  175. if (frameBuffer[frameI - 1] !== 0) {
  176. frameI += 2;
  177. break;
  178. } else if (frameBuffer[frameI - 2] !== 0) {
  179. frameI++;
  180. break;
  181. }
  182. if (frameSyncPoint + 3 !== frameI - 2) {
  183. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  184. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  185. foundKeyFrame = true;
  186. }
  187. } // drop trailing zeroes
  188. do {
  189. frameI++;
  190. } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
  191. frameSyncPoint = frameI - 2;
  192. frameI += 3;
  193. break;
  194. case 1:
  195. // skip past non-sync sequences
  196. if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
  197. frameI += 3;
  198. break;
  199. }
  200. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  201. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  202. foundKeyFrame = true;
  203. }
  204. frameSyncPoint = frameI - 2;
  205. frameI += 3;
  206. break;
  207. default:
  208. // the current byte isn't a one or zero, so it cannot be part
  209. // of a sync sequence
  210. frameI += 3;
  211. break;
  212. }
  213. }
  214. frameBuffer = frameBuffer.subarray(frameSyncPoint);
  215. frameI -= frameSyncPoint;
  216. frameSyncPoint = 0; // parse the final nal
  217. if (frameBuffer && frameBuffer.byteLength > 3) {
  218. nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
  219. if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
  220. foundKeyFrame = true;
  221. }
  222. }
  223. return foundKeyFrame;
  224. };
  225. module.exports = {
  226. parseType: parseType,
  227. parsePat: parsePat,
  228. parsePmt: parsePmt,
  229. parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
  230. parsePesType: parsePesType,
  231. parsePesTime: parsePesTime,
  232. videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
  233. };