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.Uint8ArrayUtils');
  12. /**
  13. * @see https://en.wikipedia.org/wiki/MPEG_transport_stream
  14. * @export
  15. */
  16. shaka.util.TsParser = class {
  17. /** */
  18. constructor() {
  19. /** @private {?number} */
  20. this.pmtId_ = null;
  21. /** @private {boolean} */
  22. this.pmtParsed_ = false;
  23. /** @private {?number} */
  24. this.videoPid_ = null;
  25. /** @private {?string} */
  26. this.videoCodec_ = null;
  27. /** @private {!Array.<Uint8Array>} */
  28. this.videoData_ = [];
  29. /** @private {?number} */
  30. this.audioPid_ = null;
  31. /** @private {?string} */
  32. this.audioCodec_ = null;
  33. /** @private {!Array.<Uint8Array>} */
  34. this.audioData_ = [];
  35. /** @private {?number} */
  36. this.id3Pid_ = null;
  37. /** @private {!Array.<Uint8Array>} */
  38. this.id3Data_ = [];
  39. }
  40. /**
  41. * Clear previous data
  42. *
  43. * @export
  44. */
  45. clearData() {
  46. this.videoData_ = [];
  47. this.audioData_ = [];
  48. this.id3Data_ = [];
  49. }
  50. /**
  51. * Parse the given data
  52. *
  53. * @param {Uint8Array} data
  54. * @return {!shaka.util.TsParser}
  55. * @export
  56. */
  57. parse(data) {
  58. const packetLength = shaka.util.TsParser.PacketLength_;
  59. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  60. // A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
  61. // one PID.
  62. if (data.length < 3 * packetLength) {
  63. return this;
  64. }
  65. const syncOffset = Math.max(0, shaka.util.TsParser.syncOffset(data));
  66. const length = data.length - (data.length + syncOffset) % packetLength;
  67. let unknownPIDs = false;
  68. // loop through TS packets
  69. for (let start = syncOffset; start < length; start += packetLength) {
  70. if (data[start] == 0x47) {
  71. const payloadUnitStartIndicator = !!(data[start + 1] & 0x40);
  72. // pid is a 13-bit field starting at the last 5 bits of TS[1]
  73. const pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];
  74. const adaptationFieldControl = (data[start + 3] & 0x30) >> 4;
  75. // if an adaption field is present, its length is specified by the
  76. // fifth byte of the TS packet header.
  77. let offset;
  78. if (adaptationFieldControl > 1) {
  79. offset = start + 5 + data[start + 4];
  80. // continue if there is only adaptation field
  81. if (offset == start + packetLength) {
  82. continue;
  83. }
  84. } else {
  85. offset = start + 4;
  86. }
  87. switch (pid) {
  88. case 0:
  89. if (payloadUnitStartIndicator) {
  90. offset += data[offset] + 1;
  91. }
  92. this.pmtId_ = this.getPmtId_(data, offset);
  93. break;
  94. case 17:
  95. case 0x1fff:
  96. break;
  97. case this.pmtId_: {
  98. if (payloadUnitStartIndicator) {
  99. offset += data[offset] + 1;
  100. }
  101. const parsedPIDs = this.parsePMT_(data, offset);
  102. // only update track id if track PID found while parsing PMT
  103. // this is to avoid resetting the PID to -1 in case
  104. // track PID transiently disappears from the stream
  105. // this could happen in case of transient missing audio samples
  106. // for example
  107. // NOTE this is only the PID of the track as found in TS,
  108. // but we are not using this for MP4 track IDs.
  109. if (parsedPIDs.video != -1) {
  110. this.videoPid_ = parsedPIDs.video;
  111. this.videoCodec_ = parsedPIDs.videoCodec;
  112. }
  113. if (parsedPIDs.audio != -1) {
  114. this.audioPid_ = parsedPIDs.audio;
  115. this.audioCodec_ = parsedPIDs.audioCodec;
  116. }
  117. if (parsedPIDs.id3 != -1) {
  118. this.id3Pid_ = parsedPIDs.id3;
  119. }
  120. if (unknownPIDs && !this.pmtParsed_) {
  121. shaka.log.debug('reparse from beginning');
  122. unknownPIDs = false;
  123. // we set it to -188, the += 188 in the for loop will reset
  124. // start to 0
  125. start = syncOffset - packetLength;
  126. }
  127. this.pmtParsed_ = true;
  128. break;
  129. }
  130. case this.videoPid_: {
  131. const videoData = data.subarray(offset, start + packetLength);
  132. if (payloadUnitStartIndicator) {
  133. this.videoData_.push(videoData);
  134. } else if (this.videoData_.length) {
  135. const prevVideoData = this.videoData_[this.videoData_.length - 1];
  136. if (prevVideoData) {
  137. this.videoData_[this.videoData_.length - 1] =
  138. Uint8ArrayUtils.concat(prevVideoData, videoData);
  139. }
  140. }
  141. break;
  142. }
  143. case this.audioPid_: {
  144. const audioData = data.subarray(offset, start + packetLength);
  145. if (payloadUnitStartIndicator) {
  146. this.audioData_.push(audioData);
  147. } else if (this.audioData_.length) {
  148. const prevAudioData = this.audioData_[this.audioData_.length - 1];
  149. if (prevAudioData) {
  150. this.audioData_[this.audioData_.length - 1] =
  151. Uint8ArrayUtils.concat(prevAudioData, audioData);
  152. }
  153. }
  154. break;
  155. }
  156. case this.id3Pid_: {
  157. const id3Data = data.subarray(offset, start + packetLength);
  158. if (payloadUnitStartIndicator) {
  159. this.id3Data_.push(id3Data);
  160. } else if (this.id3Data_.length) {
  161. const prevId3Data = this.id3Data_[this.id3Data_.length - 1];
  162. if (prevId3Data) {
  163. this.id3Data_[this.id3Data_.length - 1] =
  164. Uint8ArrayUtils.concat(prevId3Data, id3Data);
  165. }
  166. }
  167. break;
  168. }
  169. default:
  170. unknownPIDs = true;
  171. break;
  172. }
  173. } else {
  174. shaka.log.warning('Found TS packet that do not start with 0x47');
  175. }
  176. }
  177. return this;
  178. }
  179. /**
  180. * Get the PMT ID from the PAT
  181. *
  182. * @param {Uint8Array} data
  183. * @param {number} offset
  184. * @return {number}
  185. * @private
  186. */
  187. getPmtId_(data, offset) {
  188. // skip the PSI header and parse the first PMT entry
  189. return ((data[offset + 10] & 0x1f) << 8) | data[offset + 11];
  190. }
  191. /**
  192. * Parse PMT
  193. *
  194. * @param {Uint8Array} data
  195. * @param {number} offset
  196. * @return {!shaka.util.TsParser.PMT}
  197. * @private
  198. */
  199. parsePMT_(data, offset) {
  200. const result = {
  201. audio: -1,
  202. video: -1,
  203. id3: -1,
  204. audioCodec: '',
  205. videoCodec: '',
  206. };
  207. const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
  208. const tableEnd = offset + 3 + sectionLength - 4;
  209. // to determine where the table is, we have to figure out how
  210. // long the program info descriptors are
  211. const programInfoLength =
  212. ((data[offset + 10] & 0x0f) << 8) | data[offset + 11];
  213. // advance the offset to the first entry in the mapping table
  214. offset += 12 + programInfoLength;
  215. while (offset < tableEnd) {
  216. const pid = ((data[offset + 1] & 0x1f) << 8) | data[offset + 2];
  217. const esInfoLength = ((data[offset + 3] & 0x0f) << 8) | data[offset + 4];
  218. switch (data[offset]) {
  219. case 0x06:
  220. // stream_type 6 can mean a lot of different things in case of DVB.
  221. // We need to look at the descriptors. Right now, we're only
  222. // interested in AC-3 and EC-3 audio, so we do the descriptor parsing
  223. // only when we don't have an audio PID yet.
  224. if (result.audio == -1 && esInfoLength > 0) {
  225. let parsePos = offset + 5;
  226. let remaining = esInfoLength;
  227. while (remaining > 2) {
  228. const descriptorId = data[parsePos];
  229. switch (descriptorId) {
  230. // DVB Descriptor for AC-3
  231. case 0x6a:
  232. result.audio = pid;
  233. result.audioCodec = 'ac3';
  234. break;
  235. // DVB Descriptor for EC-3
  236. case 0x7a:
  237. result.audio = pid;
  238. result.audioCodec = 'ec3';
  239. break;
  240. }
  241. const descriptorLen = data[parsePos + 1] + 2;
  242. parsePos += descriptorLen;
  243. remaining -= descriptorLen;
  244. }
  245. }
  246. break;
  247. // SAMPLE-AES AAC
  248. case 0xcf:
  249. break;
  250. // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
  251. case 0x0f:
  252. if (result.audio == -1) {
  253. result.audio = pid;
  254. result.audioCodec = 'aac';
  255. }
  256. break;
  257. // Packetized metadata (ID3)
  258. case 0x15:
  259. if (result.id3 == -1) {
  260. result.id3 = pid;
  261. }
  262. break;
  263. // SAMPLE-AES AVC
  264. case 0xdb:
  265. break;
  266. // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
  267. case 0x1b:
  268. if (result.video == -1) {
  269. result.video = pid;
  270. result.videoCodec = 'avc';
  271. }
  272. break;
  273. // ISO/IEC 11172-3 (MPEG-1 audio)
  274. // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
  275. case 0x03:
  276. case 0x04:
  277. if (result.audio == -1) {
  278. result.audio = pid;
  279. result.audioCodec = 'mp3';
  280. }
  281. break;
  282. // HEVC
  283. case 0x24:
  284. if (result.video == -1) {
  285. result.video = pid;
  286. result.videoCodec = 'hvc';
  287. }
  288. break;
  289. // AC-3
  290. case 0x81:
  291. if (result.audio == -1) {
  292. result.audio = pid;
  293. result.audioCodec = 'ac3';
  294. }
  295. break;
  296. // EC-3
  297. case 0x84:
  298. case 0x87:
  299. if (result.audio == -1) {
  300. result.audio = pid;
  301. result.audioCodec = 'ec3';
  302. }
  303. break;
  304. default:
  305. // shaka.log.warning('Unknown stream type:', data[offset]);
  306. break;
  307. }
  308. // move to the next table entry
  309. // skip past the elementary stream descriptors, if present
  310. offset += esInfoLength + 5;
  311. }
  312. return result;
  313. }
  314. /**
  315. * Parse PES
  316. *
  317. * @param {Uint8Array} data
  318. * @return {?shaka.extern.MPEG_PES}
  319. * @private
  320. */
  321. parsePES_(data) {
  322. const startPrefix = (data[0] << 16) | (data[1] << 8) | data[2];
  323. // In certain live streams, the start of a TS fragment has ts packets
  324. // that are frame data that is continuing from the previous fragment. This
  325. // is to check that the pes data is the start of a new pes data
  326. if (startPrefix !== 1) {
  327. return null;
  328. }
  329. /** @type {shaka.extern.MPEG_PES} */
  330. const pes = {
  331. data: new Uint8Array(0),
  332. // get the packet length, this will be 0 for video
  333. packetLength: ((data[4] << 8) | data[5]),
  334. pts: null,
  335. dts: null,
  336. };
  337. // if PES parsed length is not zero and greater than total received length,
  338. // stop parsing. PES might be truncated. minus 6 : PES header size
  339. if (pes.packetLength && pes.packetLength > data.byteLength - 6) {
  340. return null;
  341. }
  342. // PES packets may be annotated with a PTS value, or a PTS value
  343. // and a DTS value. Determine what combination of values is
  344. // available to work with.
  345. const ptsDtsFlags = data[7];
  346. // PTS and DTS are normally stored as a 33-bit number. Javascript
  347. // performs all bitwise operations on 32-bit integers but javascript
  348. // supports a much greater range (52-bits) of integer using standard
  349. // mathematical operations.
  350. // We construct a 31-bit value using bitwise operators over the 31
  351. // most significant bits and then multiply by 4 (equal to a left-shift
  352. // of 2) before we add the final 2 least significant bits of the
  353. // timestamp (equal to an OR.)
  354. if (ptsDtsFlags & 0xC0) {
  355. // the PTS and DTS are not written out directly. For information
  356. // on how they are encoded, see
  357. // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  358. pes.pts =
  359. (data[9] & 0x0e) * 536870912 + // 1 << 29
  360. (data[10] & 0xff) * 4194304 + // 1 << 22
  361. (data[11] & 0xfe) * 16384 + // 1 << 14
  362. (data[12] & 0xff) * 128 + // 1 << 7
  363. (data[13] & 0xfe) / 2;
  364. pes.dts = pes.pts;
  365. if (ptsDtsFlags & 0x40) {
  366. pes.dts =
  367. (data[14] & 0x0e) * 536870912 + // 1 << 29
  368. (data[15] & 0xff) * 4194304 + // 1 << 22
  369. (data[16] & 0xfe) * 16384 + // 1 << 14
  370. (data[17] & 0xff) * 128 + // 1 << 7
  371. (data[18] & 0xfe) / 2;
  372. }
  373. }
  374. const pesHdrLen = data[8];
  375. // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
  376. const payloadStartOffset = pesHdrLen + 9;
  377. if (data.byteLength <= payloadStartOffset) {
  378. return null;
  379. }
  380. pes.data = data.subarray(payloadStartOffset);
  381. return pes;
  382. }
  383. /**
  384. * Parse AVC Nalus
  385. *
  386. * The code is based on hls.js
  387. * Credit to https://github.com/video-dev/hls.js/blob/master/src/demux/tsdemuxer.ts
  388. *
  389. * @param {shaka.extern.MPEG_PES} pes
  390. * @param {?shaka.extern.MPEG_PES=} nextPes
  391. * @return {!Array.<shaka.extern.VideoNalu>}
  392. * @export
  393. */
  394. parseAvcNalus(pes, nextPes) {
  395. const timescale = shaka.util.TsParser.Timescale;
  396. const time = pes.pts ? pes.pts / timescale : null;
  397. let data = pes.data;
  398. let len = data.byteLength;
  399. // A NALU does not contain is its size.
  400. // The Annex B specification solves this by requiring ‘Start Codes’ to
  401. // precede each NALU. A start code is 2 or 3 0x00 bytes followed with a
  402. // 0x01 byte. e.g. 0x000001 or 0x00000001.
  403. // More info in: https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream/24890903#24890903
  404. let numZeros = 0;
  405. /** @type {!Array.<shaka.extern.VideoNalu>} */
  406. const nalus = [];
  407. // Start position includes the first byte where we read the type.
  408. // The data we extract begins at the next byte.
  409. let lastNaluStart = -1;
  410. // Extracted from the first byte.
  411. let lastNaluType = 0;
  412. let tryToFinishLastNalu = false;
  413. /** @type {?shaka.extern.VideoNalu} */
  414. let infoOfLastNalu;
  415. for (let i = 0; i < len; ++i) {
  416. const value = data[i];
  417. if (!value) {
  418. numZeros++;
  419. } else if (numZeros >= 2 && value == 1 && tryToFinishLastNalu) {
  420. // If we are scanning the next PES, we need append the data to the
  421. // previous Nalu and don't scan for more nalus.
  422. const startCodeSize = numZeros > 3 ? 3 : numZeros;
  423. const lastByteToKeep = i - startCodeSize;
  424. // Optimization
  425. if (lastByteToKeep == 0) {
  426. break;
  427. }
  428. infoOfLastNalu.data = shaka.util.Uint8ArrayUtils.concat(
  429. infoOfLastNalu.data, data.subarray(0, lastByteToKeep));
  430. infoOfLastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
  431. infoOfLastNalu.fullData, data.subarray(0, lastByteToKeep));
  432. break;
  433. } else if (numZeros >= 2 && value == 1) {
  434. // We just read a start code. Consume the NALU we passed, if any.
  435. if (lastNaluStart >= 0) {
  436. // Because the start position includes the type, skip the first byte.
  437. const firstByteToKeep = lastNaluStart + 1;
  438. // Compute the last byte to keep. The start code is at most 3 zeros.
  439. // Any earlier zeros are not part of the start code.
  440. const startCodeSize = (numZeros > 3 ? 3 : numZeros) + 1;
  441. const lastByteToKeep = i - startCodeSize;
  442. /** @type {shaka.extern.VideoNalu} */
  443. const nalu = {
  444. // subarray's end position is exclusive, so add one.
  445. data: data.subarray(firstByteToKeep, lastByteToKeep + 1),
  446. fullData: data.subarray(lastNaluStart, lastByteToKeep + 1),
  447. type: lastNaluType,
  448. time: time,
  449. };
  450. nalus.push(nalu);
  451. }
  452. // We just read a start code, so there should be another byte here, at
  453. // least, for the NALU type. Check just in case.
  454. if (i >= len - 1) {
  455. shaka.log.warning('Malformed TS, incomplete NALU, ignoring.');
  456. return nalus;
  457. }
  458. // Advance and read the type of the next NALU.
  459. i++;
  460. lastNaluStart = i;
  461. lastNaluType = data[i] & 0x1f;
  462. numZeros = 0;
  463. } else {
  464. numZeros = 0;
  465. }
  466. // If we have gone through all the data from the PES and we have an
  467. // unfinished Nalu, we will try to use the next PES to complete the
  468. // unfinished Nalu.
  469. if (i >= (len - 1) && lastNaluStart >= 0 && numZeros >= 0) {
  470. if (tryToFinishLastNalu) {
  471. infoOfLastNalu.data = shaka.util.Uint8ArrayUtils.concat(
  472. infoOfLastNalu.data, data);
  473. infoOfLastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
  474. infoOfLastNalu.fullData, data);
  475. } else {
  476. tryToFinishLastNalu = true;
  477. // The rest of the buffer was a NALU.
  478. // Because the start position includes the type, skip the first byte.
  479. const firstByteToKeep = lastNaluStart + 1;
  480. infoOfLastNalu = {
  481. data: data.subarray(firstByteToKeep, len),
  482. fullData: data.subarray(lastNaluStart, len),
  483. type: lastNaluType,
  484. time: time,
  485. };
  486. if (nextPes && pes.packetLength == 0) {
  487. data = nextPes.data;
  488. len = data.byteLength;
  489. i = -1;
  490. }
  491. }
  492. }
  493. }
  494. if (infoOfLastNalu) {
  495. nalus.push(infoOfLastNalu);
  496. }
  497. return nalus;
  498. }
  499. /**
  500. * Return the ID3 metadata
  501. *
  502. * @return {!Array.<shaka.extern.ID3Metadata>}
  503. * @export
  504. */
  505. getMetadata() {
  506. const timescale = shaka.util.TsParser.Timescale;
  507. const metadata = [];
  508. for (const id3Data of this.id3Data_) {
  509. const pes = this.parsePES_(id3Data);
  510. if (pes) {
  511. metadata.push({
  512. cueTime: pes.pts ? pes.pts / timescale : null,
  513. data: pes.data,
  514. frames: shaka.util.Id3Utils.getID3Frames(pes.data),
  515. dts: pes.dts,
  516. pts: pes.pts,
  517. });
  518. }
  519. }
  520. return metadata;
  521. }
  522. /**
  523. * Return the audio data
  524. *
  525. * @return {!Array.<shaka.extern.MPEG_PES>}
  526. * @export
  527. */
  528. getAudioData() {
  529. const audio = [];
  530. for (const audioData of this.audioData_) {
  531. const pes = this.parsePES_(audioData);
  532. if (pes) {
  533. audio.push(pes);
  534. }
  535. }
  536. return audio;
  537. }
  538. /**
  539. * Return the audio data
  540. *
  541. * @return {!Array.<shaka.extern.MPEG_PES>}
  542. * @export
  543. */
  544. getVideoData() {
  545. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  546. const video = [];
  547. for (const videoData of this.videoData_) {
  548. const pes = this.parsePES_(videoData);
  549. if (pes && pes.pts != null && pes.dts != null) {
  550. video.push(pes);
  551. } else if (video.length) {
  552. const data = pes ? pes.data : videoData;
  553. if (!data) {
  554. continue;
  555. }
  556. const previousPes = video.pop();
  557. previousPes.data =
  558. Uint8ArrayUtils.concat(previousPes.data, data);
  559. video.push(previousPes);
  560. }
  561. }
  562. return video;
  563. }
  564. /**
  565. * Return the start time for the audio and video
  566. *
  567. * @return {{audio: ?number, video: ?number}}
  568. * @export
  569. */
  570. getStartTime() {
  571. const timescale = shaka.util.TsParser.Timescale;
  572. let audioStartTime = null;
  573. for (const pes of this.getAudioData()) {
  574. if (pes && pes.pts != null) {
  575. const startTime = Math.min(pes.dts, pes.pts) / timescale;
  576. if (audioStartTime == null || audioStartTime > startTime) {
  577. audioStartTime = startTime;
  578. }
  579. }
  580. }
  581. let videoStartTime = null;
  582. for (const pes of this.getVideoData()) {
  583. if (pes && pes.pts != null) {
  584. const startTime = Math.min(pes.dts, pes.pts) / timescale;
  585. if (videoStartTime == null || videoStartTime > startTime) {
  586. videoStartTime = startTime;
  587. }
  588. }
  589. }
  590. return {
  591. audio: audioStartTime,
  592. video: videoStartTime,
  593. };
  594. }
  595. /**
  596. * Return the audio and video codecs
  597. *
  598. * @return {{audio: ?string, video: ?string}}
  599. * @export
  600. */
  601. getCodecs() {
  602. return {
  603. audio: this.audioCodec_,
  604. video: this.videoCodec_,
  605. };
  606. }
  607. /**
  608. * Return the video data
  609. *
  610. * @return {!Array.<shaka.extern.VideoNalu>}
  611. * @export
  612. */
  613. getVideoNalus() {
  614. const nalus = [];
  615. if (this.videoCodec_ != 'avc') {
  616. return nalus;
  617. }
  618. const videoData = this.getVideoData();
  619. for (let i = 0; i < videoData.length; i++) {
  620. const pes = videoData[i];
  621. let nextPes;
  622. if (i + 1 < videoData.length) {
  623. nextPes = videoData[i + 1];
  624. }
  625. nalus.push(...this.parseAvcNalus(pes, nextPes));
  626. }
  627. return nalus;
  628. }
  629. /**
  630. * Return the video resolution
  631. *
  632. * @return {{height: ?string, width: ?string}}
  633. * @export
  634. */
  635. getVideoResolution() {
  636. shaka.Deprecate.deprecateFeature(5,
  637. 'TsParser',
  638. 'Please use getVideoInfo function instead.');
  639. const videoInfo = this.getVideoInfo();
  640. return {
  641. height: videoInfo.height,
  642. width: videoInfo.width,
  643. };
  644. }
  645. /**
  646. * Return the video information
  647. *
  648. * @return {{height: ?string, width: ?string, codec: ?string}}
  649. * @export
  650. */
  651. getVideoInfo() {
  652. const TsParser = shaka.util.TsParser;
  653. const videoInfo = {
  654. height: null,
  655. width: null,
  656. codec: null,
  657. };
  658. const videoNalus = this.getVideoNalus();
  659. if (!videoNalus.length) {
  660. return videoInfo;
  661. }
  662. const spsNalu = videoNalus.find((nalu) => {
  663. return nalu.type == TsParser.H264_NALU_TYPE_SPS_;
  664. });
  665. if (!spsNalu) {
  666. return videoInfo;
  667. }
  668. const expGolombDecoder = new shaka.util.ExpGolomb(spsNalu.data);
  669. // profile_idc
  670. const profileIdc = expGolombDecoder.readUnsignedByte();
  671. // constraint_set[0-5]_flag
  672. const profileCompatibility = expGolombDecoder.readUnsignedByte();
  673. // level_idc u(8)
  674. const levelIdc = expGolombDecoder.readUnsignedByte();
  675. // seq_parameter_set_id
  676. expGolombDecoder.skipExpGolomb();
  677. // some profiles have more optional data we don't need
  678. if (TsParser.PROFILES_WITH_OPTIONAL_SPS_DATA_.includes(profileIdc)) {
  679. const chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
  680. if (chromaFormatIdc === 3) {
  681. // separate_colour_plane_flag
  682. expGolombDecoder.skipBits(1);
  683. }
  684. // bit_depth_luma_minus8
  685. expGolombDecoder.skipExpGolomb();
  686. // bit_depth_chroma_minus8
  687. expGolombDecoder.skipExpGolomb();
  688. // qpprime_y_zero_transform_bypass_flag
  689. expGolombDecoder.skipBits(1);
  690. // seq_scaling_matrix_present_flag
  691. if (expGolombDecoder.readBoolean()) {
  692. const scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
  693. for (let i = 0; i < scalingListCount; i++) {
  694. // seq_scaling_list_present_flag[ i ]
  695. if (expGolombDecoder.readBoolean()) {
  696. if (i < 6) {
  697. expGolombDecoder.skipScalingList(16);
  698. } else {
  699. expGolombDecoder.skipScalingList(64);
  700. }
  701. }
  702. }
  703. }
  704. }
  705. // log2_max_frame_num_minus4
  706. expGolombDecoder.skipExpGolomb();
  707. const picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
  708. if (picOrderCntType === 0) {
  709. // log2_max_pic_order_cnt_lsb_minus4
  710. expGolombDecoder.readUnsignedExpGolomb();
  711. } else if (picOrderCntType === 1) {
  712. // delta_pic_order_always_zero_flag
  713. expGolombDecoder.skipBits(1);
  714. // offset_for_non_ref_pic
  715. expGolombDecoder.skipExpGolomb();
  716. // offset_for_top_to_bottom_field
  717. expGolombDecoder.skipExpGolomb();
  718. const numRefFramesInPicOrderCntCycle =
  719. expGolombDecoder.readUnsignedExpGolomb();
  720. for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
  721. // offset_for_ref_frame[ i ]
  722. expGolombDecoder.skipExpGolomb();
  723. }
  724. }
  725. // max_num_ref_frames
  726. expGolombDecoder.skipExpGolomb();
  727. // gaps_in_frame_num_value_allowed_flag
  728. expGolombDecoder.skipBits(1);
  729. const picWidthInMbsMinus1 =
  730. expGolombDecoder.readUnsignedExpGolomb();
  731. const picHeightInMapUnitsMinus1 =
  732. expGolombDecoder.readUnsignedExpGolomb();
  733. const frameMbsOnlyFlag = expGolombDecoder.readBits(1);
  734. if (frameMbsOnlyFlag === 0) {
  735. // mb_adaptive_frame_field_flag
  736. expGolombDecoder.skipBits(1);
  737. }
  738. // direct_8x8_inference_flag
  739. expGolombDecoder.skipBits(1);
  740. let frameCropLeftOffset = 0;
  741. let frameCropRightOffset = 0;
  742. let frameCropTopOffset = 0;
  743. let frameCropBottomOffset = 0;
  744. // frame_cropping_flag
  745. if (expGolombDecoder.readBoolean()) {
  746. frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
  747. frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
  748. frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
  749. frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
  750. }
  751. videoInfo.height = String(((2 - frameMbsOnlyFlag) *
  752. (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) -
  753. (frameCropBottomOffset * 2));
  754. videoInfo.width = String(((picWidthInMbsMinus1 + 1) * 16) -
  755. frameCropLeftOffset * 2 - frameCropRightOffset * 2);
  756. videoInfo.codec = 'avc1.' + this.byteToHex_(profileIdc) +
  757. this.byteToHex_(profileCompatibility) + this.byteToHex_(levelIdc);
  758. return videoInfo;
  759. }
  760. /**
  761. * Convert a byte to 2 digits of hex. (Only handles values 0-255.)
  762. *
  763. * @param {number} x
  764. * @return {string}
  765. * @private
  766. */
  767. byteToHex_(x) {
  768. return ('0' + x.toString(16).toUpperCase()).slice(-2);
  769. }
  770. /**
  771. * Check if the passed data corresponds to an MPEG2-TS
  772. *
  773. * @param {Uint8Array} data
  774. * @return {boolean}
  775. * @export
  776. */
  777. static probe(data) {
  778. const syncOffset = shaka.util.TsParser.syncOffset(data);
  779. if (syncOffset < 0) {
  780. return false;
  781. } else {
  782. if (syncOffset > 0) {
  783. shaka.log.warning('MPEG2-TS detected but first sync word found @ ' +
  784. 'offset ' + syncOffset + ', junk ahead ?');
  785. }
  786. return true;
  787. }
  788. }
  789. /**
  790. * Returns the synchronization offset
  791. *
  792. * @param {Uint8Array} data
  793. * @return {number}
  794. * @export
  795. */
  796. static syncOffset(data) {
  797. const packetLength = shaka.util.TsParser.PacketLength_;
  798. // scan 1000 first bytes
  799. const scanwindow = Math.min(1000, data.length - 3 * packetLength);
  800. let i = 0;
  801. while (i < scanwindow) {
  802. // a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
  803. // one PID, each starting with 0x47
  804. if (data[i] == 0x47 &&
  805. data[i + packetLength] == 0x47 &&
  806. data[i + 2 * packetLength] == 0x47) {
  807. return i;
  808. } else {
  809. i++;
  810. }
  811. }
  812. return -1;
  813. }
  814. };
  815. /**
  816. * @const {number}
  817. * @export
  818. */
  819. shaka.util.TsParser.Timescale = 90000;
  820. /**
  821. * @const {number}
  822. * @private
  823. */
  824. shaka.util.TsParser.PacketLength_ = 188;
  825. /**
  826. * NALU type for Sequence Parameter Set (SPS) for H.264.
  827. * @const {number}
  828. * @private
  829. */
  830. shaka.util.TsParser.H264_NALU_TYPE_SPS_ = 0x07;
  831. /**
  832. * Values of profile_idc that indicate additional fields are included in the
  833. * SPS.
  834. * see Recommendation ITU-T H.264 (4/2013)
  835. * 7.3.2.1.1 Sequence parameter set data syntax
  836. *
  837. * @const {!Array.<number>}
  838. * @private
  839. */
  840. shaka.util.TsParser.PROFILES_WITH_OPTIONAL_SPS_DATA_ =
  841. [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134];
  842. /**
  843. * @typedef {{
  844. * audio: number,
  845. * video: number,
  846. * id3: number,
  847. * audioCodec: string,
  848. * videoCodec: string
  849. * }}
  850. *
  851. * @summary PMT.
  852. * @property {number} audio
  853. * Audio PID
  854. * @property {number} video
  855. * Video PID
  856. * @property {number} id3
  857. * ID3 PID
  858. * @property {string} audioCodec
  859. * Audio codec
  860. * @property {string} videoCodec
  861. * Video codec
  862. */
  863. shaka.util.TsParser.PMT;