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