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