Source: lib/transmuxer/ts_transmuxer.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.transmuxer.TsTransmuxer');
  7. goog.require('shaka.media.Capabilities');
  8. goog.require('shaka.transmuxer.AacTransmuxer');
  9. goog.require('shaka.transmuxer.Ac3');
  10. goog.require('shaka.transmuxer.ADTS');
  11. goog.require('shaka.transmuxer.Ec3');
  12. goog.require('shaka.transmuxer.H264');
  13. goog.require('shaka.transmuxer.H265');
  14. goog.require('shaka.transmuxer.MpegAudio');
  15. goog.require('shaka.transmuxer.Opus');
  16. goog.require('shaka.transmuxer.TransmuxerEngine');
  17. goog.require('shaka.util.BufferUtils');
  18. goog.require('shaka.util.Error');
  19. goog.require('shaka.util.Id3Utils');
  20. goog.require('shaka.util.ManifestParserUtils');
  21. goog.require('shaka.util.MimeUtils');
  22. goog.require('shaka.util.Mp4Generator');
  23. goog.require('shaka.util.StreamUtils');
  24. goog.require('shaka.util.TsParser');
  25. goog.require('shaka.util.Uint8ArrayUtils');
  26. goog.requireType('shaka.media.SegmentReference');
  27. /**
  28. * @implements {shaka.extern.Transmuxer}
  29. * @export
  30. */
  31. shaka.transmuxer.TsTransmuxer = class {
  32. /**
  33. * @param {string} mimeType
  34. */
  35. constructor(mimeType) {
  36. /** @private {string} */
  37. this.originalMimeType_ = mimeType;
  38. /** @private {number} */
  39. this.frameIndex_ = 0;
  40. /** @private {!Map<string, !Uint8Array>} */
  41. this.initSegments = new Map();
  42. /** @private {?shaka.util.TsParser} */
  43. this.tsParser_ = null;
  44. /** @private {?shaka.transmuxer.AacTransmuxer} */
  45. this.aacTransmuxer_ = null;
  46. /** @private {?Uint8Array} */
  47. this.lastInitSegment_ = null;
  48. }
  49. /**
  50. * @override
  51. * @export
  52. */
  53. destroy() {
  54. this.initSegments.clear();
  55. if (this.aacTransmuxer_) {
  56. this.aacTransmuxer_.destroy();
  57. }
  58. }
  59. /**
  60. * Check if the mime type and the content type is supported.
  61. * @param {string} mimeType
  62. * @param {string=} contentType
  63. * @return {boolean}
  64. * @override
  65. * @export
  66. */
  67. isSupported(mimeType, contentType) {
  68. const Capabilities = shaka.media.Capabilities;
  69. if (!this.isTsContainer_(mimeType)) {
  70. return false;
  71. }
  72. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  73. const MimeUtils = shaka.util.MimeUtils;
  74. let convertedMimeType = mimeType;
  75. if (contentType) {
  76. convertedMimeType = this.convertCodecs(contentType, mimeType);
  77. }
  78. const codecs = MimeUtils.getCodecs(convertedMimeType);
  79. const allCodecs = MimeUtils.splitCodecs(codecs);
  80. const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  81. ContentType.AUDIO, allCodecs);
  82. const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  83. ContentType.VIDEO, allCodecs);
  84. const TsTransmuxer = shaka.transmuxer.TsTransmuxer;
  85. if (audioCodec) {
  86. const normalizedCodec = MimeUtils.getNormalizedCodec(audioCodec);
  87. if (!TsTransmuxer.SUPPORTED_AUDIO_CODECS_.includes(normalizedCodec)) {
  88. return false;
  89. }
  90. }
  91. if (videoCodec) {
  92. const normalizedCodec = MimeUtils.getNormalizedCodec(videoCodec);
  93. if (!TsTransmuxer.SUPPORTED_VIDEO_CODECS_.includes(normalizedCodec)) {
  94. return false;
  95. }
  96. }
  97. if (contentType) {
  98. return Capabilities.isTypeSupported(
  99. this.convertCodecs(contentType, mimeType));
  100. }
  101. const audioMime = this.convertCodecs(ContentType.AUDIO, mimeType);
  102. const videoMime = this.convertCodecs(ContentType.VIDEO, mimeType);
  103. return Capabilities.isTypeSupported(audioMime) ||
  104. Capabilities.isTypeSupported(videoMime);
  105. }
  106. /**
  107. * Check if the mimetype is 'video/mp2t'.
  108. * @param {string} mimeType
  109. * @return {boolean}
  110. * @private
  111. */
  112. isTsContainer_(mimeType) {
  113. return mimeType.toLowerCase().split(';')[0] == 'video/mp2t';
  114. }
  115. /**
  116. * @override
  117. * @export
  118. */
  119. convertCodecs(contentType, mimeType) {
  120. if (this.isTsContainer_(mimeType)) {
  121. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  122. const StreamUtils = shaka.util.StreamUtils;
  123. // The replace it's necessary because Firefox(the only browser that
  124. // supports MP3 in MP4) only support the MP3 codec with the mp3 string.
  125. // MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.34"') -> false
  126. // MediaSource.isTypeSupported('audio/mp4; codecs="mp3"') -> true
  127. const codecs = shaka.util.MimeUtils.getCodecs(mimeType)
  128. .replace('mp4a.40.34', 'mp3').split(',')
  129. .map((codecs) => {
  130. return StreamUtils.getCorrectAudioCodecs(codecs, 'audio/mp4');
  131. })
  132. .map(StreamUtils.getCorrectVideoCodecs).join(',');
  133. if (contentType == ContentType.AUDIO) {
  134. return `audio/mp4; codecs="${codecs}"`;
  135. }
  136. return `video/mp4; codecs="${codecs}"`;
  137. }
  138. return mimeType;
  139. }
  140. /**
  141. * @override
  142. * @export
  143. */
  144. getOriginalMimeType() {
  145. return this.originalMimeType_;
  146. }
  147. /**
  148. * @override
  149. * @export
  150. */
  151. transmux(data, stream, reference, duration, contentType) {
  152. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  153. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  154. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  155. if (contentType == ContentType.AUDIO &&
  156. !shaka.util.TsParser.probe(uint8ArrayData)) {
  157. const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
  158. let offset = id3Data.length;
  159. for (; offset < uint8ArrayData.length; offset++) {
  160. if (shaka.transmuxer.MpegAudio.probe(uint8ArrayData, offset)) {
  161. return Promise.reject(new shaka.util.Error(
  162. shaka.util.Error.Severity.CRITICAL,
  163. shaka.util.Error.Category.MEDIA,
  164. shaka.util.Error.Code.TRANSMUXING_FAILED,
  165. reference ? reference.getUris()[0] : null));
  166. }
  167. }
  168. offset = id3Data.length;
  169. for (; offset < uint8ArrayData.length; offset++) {
  170. if (shaka.transmuxer.ADTS.probe(uint8ArrayData, offset)) {
  171. if (!this.aacTransmuxer_) {
  172. this.aacTransmuxer_ =
  173. new shaka.transmuxer.AacTransmuxer('audio/aac');
  174. }
  175. return this.aacTransmuxer_
  176. .transmux(data, stream, reference, duration, contentType);
  177. }
  178. }
  179. return Promise.reject(new shaka.util.Error(
  180. shaka.util.Error.Severity.CRITICAL,
  181. shaka.util.Error.Category.MEDIA,
  182. shaka.util.Error.Code.TRANSMUXING_FAILED,
  183. reference ? reference.getUris()[0] : null));
  184. }
  185. if (!this.tsParser_) {
  186. this.tsParser_ = new shaka.util.TsParser();
  187. } else {
  188. this.tsParser_.clearData();
  189. }
  190. const tsParser = this.tsParser_.parse(uint8ArrayData);
  191. const streamInfos = [];
  192. const codecs = tsParser.getCodecs();
  193. try {
  194. let streamInfo = null;
  195. if (contentType == ContentType.VIDEO) {
  196. switch (codecs.video) {
  197. case 'avc':
  198. streamInfo =
  199. this.getAvcStreamInfo_(tsParser, stream, duration, reference);
  200. break;
  201. case 'hvc':
  202. streamInfo =
  203. this.getHvcStreamInfo_(tsParser, stream, duration, reference);
  204. break;
  205. }
  206. if (streamInfo) {
  207. streamInfos.push(streamInfo);
  208. streamInfo = null;
  209. }
  210. }
  211. if (contentType == ContentType.AUDIO) {
  212. switch (codecs.audio) {
  213. case 'aac':
  214. streamInfo =
  215. this.getAacStreamInfo_(tsParser, stream, duration, reference);
  216. break;
  217. case 'ac3':
  218. streamInfo =
  219. this.getAc3StreamInfo_(tsParser, stream, duration, reference);
  220. break;
  221. case 'ec3':
  222. streamInfo =
  223. this.getEc3StreamInfo_(tsParser, stream, duration, reference);
  224. break;
  225. case 'mp3':
  226. streamInfo =
  227. this.getMp3StreamInfo_(tsParser, stream, duration, reference);
  228. break;
  229. case 'opus':
  230. streamInfo =
  231. this.getOpusStreamInfo_(tsParser, stream, duration, reference);
  232. break;
  233. }
  234. if (streamInfo) {
  235. streamInfos.push(streamInfo);
  236. streamInfo = null;
  237. }
  238. }
  239. } catch (e) {
  240. if (e && e.code == shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA) {
  241. return Promise.resolve(new Uint8Array([]));
  242. }
  243. return Promise.reject(e);
  244. }
  245. if (!streamInfos.length) {
  246. return Promise.reject(new shaka.util.Error(
  247. shaka.util.Error.Severity.CRITICAL,
  248. shaka.util.Error.Category.MEDIA,
  249. shaka.util.Error.Code.TRANSMUXING_FAILED,
  250. reference ? reference.getUris()[0] : null));
  251. }
  252. const mp4Generator = new shaka.util.Mp4Generator(streamInfos);
  253. let initSegment;
  254. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  255. if (!this.initSegments.has(initSegmentKey)) {
  256. initSegment = mp4Generator.initSegment();
  257. this.initSegments.set(initSegmentKey, initSegment);
  258. } else {
  259. initSegment = this.initSegments.get(initSegmentKey);
  260. }
  261. const appendInitSegment = this.lastInitSegment_ !== initSegment;
  262. const segmentData = mp4Generator.segmentData();
  263. this.lastInitSegment_ = initSegment;
  264. this.frameIndex_++;
  265. if (appendInitSegment) {
  266. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  267. return Promise.resolve(transmuxData);
  268. } else {
  269. return Promise.resolve(segmentData);
  270. }
  271. }
  272. /**
  273. * @param {shaka.util.TsParser} tsParser
  274. * @param {shaka.extern.Stream} stream
  275. * @param {number} duration
  276. * @param {?shaka.media.SegmentReference} reference
  277. * @return {shaka.util.Mp4Generator.StreamInfo}
  278. * @private
  279. */
  280. getAacStreamInfo_(tsParser, stream, duration, reference) {
  281. const ADTS = shaka.transmuxer.ADTS;
  282. const timescale = shaka.util.TsParser.Timescale;
  283. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  284. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  285. const samples = [];
  286. let info;
  287. let firstPts = null;
  288. /** @type {?number} */
  289. let nextStartOffset = null;
  290. /** @type {?Uint8Array} */
  291. let overflowBytes = null;
  292. for (const audioData of tsParser.getAudioData()) {
  293. let data = audioData.data;
  294. if (!data) {
  295. continue;
  296. }
  297. let offset = 0;
  298. if (nextStartOffset == -1 && overflowBytes) {
  299. data = Uint8ArrayUtils.concat(overflowBytes, audioData.data);
  300. nextStartOffset = null;
  301. } else if (nextStartOffset != null && overflowBytes) {
  302. offset = Math.max(0, nextStartOffset);
  303. const missingFrameData =
  304. Uint8ArrayUtils.concat(overflowBytes, data.subarray(0, offset));
  305. samples.push({
  306. data: missingFrameData,
  307. size: missingFrameData.byteLength,
  308. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  309. cts: 0,
  310. flags: {
  311. isLeading: 0,
  312. isDependedOn: 0,
  313. hasRedundancy: 0,
  314. degradPrio: 0,
  315. dependsOn: 2,
  316. isNonSync: 0,
  317. },
  318. });
  319. overflowBytes = null;
  320. nextStartOffset = null;
  321. }
  322. info = ADTS.parseInfo(data, offset);
  323. if (!info) {
  324. throw new shaka.util.Error(
  325. shaka.util.Error.Severity.CRITICAL,
  326. shaka.util.Error.Category.MEDIA,
  327. shaka.util.Error.Code.TRANSMUXING_FAILED,
  328. reference ? reference.getUris()[0] : null);
  329. }
  330. stream.audioSamplingRate = info.sampleRate;
  331. stream.channelsCount = info.channelCount;
  332. if (firstPts == null && audioData.pts !== null) {
  333. firstPts = audioData.pts;
  334. }
  335. while (offset < data.length) {
  336. const header = ADTS.parseHeader(data, offset);
  337. if (!header) {
  338. overflowBytes = data.subarray(offset, data.length);
  339. nextStartOffset = -1;
  340. break;
  341. }
  342. const length = header.headerLength + header.frameLength;
  343. nextStartOffset = Math.max(0, offset + length - data.length);
  344. if (nextStartOffset != 0) {
  345. overflowBytes = data.subarray(
  346. offset + header.headerLength, offset + length);
  347. } else if (offset + length <= data.length) {
  348. const frameData = data.subarray(
  349. offset + header.headerLength, offset + length);
  350. samples.push({
  351. data: frameData,
  352. size: header.frameLength,
  353. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  354. cts: 0,
  355. flags: {
  356. isLeading: 0,
  357. isDependedOn: 0,
  358. hasRedundancy: 0,
  359. degradPrio: 0,
  360. dependsOn: 2,
  361. isNonSync: 0,
  362. },
  363. });
  364. }
  365. offset += length;
  366. }
  367. }
  368. if (!info || firstPts == null) {
  369. if (!tsParser.getVideoData().length) {
  370. throw new shaka.util.Error(
  371. shaka.util.Error.Severity.CRITICAL,
  372. shaka.util.Error.Category.MEDIA,
  373. shaka.util.Error.Code.TRANSMUXING_FAILED,
  374. reference ? reference.getUris()[0] : null);
  375. }
  376. firstPts = reference.startTime * timescale;
  377. const allCodecs = shaka.util.MimeUtils.splitCodecs(stream.codecs);
  378. const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  379. shaka.util.ManifestParserUtils.ContentType.AUDIO, allCodecs);
  380. if (!audioCodec || !stream.channelsCount || !stream.audioSamplingRate) {
  381. throw new shaka.util.Error(
  382. shaka.util.Error.Severity.CRITICAL,
  383. shaka.util.Error.Category.MEDIA,
  384. shaka.util.Error.Code.TRANSMUXING_FAILED,
  385. reference ? reference.getUris()[0] : null);
  386. }
  387. info = {
  388. sampleRate: stream.audioSamplingRate,
  389. channelCount: stream.channelsCount,
  390. codec: audioCodec,
  391. };
  392. const silenceFrame =
  393. ADTS.getSilentFrame(audioCodec, stream.channelsCount);
  394. if (!silenceFrame) {
  395. throw new shaka.util.Error(
  396. shaka.util.Error.Severity.CRITICAL,
  397. shaka.util.Error.Category.MEDIA,
  398. shaka.util.Error.Code.TRANSMUXING_FAILED,
  399. reference ? reference.getUris()[0] : null);
  400. }
  401. const segmentDuration =
  402. (reference.endTime - reference.startTime) * timescale;
  403. const finalPTs = firstPts + segmentDuration;
  404. let currentPts = firstPts;
  405. while (currentPts < finalPTs) {
  406. samples.push({
  407. data: silenceFrame,
  408. size: silenceFrame.byteLength,
  409. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  410. cts: 0,
  411. flags: {
  412. isLeading: 0,
  413. isDependedOn: 0,
  414. hasRedundancy: 0,
  415. degradPrio: 0,
  416. dependsOn: 2,
  417. isNonSync: 0,
  418. },
  419. });
  420. currentPts += ADTS.AAC_SAMPLES_PER_FRAME / info.sampleRate * timescale;
  421. }
  422. }
  423. /** @type {number} */
  424. const sampleRate = info.sampleRate;
  425. /** @type {number} */
  426. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  427. return {
  428. id: stream.id,
  429. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  430. codecs: info.codec,
  431. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  432. timescale: sampleRate,
  433. duration: duration,
  434. videoNalus: [],
  435. audioConfig: new Uint8Array([]),
  436. videoConfig: new Uint8Array([]),
  437. hSpacing: 0,
  438. vSpacing: 0,
  439. data: {
  440. sequenceNumber: this.frameIndex_,
  441. baseMediaDecodeTime: baseMediaDecodeTime,
  442. samples: samples,
  443. },
  444. stream: stream,
  445. };
  446. }
  447. /**
  448. * @param {shaka.util.TsParser} tsParser
  449. * @param {shaka.extern.Stream} stream
  450. * @param {number} duration
  451. * @param {?shaka.media.SegmentReference} reference
  452. * @return {shaka.util.Mp4Generator.StreamInfo}
  453. * @private
  454. */
  455. getAc3StreamInfo_(tsParser, stream, duration, reference) {
  456. const Ac3 = shaka.transmuxer.Ac3;
  457. const timescale = shaka.util.TsParser.Timescale;
  458. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  459. const samples = [];
  460. /** @type {number} */
  461. let sampleRate = 0;
  462. /** @type {!Uint8Array} */
  463. let audioConfig = new Uint8Array([]);
  464. let firstPts = null;
  465. for (const audioData of tsParser.getAudioData()) {
  466. const data = audioData.data;
  467. if (firstPts == null && audioData.pts !== null) {
  468. firstPts = audioData.pts;
  469. }
  470. let offset = 0;
  471. while (offset < data.length) {
  472. const frame = Ac3.parseFrame(data, offset);
  473. if (!frame) {
  474. offset++;
  475. continue;
  476. }
  477. stream.audioSamplingRate = frame.sampleRate;
  478. stream.channelsCount = frame.channelCount;
  479. sampleRate = frame.sampleRate;
  480. audioConfig = frame.audioConfig;
  481. const frameData = data.subarray(
  482. offset, offset + frame.frameLength);
  483. samples.push({
  484. data: frameData,
  485. size: frame.frameLength,
  486. duration: Ac3.AC3_SAMPLES_PER_FRAME,
  487. cts: 0,
  488. flags: {
  489. isLeading: 0,
  490. isDependedOn: 0,
  491. hasRedundancy: 0,
  492. degradPrio: 0,
  493. dependsOn: 2,
  494. isNonSync: 0,
  495. },
  496. });
  497. offset += frame.frameLength;
  498. }
  499. }
  500. if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
  501. throw new shaka.util.Error(
  502. shaka.util.Error.Severity.CRITICAL,
  503. shaka.util.Error.Category.MEDIA,
  504. shaka.util.Error.Code.TRANSMUXING_FAILED,
  505. reference ? reference.getUris()[0] : null);
  506. }
  507. /** @type {number} */
  508. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  509. return {
  510. id: stream.id,
  511. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  512. codecs: 'ac-3',
  513. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  514. timescale: sampleRate,
  515. duration: duration,
  516. videoNalus: [],
  517. audioConfig: audioConfig,
  518. videoConfig: new Uint8Array([]),
  519. hSpacing: 0,
  520. vSpacing: 0,
  521. data: {
  522. sequenceNumber: this.frameIndex_,
  523. baseMediaDecodeTime: baseMediaDecodeTime,
  524. samples: samples,
  525. },
  526. stream: stream,
  527. };
  528. }
  529. /**
  530. * @param {shaka.util.TsParser} tsParser
  531. * @param {shaka.extern.Stream} stream
  532. * @param {number} duration
  533. * @param {?shaka.media.SegmentReference} reference
  534. * @return {shaka.util.Mp4Generator.StreamInfo}
  535. * @private
  536. */
  537. getEc3StreamInfo_(tsParser, stream, duration, reference) {
  538. const Ec3 = shaka.transmuxer.Ec3;
  539. const timescale = shaka.util.TsParser.Timescale;
  540. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  541. const samples = [];
  542. /** @type {number} */
  543. let sampleRate = 0;
  544. /** @type {!Uint8Array} */
  545. let audioConfig = new Uint8Array([]);
  546. let firstPts = null;
  547. for (const audioData of tsParser.getAudioData()) {
  548. const data = audioData.data;
  549. if (firstPts == null && audioData.pts !== null) {
  550. firstPts = audioData.pts;
  551. }
  552. let offset = 0;
  553. while (offset < data.length) {
  554. const frame = Ec3.parseFrame(data, offset);
  555. if (!frame) {
  556. offset++;
  557. continue;
  558. }
  559. stream.audioSamplingRate = frame.sampleRate;
  560. stream.channelsCount = frame.channelCount;
  561. sampleRate = frame.sampleRate;
  562. audioConfig = frame.audioConfig;
  563. const frameData = data.subarray(
  564. offset, offset + frame.frameLength);
  565. samples.push({
  566. data: frameData,
  567. size: frame.frameLength,
  568. duration: Ec3.EC3_SAMPLES_PER_FRAME,
  569. cts: 0,
  570. flags: {
  571. isLeading: 0,
  572. isDependedOn: 0,
  573. hasRedundancy: 0,
  574. degradPrio: 0,
  575. dependsOn: 2,
  576. isNonSync: 0,
  577. },
  578. });
  579. offset += frame.frameLength;
  580. }
  581. }
  582. if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
  583. throw new shaka.util.Error(
  584. shaka.util.Error.Severity.CRITICAL,
  585. shaka.util.Error.Category.MEDIA,
  586. shaka.util.Error.Code.TRANSMUXING_FAILED,
  587. reference ? reference.getUris()[0] : null);
  588. }
  589. /** @type {number} */
  590. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  591. return {
  592. id: stream.id,
  593. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  594. codecs: 'ec-3',
  595. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  596. timescale: sampleRate,
  597. duration: duration,
  598. videoNalus: [],
  599. audioConfig: audioConfig,
  600. videoConfig: new Uint8Array([]),
  601. hSpacing: 0,
  602. vSpacing: 0,
  603. data: {
  604. sequenceNumber: this.frameIndex_,
  605. baseMediaDecodeTime: baseMediaDecodeTime,
  606. samples: samples,
  607. },
  608. stream: stream,
  609. };
  610. }
  611. /**
  612. * @param {shaka.util.TsParser} tsParser
  613. * @param {shaka.extern.Stream} stream
  614. * @param {number} duration
  615. * @param {?shaka.media.SegmentReference} reference
  616. * @return {shaka.util.Mp4Generator.StreamInfo}
  617. * @private
  618. */
  619. getMp3StreamInfo_(tsParser, stream, duration, reference) {
  620. const MpegAudio = shaka.transmuxer.MpegAudio;
  621. const timescale = shaka.util.TsParser.Timescale;
  622. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  623. const samples = [];
  624. let firstHeader;
  625. let firstPts = null;
  626. for (const audioData of tsParser.getAudioData()) {
  627. const data = audioData.data;
  628. if (!data) {
  629. continue;
  630. }
  631. if (firstPts == null && audioData.pts !== null) {
  632. firstPts = audioData.pts;
  633. }
  634. let offset = 0;
  635. while (offset < data.length) {
  636. const header = MpegAudio.parseHeader(data, offset);
  637. if (!header) {
  638. offset++;
  639. continue;
  640. }
  641. if (!firstHeader) {
  642. firstHeader = header;
  643. }
  644. if (offset + header.frameLength <= data.length) {
  645. samples.push({
  646. data: data.subarray(offset, offset + header.frameLength),
  647. size: header.frameLength,
  648. duration: MpegAudio.MPEG_AUDIO_SAMPLE_PER_FRAME,
  649. cts: 0,
  650. flags: {
  651. isLeading: 0,
  652. isDependedOn: 0,
  653. hasRedundancy: 0,
  654. degradPrio: 0,
  655. dependsOn: 2,
  656. isNonSync: 0,
  657. },
  658. });
  659. }
  660. offset += header.frameLength;
  661. }
  662. }
  663. if (!firstHeader || firstPts == null) {
  664. throw new shaka.util.Error(
  665. shaka.util.Error.Severity.CRITICAL,
  666. shaka.util.Error.Category.MEDIA,
  667. shaka.util.Error.Code.TRANSMUXING_FAILED,
  668. reference ? reference.getUris()[0] : null);
  669. }
  670. /** @type {number} */
  671. const sampleRate = firstHeader.sampleRate;
  672. /** @type {number} */
  673. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  674. return {
  675. id: stream.id,
  676. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  677. codecs: 'mp3',
  678. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  679. timescale: sampleRate,
  680. duration: duration,
  681. videoNalus: [],
  682. audioConfig: new Uint8Array([]),
  683. videoConfig: new Uint8Array([]),
  684. hSpacing: 0,
  685. vSpacing: 0,
  686. data: {
  687. sequenceNumber: this.frameIndex_,
  688. baseMediaDecodeTime: baseMediaDecodeTime,
  689. samples: samples,
  690. },
  691. stream: stream,
  692. };
  693. }
  694. /**
  695. * @param {shaka.util.TsParser} tsParser
  696. * @param {shaka.extern.Stream} stream
  697. * @param {number} duration
  698. * @param {?shaka.media.SegmentReference} reference
  699. * @return {shaka.util.Mp4Generator.StreamInfo}
  700. * @private
  701. */
  702. getOpusStreamInfo_(tsParser, stream, duration, reference) {
  703. const Opus = shaka.transmuxer.Opus;
  704. const timescale = shaka.util.TsParser.Timescale;
  705. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  706. const samples = [];
  707. let firstPts = null;
  708. /** @type {?shaka.util.TsParser.OpusMetadata} */
  709. const opusMetadata = tsParser.getOpusMetadata();
  710. if (!opusMetadata) {
  711. throw new shaka.util.Error(
  712. shaka.util.Error.Severity.CRITICAL,
  713. shaka.util.Error.Category.MEDIA,
  714. shaka.util.Error.Code.TRANSMUXING_FAILED,
  715. reference ? reference.getUris()[0] : null);
  716. }
  717. /** @type {!Uint8Array} */
  718. const audioConfig = Opus.getAudioConfig(opusMetadata);
  719. /** @type {number} */
  720. const sampleRate = opusMetadata.sampleRate;
  721. for (const audioData of tsParser.getAudioData()) {
  722. const data = audioData.data;
  723. if (firstPts == null && audioData.pts !== null) {
  724. firstPts = audioData.pts;
  725. }
  726. let offset = 0;
  727. while (offset < data.length) {
  728. const opusPendingTrimStart = (data[offset + 1] & 0x10) !== 0;
  729. const trimEnd = (data[offset + 1] & 0x08) !== 0;
  730. let index = offset + 2;
  731. let size = 0;
  732. while (data[index] === 0xFF) {
  733. size += 255;
  734. index += 1;
  735. }
  736. size += data[index];
  737. index += 1;
  738. index += opusPendingTrimStart ? 2 : 0;
  739. index += trimEnd ? 2 : 0;
  740. const sample = data.slice(index, index + size);
  741. samples.push({
  742. data: sample,
  743. size: sample.byteLength,
  744. duration: Opus.OPUS_AUDIO_SAMPLE_PER_FRAME,
  745. cts: 0,
  746. flags: {
  747. isLeading: 0,
  748. isDependedOn: 0,
  749. hasRedundancy: 0,
  750. degradPrio: 0,
  751. dependsOn: 2,
  752. isNonSync: 0,
  753. },
  754. });
  755. offset = index + size;
  756. }
  757. }
  758. if (audioConfig.byteLength == 0 || firstPts == null) {
  759. throw new shaka.util.Error(
  760. shaka.util.Error.Severity.CRITICAL,
  761. shaka.util.Error.Category.MEDIA,
  762. shaka.util.Error.Code.TRANSMUXING_FAILED,
  763. reference ? reference.getUris()[0] : null);
  764. }
  765. stream.audioSamplingRate = opusMetadata.sampleRate;
  766. stream.channelsCount = opusMetadata.channelCount;
  767. /** @type {number} */
  768. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  769. return {
  770. id: stream.id,
  771. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  772. codecs: 'opus',
  773. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  774. timescale: sampleRate,
  775. duration: duration,
  776. videoNalus: [],
  777. audioConfig: audioConfig,
  778. videoConfig: new Uint8Array([]),
  779. hSpacing: 0,
  780. vSpacing: 0,
  781. data: {
  782. sequenceNumber: this.frameIndex_,
  783. baseMediaDecodeTime: baseMediaDecodeTime,
  784. samples: samples,
  785. },
  786. stream: stream,
  787. };
  788. }
  789. /**
  790. * @param {shaka.util.TsParser} tsParser
  791. * @param {shaka.extern.Stream} stream
  792. * @param {number} duration
  793. * @param {?shaka.media.SegmentReference} reference
  794. * @return {shaka.util.Mp4Generator.StreamInfo}
  795. * @private
  796. */
  797. getAvcStreamInfo_(tsParser, stream, duration, reference) {
  798. const H264 = shaka.transmuxer.H264;
  799. const timescale = shaka.util.TsParser.Timescale;
  800. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  801. const samples = [];
  802. /** @type {?number} */
  803. let baseMediaDecodeTime = null;
  804. const videoData = tsParser.getVideoData();
  805. const videoSamples = H264.getVideoSamples(videoData);
  806. if (!videoSamples.length) {
  807. throw new shaka.util.Error(
  808. shaka.util.Error.Severity.CRITICAL,
  809. shaka.util.Error.Category.MEDIA,
  810. shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA,
  811. reference ? reference.getUris()[0] : null);
  812. }
  813. for (let i = 0; i < videoSamples.length; i++) {
  814. const videoSample = videoSamples[i];
  815. if (baseMediaDecodeTime == null) {
  816. baseMediaDecodeTime = videoSample.dts;
  817. }
  818. let duration;
  819. if (i + 1 < videoSamples.length) {
  820. duration = (videoSamples[i + 1].dts || 0) - (videoSample.dts || 0);
  821. } else if (videoSamples.length > 1) {
  822. duration = (videoSample.dts || 0) - (videoSamples[i - 1].dts || 0);
  823. } else {
  824. duration = (reference.endTime - reference.startTime) * timescale;
  825. }
  826. samples.push({
  827. data: videoSample.data,
  828. size: videoSample.data.byteLength,
  829. duration: duration,
  830. cts: Math.round((videoSample.pts || 0) - (videoSample.dts || 0)),
  831. flags: {
  832. isLeading: 0,
  833. isDependedOn: 0,
  834. hasRedundancy: 0,
  835. degradPrio: 0,
  836. dependsOn: videoSample.isKeyframe ? 2 : 1,
  837. isNonSync: videoSample.isKeyframe ? 0 : 1,
  838. },
  839. });
  840. }
  841. const nalus = [];
  842. for (const pes of videoData) {
  843. nalus.push(...pes.nalus);
  844. }
  845. const info = H264.parseInfo(nalus);
  846. if (!info || baseMediaDecodeTime == null) {
  847. throw new shaka.util.Error(
  848. shaka.util.Error.Severity.CRITICAL,
  849. shaka.util.Error.Category.MEDIA,
  850. shaka.util.Error.Code.TRANSMUXING_FAILED,
  851. reference ? reference.getUris()[0] : null);
  852. }
  853. stream.height = info.height;
  854. stream.width = info.width;
  855. return {
  856. id: stream.id,
  857. type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
  858. codecs: 'avc1',
  859. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  860. timescale: timescale,
  861. duration: duration,
  862. videoNalus: [],
  863. audioConfig: new Uint8Array([]),
  864. videoConfig: info.videoConfig,
  865. hSpacing: info.hSpacing,
  866. vSpacing: info.vSpacing,
  867. data: {
  868. sequenceNumber: this.frameIndex_,
  869. baseMediaDecodeTime: baseMediaDecodeTime,
  870. samples: samples,
  871. },
  872. stream: stream,
  873. };
  874. }
  875. /**
  876. * @param {shaka.util.TsParser} tsParser
  877. * @param {shaka.extern.Stream} stream
  878. * @param {number} duration
  879. * @param {?shaka.media.SegmentReference} reference
  880. * @return {shaka.util.Mp4Generator.StreamInfo}
  881. * @private
  882. */
  883. getHvcStreamInfo_(tsParser, stream, duration, reference) {
  884. const H265 = shaka.transmuxer.H265;
  885. const timescale = shaka.util.TsParser.Timescale;
  886. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  887. const samples = [];
  888. /** @type {?number} */
  889. let baseMediaDecodeTime = null;
  890. const nalus = [];
  891. const videoData = tsParser.getVideoData();
  892. if (!videoData.length) {
  893. throw new shaka.util.Error(
  894. shaka.util.Error.Severity.CRITICAL,
  895. shaka.util.Error.Category.MEDIA,
  896. shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA,
  897. reference ? reference.getUris()[0] : null);
  898. }
  899. for (let i = 0; i < videoData.length; i++) {
  900. const pes = videoData[i];
  901. const dataNalus = pes.nalus;
  902. nalus.push(...dataNalus);
  903. const frame = H265.parseFrame(dataNalus);
  904. if (!frame) {
  905. continue;
  906. }
  907. if (baseMediaDecodeTime == null && pes.dts != null) {
  908. baseMediaDecodeTime = pes.dts;
  909. }
  910. let duration;
  911. if (i + 1 < videoData.length) {
  912. duration = (videoData[i + 1].dts || 0) - (pes.dts || 0);
  913. } else if (videoData.length > 1) {
  914. duration = (pes.dts || 0) - (videoData[i - 1].dts || 0);
  915. } else {
  916. duration = (reference.endTime - reference.startTime) * timescale;
  917. }
  918. samples.push({
  919. data: frame.data,
  920. size: frame.data.byteLength,
  921. duration: duration,
  922. cts: Math.round((pes.pts || 0) - (pes.dts || 0)),
  923. flags: {
  924. isLeading: 0,
  925. isDependedOn: 0,
  926. hasRedundancy: 0,
  927. degradPrio: 0,
  928. dependsOn: frame.isKeyframe ? 2 : 1,
  929. isNonSync: frame.isKeyframe ? 0 : 1,
  930. },
  931. });
  932. }
  933. const info = H265.parseInfo(nalus);
  934. if (!info || baseMediaDecodeTime == null) {
  935. throw new shaka.util.Error(
  936. shaka.util.Error.Severity.CRITICAL,
  937. shaka.util.Error.Category.MEDIA,
  938. shaka.util.Error.Code.TRANSMUXING_FAILED,
  939. reference ? reference.getUris()[0] : null);
  940. }
  941. stream.height = info.height;
  942. stream.width = info.width;
  943. return {
  944. id: stream.id,
  945. type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
  946. codecs: 'hvc1',
  947. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  948. timescale: timescale,
  949. duration: duration,
  950. videoNalus: [],
  951. audioConfig: new Uint8Array([]),
  952. videoConfig: info.videoConfig,
  953. hSpacing: info.hSpacing,
  954. vSpacing: info.vSpacing,
  955. data: {
  956. sequenceNumber: this.frameIndex_,
  957. baseMediaDecodeTime: baseMediaDecodeTime,
  958. samples: samples,
  959. },
  960. stream: stream,
  961. };
  962. }
  963. };
  964. /**
  965. * Supported audio codecs.
  966. *
  967. * @private
  968. * @const {!Array<string>}
  969. */
  970. shaka.transmuxer.TsTransmuxer.SUPPORTED_AUDIO_CODECS_ = [
  971. 'aac',
  972. 'ac-3',
  973. 'ec-3',
  974. 'mp3',
  975. 'opus',
  976. ];
  977. /**
  978. * Supported audio codecs.
  979. *
  980. * @private
  981. * @const {!Array<string>}
  982. */
  983. shaka.transmuxer.TsTransmuxer.SUPPORTED_VIDEO_CODECS_ = [
  984. 'avc',
  985. 'hevc',
  986. ];
  987. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  988. 'video/mp2t',
  989. () => new shaka.transmuxer.TsTransmuxer('video/mp2t'),
  990. shaka.transmuxer.TransmuxerEngine.PluginPriority.PREFERRED);