ts-inspector.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. * Parse mpeg2 transport stream packets to extract basic timing information
  8. */
  9. 'use strict';
  10. var StreamTypes = require('../m2ts/stream-types.js');
  11. var handleRollover = require('../m2ts/timestamp-rollover-stream.js').handleRollover;
  12. var probe = {};
  13. probe.ts = require('../m2ts/probe.js');
  14. probe.aac = require('../aac/utils.js');
  15. var ONE_SECOND_IN_TS = require('../utils/clock').ONE_SECOND_IN_TS;
  16. var MP2T_PACKET_LENGTH = 188,
  17. // bytes
  18. SYNC_BYTE = 0x47;
  19. /**
  20. * walks through segment data looking for pat and pmt packets to parse out
  21. * program map table information
  22. */
  23. var parsePsi_ = function parsePsi_(bytes, pmt) {
  24. var startIndex = 0,
  25. endIndex = MP2T_PACKET_LENGTH,
  26. packet,
  27. type;
  28. while (endIndex < bytes.byteLength) {
  29. // Look for a pair of start and end sync bytes in the data..
  30. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  31. // We found a packet
  32. packet = bytes.subarray(startIndex, endIndex);
  33. type = probe.ts.parseType(packet, pmt.pid);
  34. switch (type) {
  35. case 'pat':
  36. pmt.pid = probe.ts.parsePat(packet);
  37. break;
  38. case 'pmt':
  39. var table = probe.ts.parsePmt(packet);
  40. pmt.table = pmt.table || {};
  41. Object.keys(table).forEach(function (key) {
  42. pmt.table[key] = table[key];
  43. });
  44. break;
  45. default:
  46. break;
  47. }
  48. startIndex += MP2T_PACKET_LENGTH;
  49. endIndex += MP2T_PACKET_LENGTH;
  50. continue;
  51. } // If we get here, we have somehow become de-synchronized and we need to step
  52. // forward one byte at a time until we find a pair of sync bytes that denote
  53. // a packet
  54. startIndex++;
  55. endIndex++;
  56. }
  57. };
  58. /**
  59. * walks through the segment data from the start and end to get timing information
  60. * for the first and last audio pes packets
  61. */
  62. var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
  63. var startIndex = 0,
  64. endIndex = MP2T_PACKET_LENGTH,
  65. packet,
  66. type,
  67. pesType,
  68. pusi,
  69. parsed;
  70. var endLoop = false; // Start walking from start of segment to get first audio packet
  71. while (endIndex <= bytes.byteLength) {
  72. // Look for a pair of start and end sync bytes in the data..
  73. if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
  74. // We found a packet
  75. packet = bytes.subarray(startIndex, endIndex);
  76. type = probe.ts.parseType(packet, pmt.pid);
  77. switch (type) {
  78. case 'pes':
  79. pesType = probe.ts.parsePesType(packet, pmt.table);
  80. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  81. if (pesType === 'audio' && pusi) {
  82. parsed = probe.ts.parsePesTime(packet);
  83. if (parsed) {
  84. parsed.type = 'audio';
  85. result.audio.push(parsed);
  86. endLoop = true;
  87. }
  88. }
  89. break;
  90. default:
  91. break;
  92. }
  93. if (endLoop) {
  94. break;
  95. }
  96. startIndex += MP2T_PACKET_LENGTH;
  97. endIndex += MP2T_PACKET_LENGTH;
  98. continue;
  99. } // If we get here, we have somehow become de-synchronized and we need to step
  100. // forward one byte at a time until we find a pair of sync bytes that denote
  101. // a packet
  102. startIndex++;
  103. endIndex++;
  104. } // Start walking from end of segment to get last audio packet
  105. endIndex = bytes.byteLength;
  106. startIndex = endIndex - MP2T_PACKET_LENGTH;
  107. endLoop = false;
  108. while (startIndex >= 0) {
  109. // Look for a pair of start and end sync bytes in the data..
  110. if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) {
  111. // We found a packet
  112. packet = bytes.subarray(startIndex, endIndex);
  113. type = probe.ts.parseType(packet, pmt.pid);
  114. switch (type) {
  115. case 'pes':
  116. pesType = probe.ts.parsePesType(packet, pmt.table);
  117. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  118. if (pesType === 'audio' && pusi) {
  119. parsed = probe.ts.parsePesTime(packet);
  120. if (parsed) {
  121. parsed.type = 'audio';
  122. result.audio.push(parsed);
  123. endLoop = true;
  124. }
  125. }
  126. break;
  127. default:
  128. break;
  129. }
  130. if (endLoop) {
  131. break;
  132. }
  133. startIndex -= MP2T_PACKET_LENGTH;
  134. endIndex -= MP2T_PACKET_LENGTH;
  135. continue;
  136. } // If we get here, we have somehow become de-synchronized and we need to step
  137. // forward one byte at a time until we find a pair of sync bytes that denote
  138. // a packet
  139. startIndex--;
  140. endIndex--;
  141. }
  142. };
  143. /**
  144. * walks through the segment data from the start and end to get timing information
  145. * for the first and last video pes packets as well as timing information for the first
  146. * key frame.
  147. */
  148. var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
  149. var startIndex = 0,
  150. endIndex = MP2T_PACKET_LENGTH,
  151. packet,
  152. type,
  153. pesType,
  154. pusi,
  155. parsed,
  156. frame,
  157. i,
  158. pes;
  159. var endLoop = false;
  160. var currentFrame = {
  161. data: [],
  162. size: 0
  163. }; // Start walking from start of segment to get first video packet
  164. while (endIndex < bytes.byteLength) {
  165. // Look for a pair of start and end sync bytes in the data..
  166. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  167. // We found a packet
  168. packet = bytes.subarray(startIndex, endIndex);
  169. type = probe.ts.parseType(packet, pmt.pid);
  170. switch (type) {
  171. case 'pes':
  172. pesType = probe.ts.parsePesType(packet, pmt.table);
  173. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  174. if (pesType === 'video') {
  175. if (pusi && !endLoop) {
  176. parsed = probe.ts.parsePesTime(packet);
  177. if (parsed) {
  178. parsed.type = 'video';
  179. result.video.push(parsed);
  180. endLoop = true;
  181. }
  182. }
  183. if (!result.firstKeyFrame) {
  184. if (pusi) {
  185. if (currentFrame.size !== 0) {
  186. frame = new Uint8Array(currentFrame.size);
  187. i = 0;
  188. while (currentFrame.data.length) {
  189. pes = currentFrame.data.shift();
  190. frame.set(pes, i);
  191. i += pes.byteLength;
  192. }
  193. if (probe.ts.videoPacketContainsKeyFrame(frame)) {
  194. var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting
  195. // the keyframe seems to work fine with HLS playback
  196. // and definitely preferable to a crash with TypeError...
  197. if (firstKeyFrame) {
  198. result.firstKeyFrame = firstKeyFrame;
  199. result.firstKeyFrame.type = 'video';
  200. } else {
  201. // eslint-disable-next-line
  202. console.warn('Failed to extract PTS/DTS from PES at first keyframe. ' + 'This could be an unusual TS segment, or else mux.js did not ' + 'parse your TS segment correctly. If you know your TS ' + 'segments do contain PTS/DTS on keyframes please file a bug ' + 'report! You can try ffprobe to double check for yourself.');
  203. }
  204. }
  205. currentFrame.size = 0;
  206. }
  207. }
  208. currentFrame.data.push(packet);
  209. currentFrame.size += packet.byteLength;
  210. }
  211. }
  212. break;
  213. default:
  214. break;
  215. }
  216. if (endLoop && result.firstKeyFrame) {
  217. break;
  218. }
  219. startIndex += MP2T_PACKET_LENGTH;
  220. endIndex += MP2T_PACKET_LENGTH;
  221. continue;
  222. } // If we get here, we have somehow become de-synchronized and we need to step
  223. // forward one byte at a time until we find a pair of sync bytes that denote
  224. // a packet
  225. startIndex++;
  226. endIndex++;
  227. } // Start walking from end of segment to get last video packet
  228. endIndex = bytes.byteLength;
  229. startIndex = endIndex - MP2T_PACKET_LENGTH;
  230. endLoop = false;
  231. while (startIndex >= 0) {
  232. // Look for a pair of start and end sync bytes in the data..
  233. if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
  234. // We found a packet
  235. packet = bytes.subarray(startIndex, endIndex);
  236. type = probe.ts.parseType(packet, pmt.pid);
  237. switch (type) {
  238. case 'pes':
  239. pesType = probe.ts.parsePesType(packet, pmt.table);
  240. pusi = probe.ts.parsePayloadUnitStartIndicator(packet);
  241. if (pesType === 'video' && pusi) {
  242. parsed = probe.ts.parsePesTime(packet);
  243. if (parsed) {
  244. parsed.type = 'video';
  245. result.video.push(parsed);
  246. endLoop = true;
  247. }
  248. }
  249. break;
  250. default:
  251. break;
  252. }
  253. if (endLoop) {
  254. break;
  255. }
  256. startIndex -= MP2T_PACKET_LENGTH;
  257. endIndex -= MP2T_PACKET_LENGTH;
  258. continue;
  259. } // If we get here, we have somehow become de-synchronized and we need to step
  260. // forward one byte at a time until we find a pair of sync bytes that denote
  261. // a packet
  262. startIndex--;
  263. endIndex--;
  264. }
  265. };
  266. /**
  267. * Adjusts the timestamp information for the segment to account for
  268. * rollover and convert to seconds based on pes packet timescale (90khz clock)
  269. */
  270. var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
  271. if (segmentInfo.audio && segmentInfo.audio.length) {
  272. var audioBaseTimestamp = baseTimestamp;
  273. if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) {
  274. audioBaseTimestamp = segmentInfo.audio[0].dts;
  275. }
  276. segmentInfo.audio.forEach(function (info) {
  277. info.dts = handleRollover(info.dts, audioBaseTimestamp);
  278. info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds
  279. info.dtsTime = info.dts / ONE_SECOND_IN_TS;
  280. info.ptsTime = info.pts / ONE_SECOND_IN_TS;
  281. });
  282. }
  283. if (segmentInfo.video && segmentInfo.video.length) {
  284. var videoBaseTimestamp = baseTimestamp;
  285. if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) {
  286. videoBaseTimestamp = segmentInfo.video[0].dts;
  287. }
  288. segmentInfo.video.forEach(function (info) {
  289. info.dts = handleRollover(info.dts, videoBaseTimestamp);
  290. info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds
  291. info.dtsTime = info.dts / ONE_SECOND_IN_TS;
  292. info.ptsTime = info.pts / ONE_SECOND_IN_TS;
  293. });
  294. if (segmentInfo.firstKeyFrame) {
  295. var frame = segmentInfo.firstKeyFrame;
  296. frame.dts = handleRollover(frame.dts, videoBaseTimestamp);
  297. frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds
  298. frame.dtsTime = frame.dts / ONE_SECOND_IN_TS;
  299. frame.ptsTime = frame.pts / ONE_SECOND_IN_TS;
  300. }
  301. }
  302. };
  303. /**
  304. * inspects the aac data stream for start and end time information
  305. */
  306. var inspectAac_ = function inspectAac_(bytes) {
  307. var endLoop = false,
  308. audioCount = 0,
  309. sampleRate = null,
  310. timestamp = null,
  311. frameSize = 0,
  312. byteIndex = 0,
  313. packet;
  314. while (bytes.length - byteIndex >= 3) {
  315. var type = probe.aac.parseType(bytes, byteIndex);
  316. switch (type) {
  317. case 'timed-metadata':
  318. // Exit early because we don't have enough to parse
  319. // the ID3 tag header
  320. if (bytes.length - byteIndex < 10) {
  321. endLoop = true;
  322. break;
  323. }
  324. frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  325. // to emit a full packet
  326. if (frameSize > bytes.length) {
  327. endLoop = true;
  328. break;
  329. }
  330. if (timestamp === null) {
  331. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  332. timestamp = probe.aac.parseAacTimestamp(packet);
  333. }
  334. byteIndex += frameSize;
  335. break;
  336. case 'audio':
  337. // Exit early because we don't have enough to parse
  338. // the ADTS frame header
  339. if (bytes.length - byteIndex < 7) {
  340. endLoop = true;
  341. break;
  342. }
  343. frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer
  344. // to emit a full packet
  345. if (frameSize > bytes.length) {
  346. endLoop = true;
  347. break;
  348. }
  349. if (sampleRate === null) {
  350. packet = bytes.subarray(byteIndex, byteIndex + frameSize);
  351. sampleRate = probe.aac.parseSampleRate(packet);
  352. }
  353. audioCount++;
  354. byteIndex += frameSize;
  355. break;
  356. default:
  357. byteIndex++;
  358. break;
  359. }
  360. if (endLoop) {
  361. return null;
  362. }
  363. }
  364. if (sampleRate === null || timestamp === null) {
  365. return null;
  366. }
  367. var audioTimescale = ONE_SECOND_IN_TS / sampleRate;
  368. var result = {
  369. audio: [{
  370. type: 'audio',
  371. dts: timestamp,
  372. pts: timestamp
  373. }, {
  374. type: 'audio',
  375. dts: timestamp + audioCount * 1024 * audioTimescale,
  376. pts: timestamp + audioCount * 1024 * audioTimescale
  377. }]
  378. };
  379. return result;
  380. };
  381. /**
  382. * inspects the transport stream segment data for start and end time information
  383. * of the audio and video tracks (when present) as well as the first key frame's
  384. * start time.
  385. */
  386. var inspectTs_ = function inspectTs_(bytes) {
  387. var pmt = {
  388. pid: null,
  389. table: null
  390. };
  391. var result = {};
  392. parsePsi_(bytes, pmt);
  393. for (var pid in pmt.table) {
  394. if (pmt.table.hasOwnProperty(pid)) {
  395. var type = pmt.table[pid];
  396. switch (type) {
  397. case StreamTypes.H264_STREAM_TYPE:
  398. result.video = [];
  399. parseVideoPes_(bytes, pmt, result);
  400. if (result.video.length === 0) {
  401. delete result.video;
  402. }
  403. break;
  404. case StreamTypes.ADTS_STREAM_TYPE:
  405. result.audio = [];
  406. parseAudioPes_(bytes, pmt, result);
  407. if (result.audio.length === 0) {
  408. delete result.audio;
  409. }
  410. break;
  411. default:
  412. break;
  413. }
  414. }
  415. }
  416. return result;
  417. };
  418. /**
  419. * Inspects segment byte data and returns an object with start and end timing information
  420. *
  421. * @param {Uint8Array} bytes The segment byte data
  422. * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
  423. * timestamps for rollover. This value must be in 90khz clock.
  424. * @return {Object} Object containing start and end frame timing info of segment.
  425. */
  426. var inspect = function inspect(bytes, baseTimestamp) {
  427. var isAacData = probe.aac.isLikelyAacData(bytes);
  428. var result;
  429. if (isAacData) {
  430. result = inspectAac_(bytes);
  431. } else {
  432. result = inspectTs_(bytes);
  433. }
  434. if (!result || !result.audio && !result.video) {
  435. return null;
  436. }
  437. adjustTimestamp_(result, baseTimestamp);
  438. return result;
  439. };
  440. module.exports = {
  441. inspect: inspect,
  442. parseAudioPes_: parseAudioPes_
  443. };