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