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