Source: lib/util/ts_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.TsParser');
  7. goog.require('shaka.Deprecate');
  8. goog.require('shaka.log');
  9. goog.require('shaka.util.ExpGolomb');
  10. goog.require('shaka.util.Id3Utils');
  11. goog.require('shaka.util.StringUtils');
  12. goog.require('shaka.util.Uint8ArrayUtils');
  13. /**
  14. * @see https://en.wikipedia.org/wiki/MPEG_transport_stream
  15. * @export
  16. */
  17. shaka.util.TsParser = class {
  18. /** */
  19. constructor() {
  20. /** @private {?number} */
  21. this.pmtId_ = null;
  22. /** @private {boolean} */
  23. this.pmtParsed_ = false;
  24. /** @private {?number} */
  25. this.videoPid_ = null;
  26. /** @private {?string} */
  27. this.videoCodec_ = null;
  28. /** @private {!Array.<!Array.<Uint8Array>>} */
  29. this.videoData_ = [];
  30. /** @private {!Array.<shaka.extern.MPEG_PES>} */
  31. this.videoPes_ = [];
  32. /** @private {?number} */
  33. this.audioPid_ = null;
  34. /** @private {?string} */
  35. this.audioCodec_ = null;
  36. /** @private {!Array.<!Array.<Uint8Array>>} */
  37. this.audioData_ = [];
  38. /** @private {!Array.<shaka.extern.MPEG_PES>} */
  39. this.audioPes_ = [];
  40. /** @private {?number} */
  41. this.id3Pid_ = null;
  42. /** @private {!Array.<!Array.<Uint8Array>>} */
  43. this.id3Data_ = [];
  44. /** @private {?number} */
  45. this.referencePts_ = null;
  46. /** @private {?number} */
  47. this.referenceDts_ = null;
  48. /** @private {?shaka.util.TsParser.OpusMetadata} */
  49. this.opusMetadata_ = null;
  50. }
  51. /**
  52. * Clear previous data
  53. *
  54. * @export
  55. */
  56. clearData() {
  57. this.videoData_ = [];
  58. this.videoPes_ = [];
  59. this.audioData_ = [];
  60. this.audioPes_ = [];
  61. this.id3Data_ = [];
  62. }
  63. /**
  64. * Parse the given data
  65. *
  66. * @param {Uint8Array} data
  67. * @return {!shaka.util.TsParser}
  68. * @export
  69. */
  70. parse(data) {
  71. const packetLength = shaka.util.TsParser.PacketLength_;
  72. // A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
  73. // one PID.
  74. if (data.length < 3 * packetLength) {
  75. return this;
  76. }
  77. const syncOffset = Math.max(0, shaka.util.TsParser.syncOffset(data));
  78. const length = data.length - (data.length + syncOffset) % packetLength;
  79. let unknownPIDs = false;
  80. // loop through TS packets
  81. for (let start = syncOffset; start < length; start += packetLength) {
  82. if (data[start] == 0x47) {
  83. const payloadUnitStartIndicator = !!(data[start + 1] & 0x40);
  84. // pid is a 13-bit field starting at the last 5 bits of TS[1]
  85. const pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];
  86. const adaptationFieldControl = (data[start + 3] & 0x30) >> 4;
  87. // if an adaption field is present, its length is specified by the
  88. // fifth byte of the TS packet header.
  89. let offset;
  90. if (adaptationFieldControl > 1) {
  91. offset = start + 5 + data[start + 4];
  92. // continue if there is only adaptation field
  93. if (offset == start + packetLength) {
  94. continue;
  95. }
  96. } else {
  97. offset = start + 4;
  98. }
  99. switch (pid) {
  100. case 0:
  101. if (payloadUnitStartIndicator) {
  102. offset += data[offset] + 1;
  103. }
  104. this.pmtId_ = this.getPmtId_(data, offset);
  105. break;
  106. case 17:
  107. case 0x1fff:
  108. break;
  109. case this.pmtId_: {
  110. if (payloadUnitStartIndicator) {
  111. offset += data[offset] + 1;
  112. }
  113. const parsedPIDs = this.parsePMT_(data, offset);
  114. // only update track id if track PID found while parsing PMT
  115. // this is to avoid resetting the PID to -1 in case
  116. // track PID transiently disappears from the stream
  117. // this could happen in case of transient missing audio samples
  118. // for example
  119. // NOTE this is only the PID of the track as found in TS,
  120. // but we are not using this for MP4 track IDs.
  121. if (parsedPIDs.video != -1) {
  122. this.videoPid_ = parsedPIDs.video;
  123. this.videoCodec_ = parsedPIDs.videoCodec;
  124. }
  125. if (parsedPIDs.audio != -1) {
  126. this.audioPid_ = parsedPIDs.audio;
  127. this.audioCodec_ = parsedPIDs.audioCodec;
  128. }
  129. if (parsedPIDs.id3 != -1) {
  130. this.id3Pid_ = parsedPIDs.id3;
  131. }
  132. if (unknownPIDs && !this.pmtParsed_) {
  133. shaka.log.debug('reparse from beginning');
  134. unknownPIDs = false;
  135. // we set it to -188, the += 188 in the for loop will reset
  136. // start to 0
  137. start = syncOffset - packetLength;
  138. }
  139. this.pmtParsed_ = true;
  140. break;
  141. }
  142. case this.videoPid_: {
  143. const videoData = data.subarray(offset, start + packetLength);
  144. if (payloadUnitStartIndicator) {
  145. this.videoData_.push([videoData]);
  146. } else if (this.videoData_.length) {
  147. const prevVideoData = this.videoData_[this.videoData_.length - 1];
  148. if (prevVideoData) {
  149. this.videoData_[this.videoData_.length - 1].push(videoData);
  150. }
  151. }
  152. break;
  153. }
  154. case this.audioPid_: {
  155. const audioData = data.subarray(offset, start + packetLength);
  156. if (payloadUnitStartIndicator) {
  157. this.audioData_.push([audioData]);
  158. } else if (this.audioData_.length) {
  159. const prevAudioData = this.audioData_[this.audioData_.length - 1];
  160. if (prevAudioData) {
  161. this.audioData_[this.audioData_.length - 1].push(audioData);
  162. }
  163. }
  164. break;
  165. }
  166. case this.id3Pid_: {
  167. const id3Data = data.subarray(offset, start + packetLength);
  168. if (payloadUnitStartIndicator) {
  169. this.id3Data_.push([id3Data]);
  170. } else if (this.id3Data_.length) {
  171. const prevId3Data = this.id3Data_[this.id3Data_.length - 1];
  172. if (prevId3Data) {
  173. this.id3Data_[this.id3Data_.length - 1].push(id3Data);
  174. }
  175. }
  176. break;
  177. }
  178. default:
  179. unknownPIDs = true;
  180. break;
  181. }
  182. } else {
  183. shaka.log.warning('Found TS packet that do not start with 0x47');
  184. }
  185. }
  186. return this;
  187. }
  188. /**
  189. * Get the PMT ID from the PAT
  190. *
  191. * @param {Uint8Array} data
  192. * @param {number} offset
  193. * @return {number}
  194. * @private
  195. */
  196. getPmtId_(data, offset) {
  197. // skip the PSI header and parse the first PMT entry
  198. return ((data[offset + 10] & 0x1f) << 8) | data[offset + 11];
  199. }
  200. /**
  201. * Parse PMT
  202. *
  203. * @param {Uint8Array} data
  204. * @param {number} offset
  205. * @return {!shaka.util.TsParser.PMT}
  206. * @private
  207. */
  208. parsePMT_(data, offset) {
  209. const StringUtils = shaka.util.StringUtils;
  210. const result = {
  211. audio: -1,
  212. video: -1,
  213. id3: -1,
  214. audioCodec: '',
  215. videoCodec: '',
  216. };
  217. const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
  218. const tableEnd = offset + 3 + sectionLength - 4;
  219. // to determine where the table is, we have to figure out how
  220. // long the program info descriptors are
  221. const programInfoLength =
  222. ((data[offset + 10] & 0x0f) << 8) | data[offset + 11];
  223. // advance the offset to the first entry in the mapping table
  224. offset += 12 + programInfoLength;
  225. while (offset < tableEnd) {
  226. const pid = ((data[offset + 1] & 0x1f) << 8) | data[offset + 2];
  227. const esInfoLength = ((data[offset + 3] & 0x0f) << 8) | data[offset + 4];
  228. switch (data[offset]) {
  229. case 0x06:
  230. // stream_type 6 can mean a lot of different things in case of DVB.
  231. // We need to look at the descriptors. Right now, we're only
  232. // interested in a few audio and video formats,.
  233. if (esInfoLength > 0) {
  234. let parsePos = offset + 5;
  235. let remaining = esInfoLength;
  236. // Descriptor info: https://www.streamguru.de/mpeg-analyzer/supported-descriptor-list/
  237. while (remaining > 2) {
  238. const descriptorId = data[parsePos];
  239. const descriptorLen = data[parsePos + 1] + 2;
  240. switch (descriptorId) {
  241. // Registration descriptor
  242. case 0x05: {
  243. const registrationData =
  244. data.subarray(parsePos + 2, parsePos + descriptorLen);
  245. const registration =
  246. StringUtils.fromCharCode(registrationData);
  247. if (result.audio == -1 && registration === 'Opus') {
  248. result.audio = pid;
  249. result.audioCodec = 'opus';
  250. } else if (result.video == -1 && registration === 'AV01') {
  251. result.video = pid;
  252. result.videoCodec = 'av1';
  253. }
  254. break;
  255. }
  256. // DVB Descriptor for AC-3
  257. case 0x6a:
  258. if (result.audio == -1) {
  259. result.audio = pid;
  260. result.audioCodec = 'ac3';
  261. }
  262. break;
  263. // DVB Descriptor for EC-3
  264. case 0x7a:
  265. if (result.audio == -1) {
  266. result.audio = pid;
  267. result.audioCodec = 'ec3';
  268. }
  269. break;
  270. // DVB Descriptor for AAC
  271. case 0x7c:
  272. if (result.audio == -1) {
  273. result.audio = pid;
  274. result.audioCodec = 'aac';
  275. }
  276. break;
  277. // DVB extension descriptor
  278. case 0x7f:
  279. if (result.audioCodec == 'opus') {
  280. const extensionDescriptorId = data[parsePos + 2];
  281. let channelConfigCode = null;
  282. // User defined (provisional Opus)
  283. if (extensionDescriptorId === 0x80) {
  284. channelConfigCode = data[parsePos + 3];
  285. }
  286. if (channelConfigCode == null) {
  287. // Not Supported Opus channel count.
  288. break;
  289. }
  290. const channelCount = (channelConfigCode & 0x0F) === 0 ?
  291. 2 : (channelConfigCode & 0x0F);
  292. this.opusMetadata_ = {
  293. channelCount,
  294. channelConfigCode,
  295. sampleRate: 48000,
  296. };
  297. }
  298. break;
  299. }
  300. parsePos += descriptorLen;
  301. remaining -= descriptorLen;
  302. }
  303. }
  304. break;
  305. // SAMPLE-AES AAC
  306. case 0xcf:
  307. break;
  308. // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
  309. case 0x0f:
  310. if (result.audio == -1) {
  311. result.audio = pid;
  312. result.audioCodec = 'aac';
  313. }
  314. break;
  315. // LOAS/LATM AAC
  316. case 0x11:
  317. if (result.audio == -1) {
  318. result.audio = pid;
  319. result.audioCodec = 'aac-loas';
  320. }
  321. break;
  322. // Packetized metadata (ID3)
  323. case 0x15:
  324. if (result.id3 == -1) {
  325. result.id3 = pid;
  326. }
  327. break;
  328. // SAMPLE-AES AVC
  329. case 0xdb:
  330. break;
  331. // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
  332. case 0x1b:
  333. if (result.video == -1) {
  334. result.video = pid;
  335. result.videoCodec = 'avc';
  336. }
  337. break;
  338. // ISO/IEC 11172-3 (MPEG-1 audio)
  339. // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
  340. case 0x03:
  341. case 0x04:
  342. if (result.audio == -1) {
  343. result.audio = pid;
  344. result.audioCodec = 'mp3';
  345. }
  346. break;
  347. // HEVC
  348. case 0x24:
  349. if (result.video == -1) {
  350. result.video = pid;
  351. result.videoCodec = 'hvc';
  352. }
  353. break;
  354. // AC-3
  355. case 0x81:
  356. if (result.audio == -1) {
  357. result.audio = pid;
  358. result.audioCodec = 'ac3';
  359. }
  360. break;
  361. // EC-3
  362. case 0x84:
  363. case 0x87:
  364. if (result.audio == -1) {
  365. result.audio = pid;
  366. result.audioCodec = 'ec3';
  367. }
  368. break;
  369. default:
  370. // shaka.log.warning('Unknown stream type:', data[offset]);
  371. break;
  372. }
  373. // move to the next table entry
  374. // skip past the elementary stream descriptors, if present
  375. offset += esInfoLength + 5;
  376. }
  377. return result;
  378. }
  379. /**
  380. * Parse PES
  381. *
  382. * @param {Uint8Array} data
  383. * @return {?shaka.extern.MPEG_PES}
  384. * @private
  385. */
  386. parsePES_(data) {
  387. const startPrefix = (data[0] << 16) | (data[1] << 8) | data[2];
  388. // In certain live streams, the start of a TS fragment has ts packets
  389. // that are frame data that is continuing from the previous fragment. This
  390. // is to check that the pes data is the start of a new pes data
  391. if (startPrefix !== 1) {
  392. return null;
  393. }
  394. /** @type {shaka.extern.MPEG_PES} */
  395. const pes = {
  396. data: new Uint8Array(0),
  397. // get the packet length, this will be 0 for video
  398. packetLength: ((data[4] << 8) | data[5]),
  399. pts: null,
  400. dts: null,
  401. nalus: [],
  402. };
  403. // if PES parsed length is not zero and greater than total received length,
  404. // stop parsing. PES might be truncated. minus 6 : PES header size
  405. if (pes.packetLength && pes.packetLength > data.byteLength - 6) {
  406. return null;
  407. }
  408. // PES packets may be annotated with a PTS value, or a PTS value
  409. // and a DTS value. Determine what combination of values is
  410. // available to work with.
  411. const ptsDtsFlags = data[7];
  412. // PTS and DTS are normally stored as a 33-bit number. Javascript
  413. // performs all bitwise operations on 32-bit integers but javascript
  414. // supports a much greater range (52-bits) of integer using standard
  415. // mathematical operations.
  416. // We construct a 31-bit value using bitwise operators over the 31
  417. // most significant bits and then multiply by 4 (equal to a left-shift
  418. // of 2) before we add the final 2 least significant bits of the
  419. // timestamp (equal to an OR.)
  420. if (ptsDtsFlags & 0xC0) {
  421. // the PTS and DTS are not written out directly. For information
  422. // on how they are encoded, see
  423. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  424. const pts =
  425. (data[9] & 0x0e) * 536870912 + // 1 << 29
  426. (data[10] & 0xff) * 4194304 + // 1 << 22
  427. (data[11] & 0xfe) * 16384 + // 1 << 14
  428. (data[12] & 0xff) * 128 + // 1 << 7
  429. (data[13] & 0xfe) / 2;
  430. if (this.referencePts_ == null) {
  431. this.referencePts_ = pts;
  432. }
  433. pes.pts = this.handleRollover_(pts, this.referencePts_);
  434. this.referencePts_ = pes.pts;
  435. pes.dts = pes.pts;
  436. if (ptsDtsFlags & 0x40) {
  437. const dts =
  438. (data[14] & 0x0e) * 536870912 + // 1 << 29
  439. (data[15] & 0xff) * 4194304 + // 1 << 22
  440. (data[16] & 0xfe) * 16384 + // 1 << 14
  441. (data[17] & 0xff) * 128 + // 1 << 7
  442. (data[18] & 0xfe) / 2;
  443. if (this.referenceDts_ == null) {
  444. this.referenceDts_ = dts;
  445. }
  446. pes.dts = this.handleRollover_(dts, this.referenceDts_);
  447. }
  448. this.referenceDts_ = pes.dts;
  449. }
  450. const pesHdrLen = data[8];
  451. // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
  452. const payloadStartOffset = pesHdrLen + 9;
  453. if (data.byteLength <= payloadStartOffset) {
  454. return null;
  455. }
  456. pes.data = data.subarray(payloadStartOffset);
  457. return pes;
  458. }
  459. /**
  460. * Parse AVC Nalus
  461. *
  462. * The code is based on hls.js
  463. * Credit to https://github.com/video-dev/hls.js/blob/master/src/demux/tsdemuxer.ts
  464. *
  465. * @param {shaka.extern.MPEG_PES} pes
  466. * @param {?shaka.extern.MPEG_PES=} nextPes
  467. * @return {!Array.<shaka.extern.VideoNalu>}
  468. * @export
  469. */
  470. parseAvcNalus(pes, nextPes) {
  471. shaka.Deprecate.deprecateFeature(5,
  472. 'TsParser.parseAvcNalus',
  473. 'Please use parseNalus function instead.');
  474. return this.parseNalus(pes);
  475. }
  476. /**
  477. * Parse AVC and HVC Nalus
  478. *
  479. * The code is based on hls.js
  480. * Credit to https://github.com/video-dev/hls.js/blob/master/src/demux/tsdemuxer.ts
  481. *
  482. * @param {shaka.extern.MPEG_PES} pes
  483. * @param {?shaka.extern.VideoNalu=} lastNalu
  484. * @param {?number=} lastState
  485. * @return {!Array.<shaka.extern.VideoNalu>}
  486. * @export
  487. */
  488. parseNalus(pes, lastNalu, lastState) {
  489. const timescale = shaka.util.TsParser.Timescale;
  490. const time = pes.pts ? pes.pts / timescale : null;
  491. const data = pes.data;
  492. const len = data.byteLength;
  493. let naluHeaderSize = 1;
  494. if (this.videoCodec_ == 'hvc') {
  495. naluHeaderSize = 2;
  496. }
  497. // A NALU does not contain is its size.
  498. // The Annex B specification solves this by requiring ‘Start Codes’ to
  499. // precede each NALU. A start code is 2 or 3 0x00 bytes followed with a
  500. // 0x01 byte. e.g. 0x000001 or 0x00000001.
  501. // More info in: https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream/24890903#24890903
  502. let numZeros = lastState || 0;
  503. /** @type {!Array.<shaka.extern.VideoNalu>} */
  504. const nalus = [];
  505. // Start position includes the first byte where we read the type.
  506. // The data we extract begins at the next byte.
  507. let lastNaluStart = -1;
  508. // Extracted from the first byte.
  509. let lastNaluType = 0;
  510. /** @type {?shaka.extern.VideoNalu} */
  511. let infoOfLastNalu;
  512. for (let i = 0; i < len; ++i) {
  513. const value = data[i];
  514. if (!value) {
  515. numZeros++;
  516. } else if (numZeros >= 2 && value == 1) {
  517. if (lastNalu && !nalus.length && lastNaluStart == -1) {
  518. // If we are scanning the next PES, we need append the data to the
  519. // previous Nalu and don't scan for more nalus.
  520. const startCodeSize = numZeros > 3 ? 3 : numZeros;
  521. const lastByteToKeep = i - startCodeSize;
  522. // Optimization
  523. if (lastState && lastByteToKeep != 0) {
  524. const prevData = data.subarray(0, lastByteToKeep);
  525. lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
  526. lastNalu.data, prevData);
  527. lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
  528. lastNalu.fullData, prevData);
  529. }
  530. }
  531. // We just read a start code. Consume the NALU we passed, if any.
  532. if (lastNaluStart >= 0) {
  533. // Because the start position includes the header size.
  534. const firstByteToKeep = lastNaluStart + naluHeaderSize;
  535. // Compute the last byte to keep. The start code is at most 3 zeros.
  536. // Any earlier zeros are not part of the start code.
  537. const startCodeSize = (numZeros > 3 ? 3 : numZeros) + 1;
  538. const lastByteToKeep = i - startCodeSize;
  539. /** @type {shaka.extern.VideoNalu} */
  540. const nalu = {
  541. // subarray's end position is exclusive, so add one.
  542. data: data.subarray(firstByteToKeep, lastByteToKeep + 1),
  543. fullData: data.subarray(lastNaluStart, lastByteToKeep + 1),
  544. type: lastNaluType,
  545. time: time,
  546. };
  547. nalus.push(nalu);
  548. } else if (lastNalu && !nalus.length) {
  549. const overflow = i - numZeros;
  550. if (overflow > 0) {
  551. const prevData = data.subarray(0, overflow);
  552. lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
  553. lastNalu.data, prevData);
  554. lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
  555. lastNalu.fullData, prevData);
  556. }
  557. }
  558. // We just read a start code, so there should be another byte here, at
  559. // least, for the NALU type. Check just in case.
  560. if (i >= len - naluHeaderSize) {
  561. shaka.log.warning('Malformed TS, incomplete NALU, ignoring.');
  562. return nalus;
  563. }
  564. // Advance and read the type of the next NALU.
  565. i++;
  566. lastNaluStart = i;
  567. if (this.videoCodec_ == 'hvc') {
  568. lastNaluType = (data[i] >> 1) & 0x3f;
  569. } else {
  570. lastNaluType = data[i] & 0x1f;
  571. }
  572. numZeros = 0;
  573. } else {
  574. numZeros = 0;
  575. }
  576. // If we have gone through all the data from the PES and we have an
  577. // unfinished Nalu, we will try to use the next PES to complete the
  578. // unfinished Nalu.
  579. if (i >= (len - 1) && lastNaluStart >= 0 && numZeros >= 0) {
  580. // The rest of the buffer was a NALU.
  581. // Because the start position includes the header size.
  582. const firstByteToKeep = lastNaluStart + naluHeaderSize;
  583. infoOfLastNalu = {
  584. data: data.subarray(firstByteToKeep, len),
  585. fullData: data.subarray(lastNaluStart, len),
  586. type: lastNaluType,
  587. time: time,
  588. };
  589. }
  590. }
  591. if (infoOfLastNalu) {
  592. nalus.push(infoOfLastNalu);
  593. }
  594. if (!nalus.length && lastNalu) {
  595. lastNalu.data = shaka.util.Uint8ArrayUtils.concat(
  596. lastNalu.data, data);
  597. lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
  598. lastNalu.fullData, data);
  599. }
  600. lastState = Math.min(3, numZeros);
  601. return nalus;
  602. }
  603. /**
  604. * Return the ID3 metadata
  605. *
  606. * @return {!Array.<shaka.extern.ID3Metadata>}
  607. * @export
  608. */
  609. getMetadata() {
  610. const timescale = shaka.util.TsParser.Timescale;
  611. const metadata = [];
  612. for (const id3DataArray of this.id3Data_) {
  613. const id3Data = shaka.util.Uint8ArrayUtils.concat(...id3DataArray);
  614. const pes = this.parsePES_(id3Data);
  615. if (pes) {
  616. metadata.push({
  617. cueTime: pes.pts ? pes.pts / timescale : null,
  618. data: pes.data,
  619. frames: shaka.util.Id3Utils.getID3Frames(pes.data),
  620. dts: pes.dts,
  621. pts: pes.pts,
  622. });
  623. }
  624. }
  625. return metadata;
  626. }
  627. /**
  628. * Return the audio data
  629. *
  630. * @return {!Array.<shaka.extern.MPEG_PES>}
  631. * @export
  632. */
  633. getAudioData() {
  634. if (this.audioData_.length && !this.audioPes_.length) {
  635. let sort = false;
  636. for (const audioDataArray of this.audioData_) {
  637. const audioData = shaka.util.Uint8ArrayUtils.concat(...audioDataArray);
  638. const pes = this.parsePES_(audioData);
  639. let previousPes = this.audioPes_.length ?
  640. this.audioPes_[this.audioPes_.length - 1] : null;
  641. if (pes && pes.pts != null && pes.dts != null && (!previousPes ||
  642. (previousPes.pts != pes.pts && previousPes.dts != pes.dts))) {
  643. if (this.audioPes_.length && pes.dts < (previousPes.dts || 0)) {
  644. sort = true;
  645. }
  646. this.audioPes_.push(pes);
  647. } else if (this.audioPes_.length) {
  648. const data = pes ? pes.data : audioData;
  649. if (!data) {
  650. continue;
  651. }
  652. previousPes = this.audioPes_.pop();
  653. previousPes.data =
  654. shaka.util.Uint8ArrayUtils.concat(previousPes.data, data);
  655. this.audioPes_.push(previousPes);
  656. }
  657. }
  658. if (sort) {
  659. this.audioPes_ = this.audioPes_.sort((a, b) => {
  660. const deltadts = (a.dts || 0) - (b.dts || 0);
  661. const deltapts = (a.pts || 0) - (b.pts || 0);
  662. return deltadts || deltapts;
  663. });
  664. }
  665. }
  666. return this.audioPes_;
  667. }
  668. /**
  669. * Return the video data
  670. *
  671. * @param {boolean=} naluProcessing
  672. * @return {!Array.<shaka.extern.MPEG_PES>}
  673. * @export
  674. */
  675. getVideoData(naluProcessing = true) {
  676. if (this.videoData_.length && !this.videoPes_.length) {
  677. let sort = false;
  678. for (const videoDataArray of this.videoData_) {
  679. const videoData = shaka.util.Uint8ArrayUtils.concat(...videoDataArray);
  680. const pes = this.parsePES_(videoData);
  681. let previousPes = this.videoPes_.length ?
  682. this.videoPes_[this.videoPes_.length - 1] : null;
  683. if (pes && pes.pts != null && pes.dts != null && (!previousPes ||
  684. (previousPes.pts != pes.pts && previousPes.dts != pes.dts))) {
  685. if (this.videoPes_.length && pes.dts < (previousPes.dts || 0)) {
  686. sort = true;
  687. }
  688. this.videoPes_.push(pes);
  689. } else if (this.videoPes_.length) {
  690. const data = pes ? pes.data : videoData;
  691. if (!data) {
  692. continue;
  693. }
  694. previousPes = this.videoPes_.pop();
  695. previousPes.data =
  696. shaka.util.Uint8ArrayUtils.concat(previousPes.data, data);
  697. this.videoPes_.push(previousPes);
  698. }
  699. }
  700. if (naluProcessing) {
  701. let lastNalu;
  702. let lastState;
  703. const pesWithLength = [];
  704. for (const pes of this.videoPes_) {
  705. pes.nalus = this.parseNalus(pes, lastNalu, lastState);
  706. if (pes.nalus.length) {
  707. pesWithLength.push(pes);
  708. lastNalu = pes.nalus[pes.nalus.length - 1];
  709. }
  710. }
  711. this.videoPes_ = pesWithLength;
  712. }
  713. if (sort) {
  714. this.videoPes_ = this.videoPes_.sort((a, b) => {
  715. const deltadts = (a.dts || 0) - (b.dts || 0);
  716. const deltapts = (a.pts || 0) - (b.pts || 0);
  717. return deltadts || deltapts;
  718. });
  719. }
  720. }
  721. if (!naluProcessing) {
  722. const prevVideoPes = this.videoPes_;
  723. this.videoPes_ = [];
  724. return prevVideoPes;
  725. }
  726. return this.videoPes_;
  727. }
  728. /**
  729. * Return the start time for the audio and video
  730. *
  731. * @param {string} contentType
  732. * @return {?number}
  733. * @export
  734. */
  735. getStartTime(contentType) {
  736. const timescale = shaka.util.TsParser.Timescale;
  737. if (contentType == 'audio') {
  738. let audioStartTime = null;
  739. const audioData = this.getAudioData();
  740. if (audioData.length) {
  741. const pes = audioData[0];
  742. audioStartTime = Math.min(pes.dts, pes.pts) / timescale;
  743. }
  744. return audioStartTime;
  745. } else if (contentType == 'video') {
  746. let videoStartTime = null;
  747. const videoData = this.getVideoData(/* naluProcessing= */ false);
  748. if (videoData.length) {
  749. const pes = videoData[0];
  750. videoStartTime = Math.min(pes.dts, pes.pts) / timescale;
  751. }
  752. return videoStartTime;
  753. }
  754. return null;
  755. }
  756. /**
  757. * Return the audio and video codecs
  758. *
  759. * @return {{audio: ?string, video: ?string}}
  760. * @export
  761. */
  762. getCodecs() {
  763. return {
  764. audio: this.audioCodec_,
  765. video: this.videoCodec_,
  766. };
  767. }
  768. /**
  769. * Return the video data
  770. *
  771. * @return {!Array.<shaka.extern.VideoNalu>}
  772. * @export
  773. */
  774. getVideoNalus() {
  775. const nalus = [];
  776. for (const pes of this.getVideoData()) {
  777. nalus.push(...pes.nalus);
  778. }
  779. return nalus;
  780. }
  781. /**
  782. * Return the video resolution
  783. *
  784. * @return {{height: ?string, width: ?string}}
  785. * @export
  786. */
  787. getVideoResolution() {
  788. shaka.Deprecate.deprecateFeature(5,
  789. 'TsParser.getVideoResolution',
  790. 'Please use getVideoInfo function instead.');
  791. const videoInfo = this.getVideoInfo();
  792. return {
  793. height: videoInfo.height,
  794. width: videoInfo.width,
  795. };
  796. }
  797. /**
  798. * Return the video information
  799. *
  800. * @return {{height: ?string, width: ?string, codec: ?string}}
  801. * @export
  802. */
  803. getVideoInfo() {
  804. if (this.videoCodec_ == 'hvc') {
  805. return this.getHvcInfo_();
  806. }
  807. return this.getAvcInfo_();
  808. }
  809. /**
  810. * Return the video information for AVC
  811. *
  812. * @return {{height: ?string, width: ?string, codec: ?string}}
  813. * @private
  814. */
  815. getAvcInfo_() {
  816. const TsParser = shaka.util.TsParser;
  817. const videoInfo = {
  818. height: null,
  819. width: null,
  820. codec: null,
  821. };
  822. const videoNalus = this.getVideoNalus();
  823. if (!videoNalus.length) {
  824. return videoInfo;
  825. }
  826. const spsNalu = videoNalus.find((nalu) => {
  827. return nalu.type == TsParser.H264_NALU_TYPE_SPS_;
  828. });
  829. if (!spsNalu) {
  830. return videoInfo;
  831. }
  832. const expGolombDecoder = new shaka.util.ExpGolomb(spsNalu.data);
  833. // profile_idc
  834. const profileIdc = expGolombDecoder.readUnsignedByte();
  835. // constraint_set[0-5]_flag
  836. const profileCompatibility = expGolombDecoder.readUnsignedByte();
  837. // level_idc u(8)
  838. const levelIdc = expGolombDecoder.readUnsignedByte();
  839. // seq_parameter_set_id
  840. expGolombDecoder.skipExpGolomb();
  841. // some profiles have more optional data we don't need
  842. if (TsParser.PROFILES_WITH_OPTIONAL_SPS_DATA_.includes(profileIdc)) {
  843. const chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  844. if (chromaFormatIdc === 3) {
  845. // separate_colour_plane_flag
  846. expGolombDecoder.skipBits(1);
  847. }
  848. // bit_depth_luma_minus8
  849. expGolombDecoder.skipExpGolomb();
  850. // bit_depth_chroma_minus8
  851. expGolombDecoder.skipExpGolomb();
  852. // qpprime_y_zero_transform_bypass_flag
  853. expGolombDecoder.skipBits(1);
  854. // seq_scaling_matrix_present_flag
  855. if (expGolombDecoder.readBoolean()) {
  856. const scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
  857. for (let i = 0; i < scalingListCount; i++) {
  858. // seq_scaling_list_present_flag[ i ]
  859. if (expGolombDecoder.readBoolean()) {
  860. if (i < 6) {
  861. expGolombDecoder.skipScalingList(16);
  862. } else {
  863. expGolombDecoder.skipScalingList(64);
  864. }
  865. }
  866. }
  867. }
  868. }
  869. // log2_max_frame_num_minus4
  870. expGolombDecoder.skipExpGolomb();
  871. const picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  872. if (picOrderCntType === 0) {
  873. // log2_max_pic_order_cnt_lsb_minus4
  874. expGolombDecoder.readUnsignedExpGolomb();
  875. } else if (picOrderCntType === 1) {
  876. // delta_pic_order_always_zero_flag
  877. expGolombDecoder.skipBits(1);
  878. // offset_for_non_ref_pic
  879. expGolombDecoder.skipExpGolomb();
  880. // offset_for_top_to_bottom_field
  881. expGolombDecoder.skipExpGolomb();
  882. const numRefFramesInPicOrderCntCycle =
  883. expGolombDecoder.readUnsignedExpGolomb();
  884. for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  885. // offset_for_ref_frame[ i ]
  886. expGolombDecoder.skipExpGolomb();
  887. }
  888. }
  889. // max_num_ref_frames
  890. expGolombDecoder.skipExpGolomb();
  891. // gaps_in_frame_num_value_allowed_flag
  892. expGolombDecoder.skipBits(1);
  893. const picWidthInMbsMinus1 =
  894. expGolombDecoder.readUnsignedExpGolomb();
  895. const picHeightInMapUnitsMinus1 =
  896. expGolombDecoder.readUnsignedExpGolomb();
  897. const frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  898. if (frameMbsOnlyFlag === 0) {
  899. // mb_adaptive_frame_field_flag
  900. expGolombDecoder.skipBits(1);
  901. }
  902. // direct_8x8_inference_flag
  903. expGolombDecoder.skipBits(1);
  904. let frameCropLeftOffset = 0;
  905. let frameCropRightOffset = 0;
  906. let frameCropTopOffset = 0;
  907. let frameCropBottomOffset = 0;
  908. // frame_cropping_flag
  909. if (expGolombDecoder.readBoolean()) {
  910. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  911. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  912. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  913. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  914. }
  915. videoInfo.height = String(((2 - frameMbsOnlyFlag) *
  916. (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) -
  917. (frameCropBottomOffset * 2));
  918. videoInfo.width = String(((picWidthInMbsMinus1 + 1) * 16) -
  919. frameCropLeftOffset * 2 - frameCropRightOffset * 2);
  920. videoInfo.codec = 'avc1.' + this.byteToHex_(profileIdc) +
  921. this.byteToHex_(profileCompatibility) + this.byteToHex_(levelIdc);
  922. return videoInfo;
  923. }
  924. /**
  925. * Return the video information for HVC
  926. *
  927. * @return {{height: ?string, width: ?string, codec: ?string}}
  928. * @private
  929. */
  930. getHvcInfo_() {
  931. const TsParser = shaka.util.TsParser;
  932. const videoInfo = {
  933. height: null,
  934. width: null,
  935. codec: null,
  936. };
  937. const videoNalus = this.getVideoNalus();
  938. if (!videoNalus.length) {
  939. return videoInfo;
  940. }
  941. const spsNalu = videoNalus.find((nalu) => {
  942. return nalu.type == TsParser.H265_NALU_TYPE_SPS_;
  943. });
  944. if (!spsNalu) {
  945. return videoInfo;
  946. }
  947. const gb = new shaka.util.ExpGolomb(
  948. spsNalu.fullData, /* convertEbsp2rbsp= */ true);
  949. // remove NALu Header
  950. gb.readUnsignedByte();
  951. gb.readUnsignedByte();
  952. // SPS
  953. gb.readBits(4); // video_paramter_set_id
  954. const maxSubLayersMinus1 = gb.readBits(3);
  955. gb.readBoolean(); // temporal_id_nesting_flag
  956. // profile_tier_level begin
  957. const generalProfileSpace = gb.readBits(2);
  958. const generalTierFlag = gb.readBits(1);
  959. const generalProfileIdc = gb.readBits(5);
  960. const generalProfileCompatibilityFlags = gb.readBits(32);
  961. const generalConstraintIndicatorFlags1 = gb.readUnsignedByte();
  962. const generalConstraintIndicatorFlags2 = gb.readUnsignedByte();
  963. const generalConstraintIndicatorFlags3 = gb.readUnsignedByte();
  964. const generalConstraintIndicatorFlags4 = gb.readUnsignedByte();
  965. const generalConstraintIndicatorFlags5 = gb.readUnsignedByte();
  966. const generalConstraintIndicatorFlags6 = gb.readUnsignedByte();
  967. const generalLevelIdc = gb.readUnsignedByte();
  968. const subLayerProfilePresentFlag = [];
  969. const subLayerLevelPresentFlag = [];
  970. for (let i = 0; i < maxSubLayersMinus1; i++) {
  971. subLayerProfilePresentFlag.push(gb.readBoolean());
  972. subLayerLevelPresentFlag.push(gb.readBoolean());
  973. }
  974. if (maxSubLayersMinus1 > 0) {
  975. for (let i = maxSubLayersMinus1; i < 8; i++) {
  976. gb.readBits(2);
  977. }
  978. }
  979. for (let i = 0; i < maxSubLayersMinus1; i++) {
  980. if (subLayerProfilePresentFlag[i]) {
  981. gb.readBits(88);
  982. }
  983. if (subLayerLevelPresentFlag[i]) {
  984. gb.readUnsignedByte();
  985. }
  986. }
  987. // profile_tier_level end
  988. gb.readUnsignedExpGolomb(); // seq_parameter_set_id
  989. const chromaFormatIdc = gb.readUnsignedExpGolomb();
  990. if (chromaFormatIdc == 3) {
  991. gb.readBits(1); // separate_colour_plane_flag
  992. }
  993. const picWidthInLumaSamples = gb.readUnsignedExpGolomb();
  994. const picHeightInLumaSamples = gb.readUnsignedExpGolomb();
  995. let leftOffset = 0;
  996. let rightOffset = 0;
  997. let topOffset = 0;
  998. let bottomOffset = 0;
  999. const conformanceWindowFlag = gb.readBoolean();
  1000. if (conformanceWindowFlag) {
  1001. leftOffset += gb.readUnsignedExpGolomb();
  1002. rightOffset += gb.readUnsignedExpGolomb();
  1003. topOffset += gb.readUnsignedExpGolomb();
  1004. bottomOffset += gb.readUnsignedExpGolomb();
  1005. }
  1006. const subWc = chromaFormatIdc === 1 || chromaFormatIdc === 2 ? 2 : 1;
  1007. const subHc = chromaFormatIdc === 1 ? 2 : 1;
  1008. videoInfo.width =
  1009. String(picWidthInLumaSamples - (leftOffset + rightOffset) * subWc);
  1010. videoInfo.height =
  1011. String(picHeightInLumaSamples - (topOffset + bottomOffset) * subHc);
  1012. const reverseBits = (integer) => {
  1013. let result = 0;
  1014. for (let i = 0; i < 32; i++) {
  1015. result |= ((integer >> i) & 1) << (31 - i);
  1016. }
  1017. return result >>> 0;
  1018. };
  1019. const profileSpace = ['', 'A', 'B', 'C'][generalProfileSpace];
  1020. const profileCompatibility = reverseBits(generalProfileCompatibilityFlags);
  1021. const tierFlag = generalTierFlag == 1 ? 'H' : 'L';
  1022. let codec = 'hvc1';
  1023. codec += '.' + profileSpace + generalProfileIdc;
  1024. codec += '.' + profileCompatibility.toString(16).toUpperCase();
  1025. codec += '.' + tierFlag + generalLevelIdc;
  1026. if (generalConstraintIndicatorFlags6) {
  1027. codec += '.' +
  1028. generalConstraintIndicatorFlags6.toString(16).toUpperCase();
  1029. }
  1030. if (generalConstraintIndicatorFlags5) {
  1031. codec += '.' +
  1032. generalConstraintIndicatorFlags5.toString(16).toUpperCase();
  1033. }
  1034. if (generalConstraintIndicatorFlags4) {
  1035. codec += '.' +
  1036. generalConstraintIndicatorFlags4.toString(16).toUpperCase();
  1037. }
  1038. if (generalConstraintIndicatorFlags3) {
  1039. codec += '.' +
  1040. generalConstraintIndicatorFlags3.toString(16).toUpperCase();
  1041. }
  1042. if (generalConstraintIndicatorFlags2) {
  1043. codec += '.' +
  1044. generalConstraintIndicatorFlags2.toString(16).toUpperCase();
  1045. }
  1046. if (generalConstraintIndicatorFlags1) {
  1047. codec += '.' +
  1048. generalConstraintIndicatorFlags1.toString(16).toUpperCase();
  1049. }
  1050. videoInfo.codec = codec;
  1051. return videoInfo;
  1052. }
  1053. /**
  1054. * Return the Opus metadata
  1055. *
  1056. * @return {?shaka.util.TsParser.OpusMetadata}
  1057. */
  1058. getOpusMetadata() {
  1059. return this.opusMetadata_;
  1060. }
  1061. /**
  1062. * Convert a byte to 2 digits of hex. (Only handles values 0-255.)
  1063. *
  1064. * @param {number} x
  1065. * @return {string}
  1066. * @private
  1067. */
  1068. byteToHex_(x) {
  1069. return ('0' + x.toString(16).toUpperCase()).slice(-2);
  1070. }
  1071. /**
  1072. * @param {number} value
  1073. * @param {number} reference
  1074. * @return {number}
  1075. * @private
  1076. */
  1077. handleRollover_(value, reference) {
  1078. const MAX_TS = 8589934592;
  1079. const RO_THRESH = 4294967296;
  1080. let direction = 1;
  1081. if (value > reference) {
  1082. // If the current timestamp value is greater than our reference timestamp
  1083. // and we detect a timestamp rollover, this means the roll over is
  1084. // happening in the opposite direction.
  1085. // Example scenario: Enter a long stream/video just after a rollover
  1086. // occurred. The reference point will be set to a small number, e.g. 1.
  1087. // The user then seeks backwards over the rollover point. In loading this
  1088. // segment, the timestamp values will be very large, e.g. 2^33 - 1. Since
  1089. // this comes before the data we loaded previously, we want to adjust the
  1090. // time stamp to be `value - 2^33`.
  1091. direction = -1;
  1092. }
  1093. // Note: A seek forwards or back that is greater than the RO_THRESH
  1094. // (2^32, ~13 hours) will cause an incorrect adjustment.
  1095. while (Math.abs(reference - value) > RO_THRESH) {
  1096. value += (direction * MAX_TS);
  1097. }
  1098. return value;
  1099. }
  1100. /**
  1101. * Check if the passed data corresponds to an MPEG2-TS
  1102. *
  1103. * @param {Uint8Array} data
  1104. * @return {boolean}
  1105. * @export
  1106. */
  1107. static probe(data) {
  1108. const syncOffset = shaka.util.TsParser.syncOffset(data);
  1109. if (syncOffset < 0) {
  1110. return false;
  1111. } else {
  1112. if (syncOffset > 0) {
  1113. shaka.log.warning('MPEG2-TS detected but first sync word found @ ' +
  1114. 'offset ' + syncOffset + ', junk ahead ?');
  1115. }
  1116. return true;
  1117. }
  1118. }
  1119. /**
  1120. * Returns the synchronization offset
  1121. *
  1122. * @param {Uint8Array} data
  1123. * @return {number}
  1124. * @export
  1125. */
  1126. static syncOffset(data) {
  1127. const packetLength = shaka.util.TsParser.PacketLength_;
  1128. // scan 1000 first bytes
  1129. const scanwindow = Math.min(1000, data.length - 3 * packetLength);
  1130. let i = 0;
  1131. while (i < scanwindow) {
  1132. // a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
  1133. // one PID, each starting with 0x47
  1134. if (data[i] == 0x47 &&
  1135. data[i + packetLength] == 0x47 &&
  1136. data[i + 2 * packetLength] == 0x47) {
  1137. return i;
  1138. } else {
  1139. i++;
  1140. }
  1141. }
  1142. return -1;
  1143. }
  1144. };
  1145. /**
  1146. * @const {number}
  1147. * @export
  1148. */
  1149. shaka.util.TsParser.Timescale = 90000;
  1150. /**
  1151. * @const {number}
  1152. * @private
  1153. */
  1154. shaka.util.TsParser.PacketLength_ = 188;
  1155. /**
  1156. * NALU type for Sequence Parameter Set (SPS) for H.264.
  1157. * @const {number}
  1158. * @private
  1159. */
  1160. shaka.util.TsParser.H264_NALU_TYPE_SPS_ = 0x07;
  1161. /**
  1162. * NALU type for Sequence Parameter Set (SPS) for H.265.
  1163. * @const {number}
  1164. * @private
  1165. */
  1166. shaka.util.TsParser.H265_NALU_TYPE_SPS_ = 0x21;
  1167. /**
  1168. * Values of profile_idc that indicate additional fields are included in the
  1169. * SPS.
  1170. * see Recommendation ITU-T H.264 (4/2013)
  1171. * 7.3.2.1.1 Sequence parameter set data syntax
  1172. *
  1173. * @const {!Array.<number>}
  1174. * @private
  1175. */
  1176. shaka.util.TsParser.PROFILES_WITH_OPTIONAL_SPS_DATA_ =
  1177. [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134];
  1178. /**
  1179. * @typedef {{
  1180. * audio: number,
  1181. * video: number,
  1182. * id3: number,
  1183. * audioCodec: string,
  1184. * videoCodec: string
  1185. * }}
  1186. *
  1187. * @summary PMT.
  1188. * @property {number} audio
  1189. * Audio PID
  1190. * @property {number} video
  1191. * Video PID
  1192. * @property {number} id3
  1193. * ID3 PID
  1194. * @property {string} audioCodec
  1195. * Audio codec
  1196. * @property {string} videoCodec
  1197. * Video codec
  1198. */
  1199. shaka.util.TsParser.PMT;
  1200. /**
  1201. * @typedef {{
  1202. * channelCount: number,
  1203. * channelConfigCode: number,
  1204. * sampleRate: number
  1205. * }}
  1206. *
  1207. * @summary PMT.
  1208. * @property {number} channelCount
  1209. * @property {number} channelConfigCode
  1210. * @property {number} sampleRate
  1211. */
  1212. shaka.util.TsParser.OpusMetadata;