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