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. this.tsParser_.setDiscontinuitySequence(reference.discontinuitySequence);
  191. const tsParser = this.tsParser_.parse(uint8ArrayData);
  192. const streamInfos = [];
  193. const codecs = tsParser.getCodecs();
  194. try {
  195. let streamInfo = null;
  196. if (contentType == ContentType.VIDEO) {
  197. switch (codecs.video) {
  198. case 'avc':
  199. streamInfo =
  200. this.getAvcStreamInfo_(tsParser, stream, duration, reference);
  201. break;
  202. case 'hvc':
  203. streamInfo =
  204. this.getHvcStreamInfo_(tsParser, stream, duration, reference);
  205. break;
  206. }
  207. if (streamInfo) {
  208. streamInfos.push(streamInfo);
  209. streamInfo = null;
  210. }
  211. }
  212. if (contentType == ContentType.AUDIO) {
  213. switch (codecs.audio) {
  214. case 'aac':
  215. streamInfo =
  216. this.getAacStreamInfo_(tsParser, stream, duration, reference);
  217. break;
  218. case 'ac3':
  219. streamInfo =
  220. this.getAc3StreamInfo_(tsParser, stream, duration, reference);
  221. break;
  222. case 'ec3':
  223. streamInfo =
  224. this.getEc3StreamInfo_(tsParser, stream, duration, reference);
  225. break;
  226. case 'mp3':
  227. streamInfo =
  228. this.getMp3StreamInfo_(tsParser, stream, duration, reference);
  229. break;
  230. case 'opus':
  231. streamInfo =
  232. this.getOpusStreamInfo_(tsParser, stream, duration, reference);
  233. break;
  234. }
  235. if (streamInfo) {
  236. streamInfos.push(streamInfo);
  237. streamInfo = null;
  238. }
  239. }
  240. } catch (e) {
  241. if (e && e.code == shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA) {
  242. return Promise.resolve(new Uint8Array([]));
  243. }
  244. return Promise.reject(e);
  245. }
  246. if (!streamInfos.length) {
  247. return Promise.reject(new shaka.util.Error(
  248. shaka.util.Error.Severity.CRITICAL,
  249. shaka.util.Error.Category.MEDIA,
  250. shaka.util.Error.Code.TRANSMUXING_FAILED,
  251. reference ? reference.getUris()[0] : null));
  252. }
  253. const mp4Generator = new shaka.util.Mp4Generator(streamInfos);
  254. let initSegment;
  255. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  256. if (!this.initSegments.has(initSegmentKey)) {
  257. initSegment = mp4Generator.initSegment();
  258. this.initSegments.set(initSegmentKey, initSegment);
  259. } else {
  260. initSegment = this.initSegments.get(initSegmentKey);
  261. }
  262. const appendInitSegment = this.lastInitSegment_ !== initSegment;
  263. const segmentData = mp4Generator.segmentData();
  264. this.lastInitSegment_ = initSegment;
  265. this.frameIndex_++;
  266. if (appendInitSegment) {
  267. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  268. return Promise.resolve(transmuxData);
  269. } else {
  270. return Promise.resolve(segmentData);
  271. }
  272. }
  273. /**
  274. * @param {shaka.util.TsParser} tsParser
  275. * @param {shaka.extern.Stream} stream
  276. * @param {number} duration
  277. * @param {?shaka.media.SegmentReference} reference
  278. * @return {shaka.util.Mp4Generator.StreamInfo}
  279. * @private
  280. */
  281. getAacStreamInfo_(tsParser, stream, duration, reference) {
  282. const ADTS = shaka.transmuxer.ADTS;
  283. const timescale = shaka.util.TsParser.Timescale;
  284. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  285. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  286. const samples = [];
  287. let info;
  288. let firstPts = null;
  289. /** @type {?number} */
  290. let nextStartOffset = null;
  291. /** @type {?Uint8Array} */
  292. let overflowBytes = null;
  293. for (const audioData of tsParser.getAudioData()) {
  294. let data = audioData.data;
  295. if (!data) {
  296. continue;
  297. }
  298. let offset = 0;
  299. if (nextStartOffset == -1 && overflowBytes) {
  300. data = Uint8ArrayUtils.concat(overflowBytes, audioData.data);
  301. nextStartOffset = null;
  302. } else if (nextStartOffset != null && overflowBytes) {
  303. offset = Math.max(0, nextStartOffset);
  304. const missingFrameData =
  305. Uint8ArrayUtils.concat(overflowBytes, data.subarray(0, offset));
  306. samples.push({
  307. data: missingFrameData,
  308. size: missingFrameData.byteLength,
  309. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  310. cts: 0,
  311. flags: {
  312. isLeading: 0,
  313. isDependedOn: 0,
  314. hasRedundancy: 0,
  315. degradPrio: 0,
  316. dependsOn: 2,
  317. isNonSync: 0,
  318. },
  319. });
  320. overflowBytes = null;
  321. nextStartOffset = null;
  322. }
  323. info = ADTS.parseInfo(data, offset);
  324. if (!info) {
  325. throw new shaka.util.Error(
  326. shaka.util.Error.Severity.CRITICAL,
  327. shaka.util.Error.Category.MEDIA,
  328. shaka.util.Error.Code.TRANSMUXING_FAILED,
  329. reference ? reference.getUris()[0] : null);
  330. }
  331. stream.audioSamplingRate = info.sampleRate;
  332. stream.channelsCount = info.channelCount;
  333. if (firstPts == null && audioData.pts !== null) {
  334. firstPts = audioData.pts;
  335. }
  336. while (offset < data.length) {
  337. const header = ADTS.parseHeader(data, offset);
  338. if (!header) {
  339. overflowBytes = data.subarray(offset, data.length);
  340. nextStartOffset = -1;
  341. break;
  342. }
  343. const length = header.headerLength + header.frameLength;
  344. nextStartOffset = Math.max(0, offset + length - data.length);
  345. if (nextStartOffset != 0) {
  346. overflowBytes = data.subarray(
  347. offset + header.headerLength, offset + length);
  348. } else if (offset + length <= data.length) {
  349. const frameData = data.subarray(
  350. offset + header.headerLength, offset + length);
  351. samples.push({
  352. data: frameData,
  353. size: header.frameLength,
  354. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  355. cts: 0,
  356. flags: {
  357. isLeading: 0,
  358. isDependedOn: 0,
  359. hasRedundancy: 0,
  360. degradPrio: 0,
  361. dependsOn: 2,
  362. isNonSync: 0,
  363. },
  364. });
  365. }
  366. offset += length;
  367. }
  368. }
  369. if (!info || firstPts == null) {
  370. if (!tsParser.getVideoData().length) {
  371. throw new shaka.util.Error(
  372. shaka.util.Error.Severity.CRITICAL,
  373. shaka.util.Error.Category.MEDIA,
  374. shaka.util.Error.Code.TRANSMUXING_FAILED,
  375. reference ? reference.getUris()[0] : null);
  376. }
  377. firstPts = reference.startTime * timescale;
  378. const allCodecs = shaka.util.MimeUtils.splitCodecs(stream.codecs);
  379. const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  380. shaka.util.ManifestParserUtils.ContentType.AUDIO, allCodecs);
  381. if (!audioCodec || !stream.channelsCount || !stream.audioSamplingRate) {
  382. throw new shaka.util.Error(
  383. shaka.util.Error.Severity.CRITICAL,
  384. shaka.util.Error.Category.MEDIA,
  385. shaka.util.Error.Code.TRANSMUXING_FAILED,
  386. reference ? reference.getUris()[0] : null);
  387. }
  388. info = {
  389. sampleRate: stream.audioSamplingRate,
  390. channelCount: stream.channelsCount,
  391. codec: audioCodec,
  392. };
  393. const silenceFrame =
  394. ADTS.getSilentFrame(audioCodec, stream.channelsCount);
  395. if (!silenceFrame) {
  396. throw new shaka.util.Error(
  397. shaka.util.Error.Severity.CRITICAL,
  398. shaka.util.Error.Category.MEDIA,
  399. shaka.util.Error.Code.TRANSMUXING_FAILED,
  400. reference ? reference.getUris()[0] : null);
  401. }
  402. const segmentDuration =
  403. (reference.endTime - reference.startTime) * timescale;
  404. const finalPTs = firstPts + segmentDuration;
  405. let currentPts = firstPts;
  406. while (currentPts < finalPTs) {
  407. samples.push({
  408. data: silenceFrame,
  409. size: silenceFrame.byteLength,
  410. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  411. cts: 0,
  412. flags: {
  413. isLeading: 0,
  414. isDependedOn: 0,
  415. hasRedundancy: 0,
  416. degradPrio: 0,
  417. dependsOn: 2,
  418. isNonSync: 0,
  419. },
  420. });
  421. currentPts += ADTS.AAC_SAMPLES_PER_FRAME / info.sampleRate * timescale;
  422. }
  423. }
  424. /** @type {number} */
  425. const sampleRate = info.sampleRate;
  426. /** @type {number} */
  427. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  428. return {
  429. id: stream.id,
  430. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  431. codecs: info.codec,
  432. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  433. timescale: sampleRate,
  434. duration: duration,
  435. videoNalus: [],
  436. audioConfig: new Uint8Array([]),
  437. videoConfig: new Uint8Array([]),
  438. hSpacing: 0,
  439. vSpacing: 0,
  440. data: {
  441. sequenceNumber: this.frameIndex_,
  442. baseMediaDecodeTime: baseMediaDecodeTime,
  443. samples: samples,
  444. },
  445. stream: stream,
  446. };
  447. }
  448. /**
  449. * @param {shaka.util.TsParser} tsParser
  450. * @param {shaka.extern.Stream} stream
  451. * @param {number} duration
  452. * @param {?shaka.media.SegmentReference} reference
  453. * @return {shaka.util.Mp4Generator.StreamInfo}
  454. * @private
  455. */
  456. getAc3StreamInfo_(tsParser, stream, duration, reference) {
  457. const Ac3 = shaka.transmuxer.Ac3;
  458. const timescale = shaka.util.TsParser.Timescale;
  459. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  460. const samples = [];
  461. /** @type {number} */
  462. let sampleRate = 0;
  463. /** @type {!Uint8Array} */
  464. let audioConfig = new Uint8Array([]);
  465. let firstPts = null;
  466. for (const audioData of tsParser.getAudioData()) {
  467. const data = audioData.data;
  468. if (firstPts == null && audioData.pts !== null) {
  469. firstPts = audioData.pts;
  470. }
  471. let offset = 0;
  472. while (offset < data.length) {
  473. const frame = Ac3.parseFrame(data, offset);
  474. if (!frame) {
  475. offset++;
  476. continue;
  477. }
  478. stream.audioSamplingRate = frame.sampleRate;
  479. stream.channelsCount = frame.channelCount;
  480. sampleRate = frame.sampleRate;
  481. audioConfig = frame.audioConfig;
  482. const frameData = data.subarray(
  483. offset, offset + frame.frameLength);
  484. samples.push({
  485. data: frameData,
  486. size: frame.frameLength,
  487. duration: Ac3.AC3_SAMPLES_PER_FRAME,
  488. cts: 0,
  489. flags: {
  490. isLeading: 0,
  491. isDependedOn: 0,
  492. hasRedundancy: 0,
  493. degradPrio: 0,
  494. dependsOn: 2,
  495. isNonSync: 0,
  496. },
  497. });
  498. offset += frame.frameLength;
  499. }
  500. }
  501. if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
  502. throw new shaka.util.Error(
  503. shaka.util.Error.Severity.CRITICAL,
  504. shaka.util.Error.Category.MEDIA,
  505. shaka.util.Error.Code.TRANSMUXING_FAILED,
  506. reference ? reference.getUris()[0] : null);
  507. }
  508. /** @type {number} */
  509. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  510. return {
  511. id: stream.id,
  512. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  513. codecs: 'ac-3',
  514. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  515. timescale: sampleRate,
  516. duration: duration,
  517. videoNalus: [],
  518. audioConfig: audioConfig,
  519. videoConfig: new Uint8Array([]),
  520. hSpacing: 0,
  521. vSpacing: 0,
  522. data: {
  523. sequenceNumber: this.frameIndex_,
  524. baseMediaDecodeTime: baseMediaDecodeTime,
  525. samples: samples,
  526. },
  527. stream: stream,
  528. };
  529. }
  530. /**
  531. * @param {shaka.util.TsParser} tsParser
  532. * @param {shaka.extern.Stream} stream
  533. * @param {number} duration
  534. * @param {?shaka.media.SegmentReference} reference
  535. * @return {shaka.util.Mp4Generator.StreamInfo}
  536. * @private
  537. */
  538. getEc3StreamInfo_(tsParser, stream, duration, reference) {
  539. const Ec3 = shaka.transmuxer.Ec3;
  540. const timescale = shaka.util.TsParser.Timescale;
  541. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  542. const samples = [];
  543. /** @type {number} */
  544. let sampleRate = 0;
  545. /** @type {!Uint8Array} */
  546. let audioConfig = new Uint8Array([]);
  547. let firstPts = null;
  548. for (const audioData of tsParser.getAudioData()) {
  549. const data = audioData.data;
  550. if (firstPts == null && audioData.pts !== null) {
  551. firstPts = audioData.pts;
  552. }
  553. let offset = 0;
  554. while (offset < data.length) {
  555. const frame = Ec3.parseFrame(data, offset);
  556. if (!frame) {
  557. offset++;
  558. continue;
  559. }
  560. stream.audioSamplingRate = frame.sampleRate;
  561. stream.channelsCount = frame.channelCount;
  562. sampleRate = frame.sampleRate;
  563. audioConfig = frame.audioConfig;
  564. const frameData = data.subarray(
  565. offset, offset + frame.frameLength);
  566. samples.push({
  567. data: frameData,
  568. size: frame.frameLength,
  569. duration: Ec3.EC3_SAMPLES_PER_FRAME,
  570. cts: 0,
  571. flags: {
  572. isLeading: 0,
  573. isDependedOn: 0,
  574. hasRedundancy: 0,
  575. degradPrio: 0,
  576. dependsOn: 2,
  577. isNonSync: 0,
  578. },
  579. });
  580. offset += frame.frameLength;
  581. }
  582. }
  583. if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
  584. throw new shaka.util.Error(
  585. shaka.util.Error.Severity.CRITICAL,
  586. shaka.util.Error.Category.MEDIA,
  587. shaka.util.Error.Code.TRANSMUXING_FAILED,
  588. reference ? reference.getUris()[0] : null);
  589. }
  590. /** @type {number} */
  591. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  592. return {
  593. id: stream.id,
  594. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  595. codecs: 'ec-3',
  596. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  597. timescale: sampleRate,
  598. duration: duration,
  599. videoNalus: [],
  600. audioConfig: audioConfig,
  601. videoConfig: new Uint8Array([]),
  602. hSpacing: 0,
  603. vSpacing: 0,
  604. data: {
  605. sequenceNumber: this.frameIndex_,
  606. baseMediaDecodeTime: baseMediaDecodeTime,
  607. samples: samples,
  608. },
  609. stream: stream,
  610. };
  611. }
  612. /**
  613. * @param {shaka.util.TsParser} tsParser
  614. * @param {shaka.extern.Stream} stream
  615. * @param {number} duration
  616. * @param {?shaka.media.SegmentReference} reference
  617. * @return {shaka.util.Mp4Generator.StreamInfo}
  618. * @private
  619. */
  620. getMp3StreamInfo_(tsParser, stream, duration, reference) {
  621. const MpegAudio = shaka.transmuxer.MpegAudio;
  622. const timescale = shaka.util.TsParser.Timescale;
  623. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  624. const samples = [];
  625. let firstHeader;
  626. let firstPts = null;
  627. for (const audioData of tsParser.getAudioData()) {
  628. const data = audioData.data;
  629. if (!data) {
  630. continue;
  631. }
  632. if (firstPts == null && audioData.pts !== null) {
  633. firstPts = audioData.pts;
  634. }
  635. let offset = 0;
  636. while (offset < data.length) {
  637. const header = MpegAudio.parseHeader(data, offset);
  638. if (!header) {
  639. offset++;
  640. continue;
  641. }
  642. if (!firstHeader) {
  643. firstHeader = header;
  644. }
  645. if (offset + header.frameLength <= data.length) {
  646. samples.push({
  647. data: data.subarray(offset, offset + header.frameLength),
  648. size: header.frameLength,
  649. duration: MpegAudio.MPEG_AUDIO_SAMPLE_PER_FRAME,
  650. cts: 0,
  651. flags: {
  652. isLeading: 0,
  653. isDependedOn: 0,
  654. hasRedundancy: 0,
  655. degradPrio: 0,
  656. dependsOn: 2,
  657. isNonSync: 0,
  658. },
  659. });
  660. }
  661. offset += header.frameLength;
  662. }
  663. }
  664. if (!firstHeader || firstPts == null) {
  665. throw new shaka.util.Error(
  666. shaka.util.Error.Severity.CRITICAL,
  667. shaka.util.Error.Category.MEDIA,
  668. shaka.util.Error.Code.TRANSMUXING_FAILED,
  669. reference ? reference.getUris()[0] : null);
  670. }
  671. /** @type {number} */
  672. const sampleRate = firstHeader.sampleRate;
  673. /** @type {number} */
  674. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  675. return {
  676. id: stream.id,
  677. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  678. codecs: 'mp3',
  679. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  680. timescale: sampleRate,
  681. duration: duration,
  682. videoNalus: [],
  683. audioConfig: new Uint8Array([]),
  684. videoConfig: new Uint8Array([]),
  685. hSpacing: 0,
  686. vSpacing: 0,
  687. data: {
  688. sequenceNumber: this.frameIndex_,
  689. baseMediaDecodeTime: baseMediaDecodeTime,
  690. samples: samples,
  691. },
  692. stream: stream,
  693. };
  694. }
  695. /**
  696. * @param {shaka.util.TsParser} tsParser
  697. * @param {shaka.extern.Stream} stream
  698. * @param {number} duration
  699. * @param {?shaka.media.SegmentReference} reference
  700. * @return {shaka.util.Mp4Generator.StreamInfo}
  701. * @private
  702. */
  703. getOpusStreamInfo_(tsParser, stream, duration, reference) {
  704. const Opus = shaka.transmuxer.Opus;
  705. const timescale = shaka.util.TsParser.Timescale;
  706. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  707. const samples = [];
  708. let firstPts = null;
  709. /** @type {?shaka.util.TsParser.OpusMetadata} */
  710. const opusMetadata = tsParser.getOpusMetadata();
  711. if (!opusMetadata) {
  712. throw new shaka.util.Error(
  713. shaka.util.Error.Severity.CRITICAL,
  714. shaka.util.Error.Category.MEDIA,
  715. shaka.util.Error.Code.TRANSMUXING_FAILED,
  716. reference ? reference.getUris()[0] : null);
  717. }
  718. /** @type {!Uint8Array} */
  719. const audioConfig = Opus.getAudioConfig(opusMetadata);
  720. /** @type {number} */
  721. const sampleRate = opusMetadata.sampleRate;
  722. for (const audioData of tsParser.getAudioData()) {
  723. const data = audioData.data;
  724. if (firstPts == null && audioData.pts !== null) {
  725. firstPts = audioData.pts;
  726. }
  727. let offset = 0;
  728. while (offset < data.length) {
  729. const opusPendingTrimStart = (data[offset + 1] & 0x10) !== 0;
  730. const trimEnd = (data[offset + 1] & 0x08) !== 0;
  731. let index = offset + 2;
  732. let size = 0;
  733. while (data[index] === 0xFF) {
  734. size += 255;
  735. index += 1;
  736. }
  737. size += data[index];
  738. index += 1;
  739. index += opusPendingTrimStart ? 2 : 0;
  740. index += trimEnd ? 2 : 0;
  741. const sample = data.slice(index, index + size);
  742. samples.push({
  743. data: sample,
  744. size: sample.byteLength,
  745. duration: Opus.OPUS_AUDIO_SAMPLE_PER_FRAME,
  746. cts: 0,
  747. flags: {
  748. isLeading: 0,
  749. isDependedOn: 0,
  750. hasRedundancy: 0,
  751. degradPrio: 0,
  752. dependsOn: 2,
  753. isNonSync: 0,
  754. },
  755. });
  756. offset = index + size;
  757. }
  758. }
  759. if (audioConfig.byteLength == 0 || firstPts == null) {
  760. throw new shaka.util.Error(
  761. shaka.util.Error.Severity.CRITICAL,
  762. shaka.util.Error.Category.MEDIA,
  763. shaka.util.Error.Code.TRANSMUXING_FAILED,
  764. reference ? reference.getUris()[0] : null);
  765. }
  766. stream.audioSamplingRate = opusMetadata.sampleRate;
  767. stream.channelsCount = opusMetadata.channelCount;
  768. /** @type {number} */
  769. const baseMediaDecodeTime = firstPts / timescale * sampleRate;
  770. return {
  771. id: stream.id,
  772. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  773. codecs: 'opus',
  774. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  775. timescale: sampleRate,
  776. duration: duration,
  777. videoNalus: [],
  778. audioConfig: audioConfig,
  779. videoConfig: new Uint8Array([]),
  780. hSpacing: 0,
  781. vSpacing: 0,
  782. data: {
  783. sequenceNumber: this.frameIndex_,
  784. baseMediaDecodeTime: baseMediaDecodeTime,
  785. samples: samples,
  786. },
  787. stream: stream,
  788. };
  789. }
  790. /**
  791. * @param {shaka.util.TsParser} tsParser
  792. * @param {shaka.extern.Stream} stream
  793. * @param {number} duration
  794. * @param {?shaka.media.SegmentReference} reference
  795. * @return {shaka.util.Mp4Generator.StreamInfo}
  796. * @private
  797. */
  798. getAvcStreamInfo_(tsParser, stream, duration, reference) {
  799. const H264 = shaka.transmuxer.H264;
  800. const timescale = shaka.util.TsParser.Timescale;
  801. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  802. const samples = [];
  803. /** @type {?number} */
  804. let baseMediaDecodeTime = null;
  805. const videoData = tsParser.getVideoData();
  806. const videoSamples = H264.getVideoSamples(videoData);
  807. if (!videoSamples.length) {
  808. throw new shaka.util.Error(
  809. shaka.util.Error.Severity.CRITICAL,
  810. shaka.util.Error.Category.MEDIA,
  811. shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA,
  812. reference ? reference.getUris()[0] : null);
  813. }
  814. for (let i = 0; i < videoSamples.length; i++) {
  815. const videoSample = videoSamples[i];
  816. if (baseMediaDecodeTime == null) {
  817. baseMediaDecodeTime = videoSample.dts;
  818. }
  819. let duration;
  820. if (i + 1 < videoSamples.length) {
  821. duration = (videoSamples[i + 1].dts || 0) - (videoSample.dts || 0);
  822. } else if (videoSamples.length > 1) {
  823. duration = (videoSample.dts || 0) - (videoSamples[i - 1].dts || 0);
  824. } else {
  825. duration = (reference.endTime - reference.startTime) * timescale;
  826. }
  827. samples.push({
  828. data: videoSample.data,
  829. size: videoSample.data.byteLength,
  830. duration: duration,
  831. cts: Math.round((videoSample.pts || 0) - (videoSample.dts || 0)),
  832. flags: {
  833. isLeading: 0,
  834. isDependedOn: 0,
  835. hasRedundancy: 0,
  836. degradPrio: 0,
  837. dependsOn: videoSample.isKeyframe ? 2 : 1,
  838. isNonSync: videoSample.isKeyframe ? 0 : 1,
  839. },
  840. });
  841. }
  842. const nalus = [];
  843. for (const pes of videoData) {
  844. nalus.push(...pes.nalus);
  845. }
  846. const info = H264.parseInfo(nalus);
  847. if (!info || baseMediaDecodeTime == null) {
  848. throw new shaka.util.Error(
  849. shaka.util.Error.Severity.CRITICAL,
  850. shaka.util.Error.Category.MEDIA,
  851. shaka.util.Error.Code.TRANSMUXING_FAILED,
  852. reference ? reference.getUris()[0] : null);
  853. }
  854. stream.height = info.height;
  855. stream.width = info.width;
  856. return {
  857. id: stream.id,
  858. type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
  859. codecs: 'avc1',
  860. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  861. timescale: timescale,
  862. duration: duration,
  863. videoNalus: [],
  864. audioConfig: new Uint8Array([]),
  865. videoConfig: info.videoConfig,
  866. hSpacing: info.hSpacing,
  867. vSpacing: info.vSpacing,
  868. data: {
  869. sequenceNumber: this.frameIndex_,
  870. baseMediaDecodeTime: baseMediaDecodeTime,
  871. samples: samples,
  872. },
  873. stream: stream,
  874. };
  875. }
  876. /**
  877. * @param {shaka.util.TsParser} tsParser
  878. * @param {shaka.extern.Stream} stream
  879. * @param {number} duration
  880. * @param {?shaka.media.SegmentReference} reference
  881. * @return {shaka.util.Mp4Generator.StreamInfo}
  882. * @private
  883. */
  884. getHvcStreamInfo_(tsParser, stream, duration, reference) {
  885. const H265 = shaka.transmuxer.H265;
  886. const timescale = shaka.util.TsParser.Timescale;
  887. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  888. const samples = [];
  889. /** @type {?number} */
  890. let baseMediaDecodeTime = null;
  891. const nalus = [];
  892. const videoData = tsParser.getVideoData();
  893. if (!videoData.length) {
  894. throw new shaka.util.Error(
  895. shaka.util.Error.Severity.CRITICAL,
  896. shaka.util.Error.Category.MEDIA,
  897. shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA,
  898. reference ? reference.getUris()[0] : null);
  899. }
  900. for (let i = 0; i < videoData.length; i++) {
  901. const pes = videoData[i];
  902. const dataNalus = pes.nalus;
  903. nalus.push(...dataNalus);
  904. const frame = H265.parseFrame(dataNalus);
  905. if (!frame) {
  906. continue;
  907. }
  908. if (baseMediaDecodeTime == null && pes.dts != null) {
  909. baseMediaDecodeTime = pes.dts;
  910. }
  911. let duration;
  912. if (i + 1 < videoData.length) {
  913. duration = (videoData[i + 1].dts || 0) - (pes.dts || 0);
  914. } else if (videoData.length > 1) {
  915. duration = (pes.dts || 0) - (videoData[i - 1].dts || 0);
  916. } else {
  917. duration = (reference.endTime - reference.startTime) * timescale;
  918. }
  919. samples.push({
  920. data: frame.data,
  921. size: frame.data.byteLength,
  922. duration: duration,
  923. cts: Math.round((pes.pts || 0) - (pes.dts || 0)),
  924. flags: {
  925. isLeading: 0,
  926. isDependedOn: 0,
  927. hasRedundancy: 0,
  928. degradPrio: 0,
  929. dependsOn: frame.isKeyframe ? 2 : 1,
  930. isNonSync: frame.isKeyframe ? 0 : 1,
  931. },
  932. });
  933. }
  934. const info = H265.parseInfo(nalus);
  935. if (!info || baseMediaDecodeTime == null) {
  936. throw new shaka.util.Error(
  937. shaka.util.Error.Severity.CRITICAL,
  938. shaka.util.Error.Category.MEDIA,
  939. shaka.util.Error.Code.TRANSMUXING_FAILED,
  940. reference ? reference.getUris()[0] : null);
  941. }
  942. stream.height = info.height;
  943. stream.width = info.width;
  944. return {
  945. id: stream.id,
  946. type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
  947. codecs: 'hvc1',
  948. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  949. timescale: timescale,
  950. duration: duration,
  951. videoNalus: [],
  952. audioConfig: new Uint8Array([]),
  953. videoConfig: info.videoConfig,
  954. hSpacing: info.hSpacing,
  955. vSpacing: info.vSpacing,
  956. data: {
  957. sequenceNumber: this.frameIndex_,
  958. baseMediaDecodeTime: baseMediaDecodeTime,
  959. samples: samples,
  960. },
  961. stream: stream,
  962. };
  963. }
  964. };
  965. /**
  966. * Supported audio codecs.
  967. *
  968. * @private
  969. * @const {!Array<string>}
  970. */
  971. shaka.transmuxer.TsTransmuxer.SUPPORTED_AUDIO_CODECS_ = [
  972. 'aac',
  973. 'ac-3',
  974. 'ec-3',
  975. 'mp3',
  976. 'opus',
  977. ];
  978. /**
  979. * Supported audio codecs.
  980. *
  981. * @private
  982. * @const {!Array<string>}
  983. */
  984. shaka.transmuxer.TsTransmuxer.SUPPORTED_VIDEO_CODECS_ = [
  985. 'avc',
  986. 'hevc',
  987. ];
  988. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  989. 'video/mp2t',
  990. () => new shaka.transmuxer.TsTransmuxer('video/mp2t'),
  991. shaka.transmuxer.TransmuxerEngine.PluginPriority.PREFERRED);