Home Reference Source

src/demux/tsdemuxer.ts

  1. /**
  2. * highly optimized TS demuxer:
  3. * parse PAT, PMT
  4. * extract PES packet from audio and video PIDs
  5. * extract AVC/H264 NAL units and AAC/ADTS samples from PES packet
  6. * trigger the remuxer upon parsing completion
  7. * it also tries to workaround as best as it can audio codec switch (HE-AAC to AAC and vice versa), without having to restart the MediaSource.
  8. * it also controls the remuxing process :
  9. * upon discontinuity or level switch detection, it will also notifies the remuxer so that it can reset its state.
  10. */
  11.  
  12. import * as ADTS from './adts';
  13. import * as MpegAudio from './mpegaudio';
  14. import ExpGolomb from './exp-golomb';
  15. import { utf8ArrayToStr } from './id3';
  16. import SampleAesDecrypter from './sample-aes';
  17. import { Events } from '../events';
  18. import { appendUint8Array } from '../utils/mp4-tools';
  19. import { logger } from '../utils/logger';
  20. import { ErrorTypes, ErrorDetails } from '../errors';
  21. import type { HlsConfig } from '../config';
  22. import type { HlsEventEmitter } from '../events';
  23. import type {
  24. DemuxedAvcTrack,
  25. DemuxedAudioTrack,
  26. DemuxedTrack,
  27. Demuxer,
  28. DemuxerResult,
  29. AvcSample,
  30. DemuxedMetadataTrack,
  31. DemuxedUserdataTrack,
  32. ElementaryStreamData,
  33. KeyData,
  34. } from '../types/demuxer';
  35.  
  36. // We are using fixed track IDs for driving the MP4 remuxer
  37. // instead of following the TS PIDs.
  38. // There is no reason not to do this and some browsers/SourceBuffer-demuxers
  39. // may not like if there are TrackID "switches"
  40. // See https://github.com/video-dev/hls.js/issues/1331
  41. // Here we are mapping our internal track types to constant MP4 track IDs
  42. // With MSE currently one can only have one track of each, and we are muxing
  43. // whatever video/audio rendition in them.
  44. const RemuxerTrackIdConfig = {
  45. video: 1,
  46. audio: 2,
  47. id3: 3,
  48. text: 4,
  49. };
  50.  
  51. type ParsedTimestamp = {
  52. pts?: number;
  53. dts?: number;
  54. };
  55.  
  56. type PES = ParsedTimestamp & {
  57. data: Uint8Array;
  58. len: number;
  59. };
  60.  
  61. type ParsedAvcSample = ParsedTimestamp & Omit<AvcSample, 'pts' | 'dts'>;
  62.  
  63. export interface TypeSupported {
  64. mpeg: boolean;
  65. mp3: boolean;
  66. mp4: boolean;
  67. }
  68.  
  69. class TSDemuxer implements Demuxer {
  70. static readonly minProbeByteLength = 188;
  71.  
  72. private readonly observer: HlsEventEmitter;
  73. private readonly config: HlsConfig;
  74. private typeSupported: TypeSupported;
  75.  
  76. private sampleAes: SampleAesDecrypter | null = null;
  77. private pmtParsed: boolean = false;
  78. private audioCodec!: string;
  79. private videoCodec!: string;
  80. private _duration: number = 0;
  81. private aacLastPTS: number | null = null;
  82. private _initPTS: number | null = null;
  83. private _initDTS?: number | null = null;
  84. private _pmtId: number = -1;
  85.  
  86. private _avcTrack!: DemuxedAvcTrack;
  87. private _audioTrack!: DemuxedAudioTrack;
  88. private _id3Track!: DemuxedMetadataTrack;
  89. private _txtTrack!: DemuxedUserdataTrack;
  90. private aacOverFlow: Uint8Array | null = null;
  91. private avcSample: ParsedAvcSample | null = null;
  92. private remainderData: Uint8Array | null = null;
  93.  
  94. constructor(
  95. observer: HlsEventEmitter,
  96. config: HlsConfig,
  97. typeSupported: TypeSupported
  98. ) {
  99. this.observer = observer;
  100. this.config = config;
  101. this.typeSupported = typeSupported;
  102. }
  103.  
  104. static probe(data: Uint8Array) {
  105. const syncOffset = TSDemuxer.syncOffset(data);
  106. if (syncOffset < 0) {
  107. return false;
  108. } else {
  109. if (syncOffset) {
  110. logger.warn(
  111. `MPEG2-TS detected but first sync word found @ offset ${syncOffset}, junk ahead ?`
  112. );
  113. }
  114.  
  115. return true;
  116. }
  117. }
  118.  
  119. static syncOffset(data: Uint8Array) {
  120. // scan 1000 first bytes
  121. const scanwindow = Math.min(1000, data.length - 3 * 188);
  122. let i = 0;
  123. while (i < scanwindow) {
  124. // a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and one PID, each starting with 0x47
  125. if (
  126. data[i] === 0x47 &&
  127. data[i + 188] === 0x47 &&
  128. data[i + 2 * 188] === 0x47
  129. ) {
  130. return i;
  131. } else {
  132. i++;
  133. }
  134. }
  135. return -1;
  136. }
  137.  
  138. /**
  139. * Creates a track model internal to demuxer used to drive remuxing input
  140. *
  141. * @param type 'audio' | 'video' | 'id3' | 'text'
  142. * @param duration
  143. * @return TSDemuxer's internal track model
  144. */
  145. static createTrack(
  146. type: 'audio' | 'video' | 'id3' | 'text',
  147. duration: number
  148. ): DemuxedTrack {
  149. return {
  150. container:
  151. type === 'video' || type === 'audio' ? 'video/mp2t' : undefined,
  152. type,
  153. id: RemuxerTrackIdConfig[type],
  154. pid: -1,
  155. inputTimeScale: 90000,
  156. sequenceNumber: 0,
  157. samples: [],
  158. dropped: 0,
  159. duration: type === 'audio' ? duration : undefined,
  160. };
  161. }
  162.  
  163. /**
  164. * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start)
  165. * Resets all internal track instances of the demuxer.
  166. */
  167. public resetInitSegment(
  168. audioCodec: string,
  169. videoCodec: string,
  170. duration: number
  171. ) {
  172. this.pmtParsed = false;
  173. this._pmtId = -1;
  174.  
  175. this._avcTrack = TSDemuxer.createTrack(
  176. 'video',
  177. duration
  178. ) as DemuxedAvcTrack;
  179. this._audioTrack = TSDemuxer.createTrack(
  180. 'audio',
  181. duration
  182. ) as DemuxedAudioTrack;
  183. this._id3Track = TSDemuxer.createTrack(
  184. 'id3',
  185. duration
  186. ) as DemuxedMetadataTrack;
  187. this._txtTrack = TSDemuxer.createTrack(
  188. 'text',
  189. duration
  190. ) as DemuxedUserdataTrack;
  191. this._audioTrack.isAAC = true;
  192.  
  193. // flush any partial content
  194. this.aacOverFlow = null;
  195. this.aacLastPTS = null;
  196. this.avcSample = null;
  197. this.audioCodec = audioCodec;
  198. this.videoCodec = videoCodec;
  199. this._duration = duration;
  200. }
  201.  
  202. public resetTimeStamp() {}
  203.  
  204. public resetContiguity(): void {
  205. const { _audioTrack, _avcTrack, _id3Track } = this;
  206. if (_audioTrack) {
  207. _audioTrack.pesData = null;
  208. }
  209. if (_avcTrack) {
  210. _avcTrack.pesData = null;
  211. }
  212. if (_id3Track) {
  213. _id3Track.pesData = null;
  214. }
  215. this.aacOverFlow = null;
  216. this.aacLastPTS = null;
  217. }
  218.  
  219. public demux(
  220. data: Uint8Array,
  221. timeOffset: number,
  222. isSampleAes = false,
  223. flush = false
  224. ): DemuxerResult {
  225. if (!isSampleAes) {
  226. this.sampleAes = null;
  227. }
  228.  
  229. let pes: PES | null;
  230.  
  231. const avcTrack = this._avcTrack;
  232. const audioTrack = this._audioTrack;
  233. const id3Track = this._id3Track;
  234.  
  235. let avcId = avcTrack.pid;
  236. let avcData = avcTrack.pesData;
  237. let audioId = audioTrack.pid;
  238. let id3Id = id3Track.pid;
  239. let audioData = audioTrack.pesData;
  240. let id3Data = id3Track.pesData;
  241. let unknownPIDs = false;
  242. let pmtParsed = this.pmtParsed;
  243. let pmtId = this._pmtId;
  244.  
  245. let len = data.length;
  246. if (this.remainderData) {
  247. data = appendUint8Array(this.remainderData, data);
  248. len = data.length;
  249. this.remainderData = null;
  250. }
  251.  
  252. if (len < 188 && !flush) {
  253. this.remainderData = data;
  254. return {
  255. audioTrack,
  256. avcTrack,
  257. id3Track,
  258. textTrack: this._txtTrack,
  259. };
  260. }
  261.  
  262. const syncOffset = Math.max(0, TSDemuxer.syncOffset(data));
  263.  
  264. len -= (len + syncOffset) % 188;
  265. if (len < data.byteLength && !flush) {
  266. this.remainderData = new Uint8Array(
  267. data.buffer,
  268. len,
  269. data.buffer.byteLength - len
  270. );
  271. }
  272.  
  273. // loop through TS packets
  274. for (let start = syncOffset; start < len; start += 188) {
  275. if (data[start] === 0x47) {
  276. const stt = !!(data[start + 1] & 0x40);
  277. // pid is a 13-bit field starting at the last bit of TS[1]
  278. const pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];
  279. const atf = (data[start + 3] & 0x30) >> 4;
  280.  
  281. // if an adaption field is present, its length is specified by the fifth byte of the TS packet header.
  282. let offset: number;
  283. if (atf > 1) {
  284. offset = start + 5 + data[start + 4];
  285. // continue if there is only adaptation field
  286. if (offset === start + 188) {
  287. continue;
  288. }
  289. } else {
  290. offset = start + 4;
  291. }
  292. switch (pid) {
  293. case avcId:
  294. if (stt) {
  295. if (avcData && (pes = parsePES(avcData))) {
  296. this.parseAVCPES(pes, false);
  297. }
  298.  
  299. avcData = { data: [], size: 0 };
  300. }
  301. if (avcData) {
  302. avcData.data.push(data.subarray(offset, start + 188));
  303. avcData.size += start + 188 - offset;
  304. }
  305. break;
  306. case audioId:
  307. if (stt) {
  308. if (audioData && (pes = parsePES(audioData))) {
  309. if (audioTrack.isAAC) {
  310. this.parseAACPES(pes);
  311. } else {
  312. this.parseMPEGPES(pes);
  313. }
  314. }
  315. audioData = { data: [], size: 0 };
  316. }
  317. if (audioData) {
  318. audioData.data.push(data.subarray(offset, start + 188));
  319. audioData.size += start + 188 - offset;
  320. }
  321. break;
  322. case id3Id:
  323. if (stt) {
  324. if (id3Data && (pes = parsePES(id3Data))) {
  325. this.parseID3PES(pes);
  326. }
  327.  
  328. id3Data = { data: [], size: 0 };
  329. }
  330. if (id3Data) {
  331. id3Data.data.push(data.subarray(offset, start + 188));
  332. id3Data.size += start + 188 - offset;
  333. }
  334. break;
  335. case 0:
  336. if (stt) {
  337. offset += data[offset] + 1;
  338. }
  339.  
  340. pmtId = this._pmtId = parsePAT(data, offset);
  341. break;
  342. case pmtId: {
  343. if (stt) {
  344. offset += data[offset] + 1;
  345. }
  346.  
  347. const parsedPIDs = parsePMT(
  348. data,
  349. offset,
  350. this.typeSupported.mpeg === true ||
  351. this.typeSupported.mp3 === true,
  352. isSampleAes
  353. );
  354.  
  355. // only update track id if track PID found while parsing PMT
  356. // this is to avoid resetting the PID to -1 in case
  357. // track PID transiently disappears from the stream
  358. // this could happen in case of transient missing audio samples for example
  359. // NOTE this is only the PID of the track as found in TS,
  360. // but we are not using this for MP4 track IDs.
  361. avcId = parsedPIDs.avc;
  362. if (avcId > 0) {
  363. avcTrack.pid = avcId;
  364. }
  365.  
  366. audioId = parsedPIDs.audio;
  367. if (audioId > 0) {
  368. audioTrack.pid = audioId;
  369. audioTrack.isAAC = parsedPIDs.isAAC;
  370. }
  371. id3Id = parsedPIDs.id3;
  372. if (id3Id > 0) {
  373. id3Track.pid = id3Id;
  374. }
  375.  
  376. if (unknownPIDs && !pmtParsed) {
  377. logger.log('reparse from beginning');
  378. unknownPIDs = false;
  379. // we set it to -188, the += 188 in the for loop will reset start to 0
  380. start = syncOffset - 188;
  381. }
  382. pmtParsed = this.pmtParsed = true;
  383. break;
  384. }
  385. case 17:
  386. case 0x1fff:
  387. break;
  388. default:
  389. unknownPIDs = true;
  390. break;
  391. }
  392. } else {
  393. this.observer.emit(Events.ERROR, Events.ERROR, {
  394. type: ErrorTypes.MEDIA_ERROR,
  395. details: ErrorDetails.FRAG_PARSING_ERROR,
  396. fatal: false,
  397. reason: 'TS packet did not start with 0x47',
  398. });
  399. }
  400. }
  401.  
  402. avcTrack.pesData = avcData;
  403. audioTrack.pesData = audioData;
  404. id3Track.pesData = id3Data;
  405.  
  406. const demuxResult: DemuxerResult = {
  407. audioTrack,
  408. avcTrack,
  409. id3Track,
  410. textTrack: this._txtTrack,
  411. };
  412. this.extractRemainingSamples(demuxResult);
  413. return demuxResult;
  414. }
  415.  
  416. public flush(): DemuxerResult | Promise<DemuxerResult> {
  417. const { remainderData } = this;
  418. this.remainderData = null;
  419. let result: DemuxerResult;
  420. if (remainderData) {
  421. result = this.demux(remainderData, -1, false, true);
  422. } else {
  423. result = {
  424. audioTrack: this._audioTrack,
  425. avcTrack: this._avcTrack,
  426. textTrack: this._txtTrack,
  427. id3Track: this._id3Track,
  428. };
  429. }
  430. this.extractRemainingSamples(result);
  431. if (this.sampleAes) {
  432. return this.decrypt(result, this.sampleAes);
  433. }
  434. return result;
  435. }
  436.  
  437. private extractRemainingSamples(demuxResult: DemuxerResult) {
  438. const { audioTrack, avcTrack, id3Track } = demuxResult;
  439. const avcData = avcTrack.pesData;
  440. const audioData = audioTrack.pesData;
  441. const id3Data = id3Track.pesData;
  442. // try to parse last PES packets
  443. let pes: PES | null;
  444. if (avcData && (pes = parsePES(avcData))) {
  445. this.parseAVCPES(pes, true);
  446. avcTrack.pesData = null;
  447. } else {
  448. // either avcData null or PES truncated, keep it for next frag parsing
  449. avcTrack.pesData = avcData;
  450. }
  451.  
  452. if (audioData && (pes = parsePES(audioData))) {
  453. if (audioTrack.isAAC) {
  454. this.parseAACPES(pes);
  455. } else {
  456. this.parseMPEGPES(pes);
  457. }
  458.  
  459. audioTrack.pesData = null;
  460. } else {
  461. if (audioData?.size) {
  462. logger.log(
  463. 'last AAC PES packet truncated,might overlap between fragments'
  464. );
  465. }
  466.  
  467. // either audioData null or PES truncated, keep it for next frag parsing
  468. audioTrack.pesData = audioData;
  469. }
  470.  
  471. if (id3Data && (pes = parsePES(id3Data))) {
  472. this.parseID3PES(pes);
  473. id3Track.pesData = null;
  474. } else {
  475. // either id3Data null or PES truncated, keep it for next frag parsing
  476. id3Track.pesData = id3Data;
  477. }
  478. }
  479.  
  480. public demuxSampleAes(
  481. data: Uint8Array,
  482. keyData: KeyData,
  483. timeOffset: number
  484. ): Promise<DemuxerResult> {
  485. const demuxResult = this.demux(data, timeOffset, true);
  486. const sampleAes = (this.sampleAes = new SampleAesDecrypter(
  487. this.observer,
  488. this.config,
  489. keyData
  490. ));
  491. return this.decrypt(demuxResult, sampleAes);
  492. }
  493.  
  494. private decrypt(
  495. demuxResult: DemuxerResult,
  496. sampleAes: SampleAesDecrypter
  497. ): Promise<DemuxerResult> {
  498. return new Promise((resolve) => {
  499. const { audioTrack, avcTrack } = demuxResult;
  500. if (audioTrack.samples && audioTrack.isAAC) {
  501. sampleAes.decryptAacSamples(audioTrack.samples, 0, () => {
  502. if (avcTrack.samples) {
  503. sampleAes.decryptAvcSamples(avcTrack.samples, 0, 0, () => {
  504. resolve(demuxResult);
  505. });
  506. } else {
  507. resolve(demuxResult);
  508. }
  509. });
  510. } else if (avcTrack.samples) {
  511. sampleAes.decryptAvcSamples(avcTrack.samples, 0, 0, () => {
  512. resolve(demuxResult);
  513. });
  514. }
  515. });
  516. }
  517.  
  518. public destroy() {
  519. this._initPTS = this._initDTS = null;
  520. this._duration = 0;
  521. }
  522.  
  523. private parseAVCPES(pes: PES, last: boolean) {
  524. const track = this._avcTrack;
  525. const units = this.parseAVCNALu(pes.data);
  526. const debug = false;
  527. let avcSample = this.avcSample;
  528. let push: boolean;
  529. let spsfound = false;
  530. // free pes.data to save up some memory
  531. (pes as any).data = null;
  532.  
  533. // if new NAL units found and last sample still there, let's push ...
  534. // this helps parsing streams with missing AUD (only do this if AUD never found)
  535. if (avcSample && units.length && !track.audFound) {
  536. pushAccessUnit(avcSample, track);
  537. avcSample = this.avcSample = createAVCSample(false, pes.pts, pes.dts, '');
  538. }
  539.  
  540. units.forEach((unit) => {
  541. switch (unit.type) {
  542. // NDR
  543. case 1: {
  544. push = true;
  545. if (!avcSample) {
  546. avcSample = this.avcSample = createAVCSample(
  547. true,
  548. pes.pts,
  549. pes.dts,
  550. ''
  551. );
  552. }
  553.  
  554. if (debug) {
  555. avcSample.debug += 'NDR ';
  556. }
  557.  
  558. avcSample.frame = true;
  559. const data = unit.data;
  560. // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
  561. if (spsfound && data.length > 4) {
  562. // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
  563. const sliceType = new ExpGolomb(data).readSliceType();
  564. // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
  565. // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
  566. // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
  567. // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
  568. // if (sliceType === 2 || sliceType === 7) {
  569. if (
  570. sliceType === 2 ||
  571. sliceType === 4 ||
  572. sliceType === 7 ||
  573. sliceType === 9
  574. ) {
  575. avcSample.key = true;
  576. }
  577. }
  578. break;
  579. // IDR
  580. }
  581. case 5:
  582. push = true;
  583. // handle PES not starting with AUD
  584. if (!avcSample) {
  585. avcSample = this.avcSample = createAVCSample(
  586. true,
  587. pes.pts,
  588. pes.dts,
  589. ''
  590. );
  591. }
  592.  
  593. if (debug) {
  594. avcSample.debug += 'IDR ';
  595. }
  596.  
  597. avcSample.key = true;
  598. avcSample.frame = true;
  599. break;
  600. // SEI
  601. case 6: {
  602. push = true;
  603. if (debug && avcSample) {
  604. avcSample.debug += 'SEI ';
  605. }
  606.  
  607. const expGolombDecoder = new ExpGolomb(discardEPB(unit.data));
  608.  
  609. // skip frameType
  610. expGolombDecoder.readUByte();
  611.  
  612. let payloadType = 0;
  613. let payloadSize = 0;
  614. let endOfCaptions = false;
  615. let b = 0;
  616.  
  617. while (!endOfCaptions && expGolombDecoder.bytesAvailable > 1) {
  618. payloadType = 0;
  619. do {
  620. b = expGolombDecoder.readUByte();
  621. payloadType += b;
  622. } while (b === 0xff);
  623.  
  624. // Parse payload size.
  625. payloadSize = 0;
  626. do {
  627. b = expGolombDecoder.readUByte();
  628. payloadSize += b;
  629. } while (b === 0xff);
  630.  
  631. // TODO: there can be more than one payload in an SEI packet...
  632. // TODO: need to read type and size in a while loop to get them all
  633. if (payloadType === 4 && expGolombDecoder.bytesAvailable !== 0) {
  634. endOfCaptions = true;
  635.  
  636. const countryCode = expGolombDecoder.readUByte();
  637.  
  638. if (countryCode === 181) {
  639. const providerCode = expGolombDecoder.readUShort();
  640.  
  641. if (providerCode === 49) {
  642. const userStructure = expGolombDecoder.readUInt();
  643.  
  644. if (userStructure === 0x47413934) {
  645. const userDataType = expGolombDecoder.readUByte();
  646.  
  647. // Raw CEA-608 bytes wrapped in CEA-708 packet
  648. if (userDataType === 3) {
  649. const firstByte = expGolombDecoder.readUByte();
  650. const secondByte = expGolombDecoder.readUByte();
  651.  
  652. const totalCCs = 31 & firstByte;
  653. const byteArray = [firstByte, secondByte];
  654.  
  655. for (let i = 0; i < totalCCs; i++) {
  656. // 3 bytes per CC
  657. byteArray.push(expGolombDecoder.readUByte());
  658. byteArray.push(expGolombDecoder.readUByte());
  659. byteArray.push(expGolombDecoder.readUByte());
  660. }
  661.  
  662. insertSampleInOrder(this._txtTrack.samples, {
  663. type: 3,
  664. pts: pes.pts,
  665. bytes: byteArray,
  666. });
  667. }
  668. }
  669. }
  670. }
  671. } else if (
  672. payloadType === 5 &&
  673. expGolombDecoder.bytesAvailable !== 0
  674. ) {
  675. endOfCaptions = true;
  676.  
  677. if (payloadSize > 16) {
  678. const uuidStrArray: Array<string> = [];
  679. for (let i = 0; i < 16; i++) {
  680. uuidStrArray.push(expGolombDecoder.readUByte().toString(16));
  681.  
  682. if (i === 3 || i === 5 || i === 7 || i === 9) {
  683. uuidStrArray.push('-');
  684. }
  685. }
  686. const length = payloadSize - 16;
  687. const userDataPayloadBytes = new Uint8Array(length);
  688. for (let i = 0; i < length; i++) {
  689. userDataPayloadBytes[i] = expGolombDecoder.readUByte();
  690. }
  691.  
  692. insertSampleInOrder(this._txtTrack.samples, {
  693. pts: pes.pts,
  694. payloadType: payloadType,
  695. uuid: uuidStrArray.join(''),
  696. userData: utf8ArrayToStr(userDataPayloadBytes),
  697. userDataBytes: userDataPayloadBytes,
  698. });
  699. }
  700. } else if (payloadSize < expGolombDecoder.bytesAvailable) {
  701. for (let i = 0; i < payloadSize; i++) {
  702. expGolombDecoder.readUByte();
  703. }
  704. }
  705. }
  706. break;
  707. // SPS
  708. }
  709. case 7:
  710. push = true;
  711. spsfound = true;
  712. if (debug && avcSample) {
  713. avcSample.debug += 'SPS ';
  714. }
  715.  
  716. if (!track.sps) {
  717. const expGolombDecoder = new ExpGolomb(unit.data);
  718. const config = expGolombDecoder.readSPS();
  719. track.width = config.width;
  720. track.height = config.height;
  721. track.pixelRatio = config.pixelRatio;
  722. // TODO: `track.sps` is defined as a `number[]`, but we're setting it to a `Uint8Array[]`.
  723. track.sps = [unit.data] as any;
  724. track.duration = this._duration;
  725. const codecarray = unit.data.subarray(1, 4);
  726. let codecstring = 'avc1.';
  727. for (let i = 0; i < 3; i++) {
  728. let h = codecarray[i].toString(16);
  729. if (h.length < 2) {
  730. h = '0' + h;
  731. }
  732.  
  733. codecstring += h;
  734. }
  735. track.codec = codecstring;
  736. }
  737. break;
  738. // PPS
  739. case 8:
  740. push = true;
  741. if (debug && avcSample) {
  742. avcSample.debug += 'PPS ';
  743. }
  744.  
  745. if (!track.pps) {
  746. // TODO: `track.pss` is defined as a `number[]`, but we're setting it to a `Uint8Array[]`.
  747. track.pps = [unit.data] as any;
  748. }
  749.  
  750. break;
  751. // AUD
  752. case 9:
  753. push = false;
  754. track.audFound = true;
  755. if (avcSample) {
  756. pushAccessUnit(avcSample, track);
  757. }
  758.  
  759. avcSample = this.avcSample = createAVCSample(
  760. false,
  761. pes.pts,
  762. pes.dts,
  763. debug ? 'AUD ' : ''
  764. );
  765. break;
  766. // Filler Data
  767. case 12:
  768. push = false;
  769. break;
  770. default:
  771. push = false;
  772. if (avcSample) {
  773. avcSample.debug += 'unknown NAL ' + unit.type + ' ';
  774. }
  775.  
  776. break;
  777. }
  778. if (avcSample && push) {
  779. const units = avcSample.units;
  780. units.push(unit);
  781. }
  782. });
  783. // if last PES packet, push samples
  784. if (last && avcSample) {
  785. pushAccessUnit(avcSample, track);
  786. this.avcSample = null;
  787. }
  788. }
  789.  
  790. private getLastNalUnit() {
  791. let avcSample = this.avcSample;
  792. let lastUnit;
  793. // try to fallback to previous sample if current one is empty
  794. if (!avcSample || avcSample.units.length === 0) {
  795. const samples = this._avcTrack.samples;
  796. avcSample = samples[samples.length - 1];
  797. }
  798. if (avcSample?.units) {
  799. const units = avcSample.units;
  800. lastUnit = units[units.length - 1];
  801. }
  802. return lastUnit;
  803. }
  804.  
  805. private parseAVCNALu(
  806. array: Uint8Array
  807. ): Array<{
  808. data: Uint8Array;
  809. type: number;
  810. state?: number;
  811. }> {
  812. const len = array.byteLength;
  813. const track = this._avcTrack;
  814. let state = track.naluState || 0;
  815. const lastState = state;
  816. const units = [] as Array<{
  817. data: Uint8Array;
  818. type: number;
  819. state?: number;
  820. }>;
  821. let i = 0;
  822. let value;
  823. let overflow;
  824. let unitType;
  825. let lastUnitStart = -1;
  826. let lastUnitType: number = 0;
  827. // logger.log('PES:' + Hex.hexDump(array));
  828.  
  829. if (state === -1) {
  830. // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
  831. lastUnitStart = 0;
  832. // NALu type is value read from offset 0
  833. lastUnitType = array[0] & 0x1f;
  834. state = 0;
  835. i = 1;
  836. }
  837.  
  838. while (i < len) {
  839. value = array[i++];
  840. // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
  841. if (!state) {
  842. state = value ? 0 : 1;
  843. continue;
  844. }
  845. if (state === 1) {
  846. state = value ? 0 : 2;
  847. continue;
  848. }
  849. // here we have state either equal to 2 or 3
  850. if (!value) {
  851. state = 3;
  852. } else if (value === 1) {
  853. if (lastUnitStart >= 0) {
  854. const unit = {
  855. data: array.subarray(lastUnitStart, i - state - 1),
  856. type: lastUnitType,
  857. };
  858. // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
  859. units.push(unit);
  860. } else {
  861. // lastUnitStart is undefined => this is the first start code found in this PES packet
  862. // first check if start code delimiter is overlapping between 2 PES packets,
  863. // ie it started in last packet (lastState not zero)
  864. // and ended at the beginning of this PES packet (i <= 4 - lastState)
  865. const lastUnit = this.getLastNalUnit();
  866. if (lastUnit) {
  867. if (lastState && i <= 4 - lastState) {
  868. // start delimiter overlapping between PES packets
  869. // strip start delimiter bytes from the end of last NAL unit
  870. // check if lastUnit had a state different from zero
  871. if (lastUnit.state) {
  872. // strip last bytes
  873. lastUnit.data = lastUnit.data.subarray(
  874. 0,
  875. lastUnit.data.byteLength - lastState
  876. );
  877. }
  878. }
  879. // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
  880. overflow = i - state - 1;
  881. if (overflow > 0) {
  882. // logger.log('first NALU found with overflow:' + overflow);
  883. const tmp = new Uint8Array(lastUnit.data.byteLength + overflow);
  884. tmp.set(lastUnit.data, 0);
  885. tmp.set(array.subarray(0, overflow), lastUnit.data.byteLength);
  886. lastUnit.data = tmp;
  887. }
  888. }
  889. }
  890. // check if we can read unit type
  891. if (i < len) {
  892. unitType = array[i] & 0x1f;
  893. // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
  894. lastUnitStart = i;
  895. lastUnitType = unitType;
  896. state = 0;
  897. } else {
  898. // not enough byte to read unit type. let's read it on next PES parsing
  899. state = -1;
  900. }
  901. } else {
  902. state = 0;
  903. }
  904. }
  905. if (lastUnitStart >= 0 && state >= 0) {
  906. const unit = {
  907. data: array.subarray(lastUnitStart, len),
  908. type: lastUnitType,
  909. state: state,
  910. };
  911. units.push(unit);
  912. // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
  913. }
  914. // no NALu found
  915. if (units.length === 0) {
  916. // append pes.data to previous NAL unit
  917. const lastUnit = this.getLastNalUnit();
  918. if (lastUnit) {
  919. const tmp = new Uint8Array(lastUnit.data.byteLength + array.byteLength);
  920. tmp.set(lastUnit.data, 0);
  921. tmp.set(array, lastUnit.data.byteLength);
  922. lastUnit.data = tmp;
  923. }
  924. }
  925. track.naluState = state;
  926. return units;
  927. }
  928.  
  929. private parseAACPES(pes: PES) {
  930. const startOffset = 0;
  931. const track = this._audioTrack;
  932. const aacLastPTS = this.aacLastPTS;
  933. const aacOverFlow = this.aacOverFlow;
  934. let data = pes.data;
  935. if (aacOverFlow) {
  936. const tmp = new Uint8Array(aacOverFlow.byteLength + data.byteLength);
  937. tmp.set(aacOverFlow, 0);
  938. tmp.set(data, aacOverFlow.byteLength);
  939. // logger.log(`AAC: append overflowing ${aacOverFlow.byteLength} bytes to beginning of new PES`);
  940. data = tmp;
  941. }
  942. // look for ADTS header (0xFFFx)
  943. let offset: number;
  944. let len: number;
  945. for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
  946. if (ADTS.isHeader(data, offset)) {
  947. break;
  948. }
  949. }
  950. // if ADTS header does not start straight from the beginning of the PES payload, raise an error
  951. if (offset) {
  952. let reason;
  953. let fatal;
  954. if (offset < len - 1) {
  955. reason = `AAC PES did not start with ADTS header,offset:${offset}`;
  956. fatal = false;
  957. } else {
  958. reason = 'no ADTS header found in AAC PES';
  959. fatal = true;
  960. }
  961. logger.warn(`parsing error:${reason}`);
  962. this.observer.emit(Events.ERROR, Events.ERROR, {
  963. type: ErrorTypes.MEDIA_ERROR,
  964. details: ErrorDetails.FRAG_PARSING_ERROR,
  965. fatal,
  966. reason,
  967. });
  968. if (fatal) {
  969. return;
  970. }
  971. }
  972.  
  973. ADTS.initTrackConfig(track, this.observer, data, offset, this.audioCodec);
  974.  
  975. let frameIndex = 0;
  976. const frameDuration = ADTS.getFrameDuration(track.samplerate as number);
  977.  
  978. // if last AAC frame is overflowing, we should ensure timestamps are contiguous:
  979. // first sample PTS should be equal to last sample PTS + frameDuration
  980. let pts: number;
  981. if (pes.pts !== undefined) {
  982. pts = pes.pts;
  983. } else if (aacLastPTS !== null) {
  984. pts = aacLastPTS;
  985. } else {
  986. logger.warn('[tsdemuxer]: AAC PES unknown PTS');
  987. return;
  988. }
  989. if (aacOverFlow && aacLastPTS !== null) {
  990. const newPTS = aacLastPTS + frameDuration;
  991. if (Math.abs(newPTS - pts) > 1) {
  992. logger.log(
  993. `[tsdemuxer]: AAC: align PTS for overlapping frames by ${Math.round(
  994. (newPTS - pts) / 90
  995. )}`
  996. );
  997. pts = newPTS;
  998. }
  999. }
  1000.  
  1001. // scan for aac samples
  1002. let stamp: number | null = null;
  1003. while (offset < len) {
  1004. if (ADTS.isHeader(data, offset)) {
  1005. if (offset + 5 < len) {
  1006. const frame = ADTS.appendFrame(track, data, offset, pts, frameIndex);
  1007. if (frame) {
  1008. offset += frame.length;
  1009. stamp = frame.sample.pts;
  1010. frameIndex++;
  1011. continue;
  1012. }
  1013. }
  1014. // We are at an ADTS header, but do not have enough data for a frame
  1015. // Remaining data will be added to aacOverFlow
  1016. break;
  1017. } else {
  1018. // nothing found, keep looking
  1019. offset++;
  1020. }
  1021. }
  1022.  
  1023. this.aacOverFlow = offset < len ? data.subarray(offset, len) : null;
  1024. this.aacLastPTS = stamp;
  1025. }
  1026.  
  1027. private parseMPEGPES(pes: PES) {
  1028. const data = pes.data;
  1029. const length = data.length;
  1030. let frameIndex = 0;
  1031. let offset = 0;
  1032. const pts = pes.pts;
  1033. if (pts === undefined) {
  1034. logger.warn('[tsdemuxer]: MPEG PES unknown PTS');
  1035. return;
  1036. }
  1037.  
  1038. while (offset < length) {
  1039. if (MpegAudio.isHeader(data, offset)) {
  1040. const frame = MpegAudio.appendFrame(
  1041. this._audioTrack,
  1042. data,
  1043. offset,
  1044. pts,
  1045. frameIndex
  1046. );
  1047. if (frame) {
  1048. offset += frame.length;
  1049. frameIndex++;
  1050. } else {
  1051. // logger.log('Unable to parse Mpeg audio frame');
  1052. break;
  1053. }
  1054. } else {
  1055. // nothing found, keep looking
  1056. offset++;
  1057. }
  1058. }
  1059. }
  1060.  
  1061. private parseID3PES(pes: PES) {
  1062. if (pes.pts === undefined) {
  1063. logger.warn('[tsdemuxer]: ID3 PES unknown PTS');
  1064. return;
  1065. }
  1066. this._id3Track.samples.push(pes as Required<PES>);
  1067. }
  1068. }
  1069.  
  1070. function createAVCSample(
  1071. key: boolean,
  1072. pts: number | undefined,
  1073. dts: number | undefined,
  1074. debug: string
  1075. ): ParsedAvcSample {
  1076. return {
  1077. key,
  1078. frame: false,
  1079. pts,
  1080. dts,
  1081. units: [],
  1082. debug,
  1083. length: 0,
  1084. };
  1085. }
  1086.  
  1087. function parsePAT(data, offset) {
  1088. // skip the PSI header and parse the first PMT entry
  1089. return ((data[offset + 10] & 0x1f) << 8) | data[offset + 11];
  1090. // logger.log('PMT PID:' + this._pmtId);
  1091. }
  1092.  
  1093. function parsePMT(data, offset, mpegSupported, isSampleAes) {
  1094. const result = { audio: -1, avc: -1, id3: -1, isAAC: true };
  1095. const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
  1096. const tableEnd = offset + 3 + sectionLength - 4;
  1097. // to determine where the table is, we have to figure out how
  1098. // long the program info descriptors are
  1099. const programInfoLength =
  1100. ((data[offset + 10] & 0x0f) << 8) | data[offset + 11];
  1101. // advance the offset to the first entry in the mapping table
  1102. offset += 12 + programInfoLength;
  1103. while (offset < tableEnd) {
  1104. const pid = ((data[offset + 1] & 0x1f) << 8) | data[offset + 2];
  1105. switch (data[offset]) {
  1106. case 0xcf: // SAMPLE-AES AAC
  1107. if (!isSampleAes) {
  1108. logger.log(
  1109. 'ADTS AAC with AES-128-CBC frame encryption found in unencrypted stream'
  1110. );
  1111. break;
  1112. }
  1113. /* falls through */
  1114. case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
  1115. // logger.log('AAC PID:' + pid);
  1116. if (result.audio === -1) {
  1117. result.audio = pid;
  1118. }
  1119.  
  1120. break;
  1121.  
  1122. // Packetized metadata (ID3)
  1123. case 0x15:
  1124. // logger.log('ID3 PID:' + pid);
  1125. if (result.id3 === -1) {
  1126. result.id3 = pid;
  1127. }
  1128.  
  1129. break;
  1130.  
  1131. case 0xdb: // SAMPLE-AES AVC
  1132. if (!isSampleAes) {
  1133. logger.log(
  1134. 'H.264 with AES-128-CBC slice encryption found in unencrypted stream'
  1135. );
  1136. break;
  1137. }
  1138. /* falls through */
  1139. case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
  1140. // logger.log('AVC PID:' + pid);
  1141. if (result.avc === -1) {
  1142. result.avc = pid;
  1143. }
  1144.  
  1145. break;
  1146.  
  1147. // ISO/IEC 11172-3 (MPEG-1 audio)
  1148. // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
  1149. case 0x03:
  1150. case 0x04:
  1151. // logger.log('MPEG PID:' + pid);
  1152. if (!mpegSupported) {
  1153. logger.log('MPEG audio found, not supported in this browser');
  1154. } else if (result.audio === -1) {
  1155. result.audio = pid;
  1156. result.isAAC = false;
  1157. }
  1158. break;
  1159.  
  1160. case 0x24:
  1161. logger.warn('Unsupported HEVC stream type found');
  1162. break;
  1163.  
  1164. default:
  1165. // logger.log('unknown stream type:' + data[offset]);
  1166. break;
  1167. }
  1168. // move to the next table entry
  1169. // skip past the elementary stream descriptors, if present
  1170. offset += (((data[offset + 3] & 0x0f) << 8) | data[offset + 4]) + 5;
  1171. }
  1172. return result;
  1173. }
  1174.  
  1175. function parsePES(stream: ElementaryStreamData): PES | null {
  1176. let i = 0;
  1177. let frag: Uint8Array;
  1178. let pesLen: number;
  1179. let pesHdrLen: number;
  1180. let pesPts: number | undefined;
  1181. let pesDts: number | undefined;
  1182. const data = stream.data;
  1183. // safety check
  1184. if (!stream || stream.size === 0) {
  1185. return null;
  1186. }
  1187.  
  1188. // we might need up to 19 bytes to read PES header
  1189. // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes
  1190. // usually only one merge is needed (and this is rare ...)
  1191. while (data[0].length < 19 && data.length > 1) {
  1192. const newData = new Uint8Array(data[0].length + data[1].length);
  1193. newData.set(data[0]);
  1194. newData.set(data[1], data[0].length);
  1195. data[0] = newData;
  1196. data.splice(1, 1);
  1197. }
  1198. // retrieve PTS/DTS from first fragment
  1199. frag = data[0];
  1200. const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
  1201. if (pesPrefix === 1) {
  1202. pesLen = (frag[4] << 8) + frag[5];
  1203. // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated
  1204. // minus 6 : PES header size
  1205. if (pesLen && pesLen > stream.size - 6) {
  1206. return null;
  1207. }
  1208.  
  1209. const pesFlags = frag[7];
  1210. if (pesFlags & 0xc0) {
  1211. /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
  1212. as PTS / DTS is 33 bit we cannot use bitwise operator in JS,
  1213. as Bitwise operators treat their operands as a sequence of 32 bits */
  1214. pesPts =
  1215. (frag[9] & 0x0e) * 536870912 + // 1 << 29
  1216. (frag[10] & 0xff) * 4194304 + // 1 << 22
  1217. (frag[11] & 0xfe) * 16384 + // 1 << 14
  1218. (frag[12] & 0xff) * 128 + // 1 << 7
  1219. (frag[13] & 0xfe) / 2;
  1220.  
  1221. if (pesFlags & 0x40) {
  1222. pesDts =
  1223. (frag[14] & 0x0e) * 536870912 + // 1 << 29
  1224. (frag[15] & 0xff) * 4194304 + // 1 << 22
  1225. (frag[16] & 0xfe) * 16384 + // 1 << 14
  1226. (frag[17] & 0xff) * 128 + // 1 << 7
  1227. (frag[18] & 0xfe) / 2;
  1228.  
  1229. if (pesPts - pesDts > 60 * 90000) {
  1230. logger.warn(
  1231. `${Math.round(
  1232. (pesPts - pesDts) / 90000
  1233. )}s delta between PTS and DTS, align them`
  1234. );
  1235. pesPts = pesDts;
  1236. }
  1237. } else {
  1238. pesDts = pesPts;
  1239. }
  1240. }
  1241. pesHdrLen = frag[8];
  1242. // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
  1243. let payloadStartOffset = pesHdrLen + 9;
  1244. if (stream.size <= payloadStartOffset) {
  1245. return null;
  1246. }
  1247. stream.size -= payloadStartOffset;
  1248. // reassemble PES packet
  1249. const pesData = new Uint8Array(stream.size);
  1250. for (let j = 0, dataLen = data.length; j < dataLen; j++) {
  1251. frag = data[j];
  1252. let len = frag.byteLength;
  1253. if (payloadStartOffset) {
  1254. if (payloadStartOffset > len) {
  1255. // trim full frag if PES header bigger than frag
  1256. payloadStartOffset -= len;
  1257. continue;
  1258. } else {
  1259. // trim partial frag if PES header smaller than frag
  1260. frag = frag.subarray(payloadStartOffset);
  1261. len -= payloadStartOffset;
  1262. payloadStartOffset = 0;
  1263. }
  1264. }
  1265. pesData.set(frag, i);
  1266. i += len;
  1267. }
  1268. if (pesLen) {
  1269. // payload size : remove PES header + PES extension
  1270. pesLen -= pesHdrLen + 3;
  1271. }
  1272. return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen };
  1273. }
  1274. return null;
  1275. }
  1276.  
  1277. function pushAccessUnit(avcSample: ParsedAvcSample, avcTrack: DemuxedAvcTrack) {
  1278. if (avcSample.units.length && avcSample.frame) {
  1279. // if sample does not have PTS/DTS, patch with last sample PTS/DTS
  1280. if (avcSample.pts === undefined) {
  1281. const samples = avcTrack.samples;
  1282. const nbSamples = samples.length;
  1283. if (nbSamples) {
  1284. const lastSample = samples[nbSamples - 1];
  1285. avcSample.pts = lastSample.pts;
  1286. avcSample.dts = lastSample.dts;
  1287. } else {
  1288. // dropping samples, no timestamp found
  1289. avcTrack.dropped++;
  1290. return;
  1291. }
  1292. }
  1293. avcTrack.samples.push(avcSample as AvcSample);
  1294. }
  1295. if (avcSample.debug.length) {
  1296. logger.log(avcSample.pts + '/' + avcSample.dts + ':' + avcSample.debug);
  1297. }
  1298. }
  1299.  
  1300. function insertSampleInOrder(arr, data) {
  1301. const len = arr.length;
  1302. if (len > 0) {
  1303. if (data.pts >= arr[len - 1].pts) {
  1304. arr.push(data);
  1305. } else {
  1306. for (let pos = len - 1; pos >= 0; pos--) {
  1307. if (data.pts < arr[pos].pts) {
  1308. arr.splice(pos, 0, data);
  1309. break;
  1310. }
  1311. }
  1312. }
  1313. } else {
  1314. arr.push(data);
  1315. }
  1316. }
  1317.  
  1318. /**
  1319. * remove Emulation Prevention bytes from a RBSP
  1320. */
  1321. export function discardEPB(data: Uint8Array): Uint8Array {
  1322. const length = data.byteLength;
  1323. const EPBPositions = [] as Array<number>;
  1324. let i = 1;
  1325.  
  1326. // Find all `Emulation Prevention Bytes`
  1327. while (i < length - 2) {
  1328. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  1329. EPBPositions.push(i + 2);
  1330. i += 2;
  1331. } else {
  1332. i++;
  1333. }
  1334. }
  1335.  
  1336. // If no Emulation Prevention Bytes were found just return the original
  1337. // array
  1338. if (EPBPositions.length === 0) {
  1339. return data;
  1340. }
  1341.  
  1342. // Create a new array to hold the NAL unit data
  1343. const newLength = length - EPBPositions.length;
  1344. const newData = new Uint8Array(newLength);
  1345. let sourceIndex = 0;
  1346.  
  1347. for (i = 0; i < newLength; sourceIndex++, i++) {
  1348. if (sourceIndex === EPBPositions[0]) {
  1349. // Skip this byte
  1350. sourceIndex++;
  1351. // Remove this position index
  1352. EPBPositions.shift();
  1353. }
  1354. newData[i] = data[sourceIndex];
  1355. }
  1356. return newData;
  1357. }
  1358.  
  1359. export default TSDemuxer;