ts-inspector.js 15 KB

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