Source: lib/transmuxer/ec3_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.Ec3Transmuxer');
  7. goog.require('shaka.media.Capabilities');
  8. goog.require('shaka.transmuxer.Ec3');
  9. goog.require('shaka.transmuxer.TransmuxerEngine');
  10. goog.require('shaka.util.BufferUtils');
  11. goog.require('shaka.util.Error');
  12. goog.require('shaka.util.Id3Utils');
  13. goog.require('shaka.util.ManifestParserUtils');
  14. goog.require('shaka.util.Mp4Generator');
  15. goog.require('shaka.util.Uint8ArrayUtils');
  16. /**
  17. * @implements {shaka.extern.Transmuxer}
  18. * @export
  19. */
  20. shaka.transmuxer.Ec3Transmuxer = class {
  21. /**
  22. * @param {string} mimeType
  23. */
  24. constructor(mimeType) {
  25. /** @private {string} */
  26. this.originalMimeType_ = mimeType;
  27. /** @private {number} */
  28. this.frameIndex_ = 0;
  29. /** @private {!Map.<string, !Uint8Array>} */
  30. this.initSegments = new Map();
  31. }
  32. /**
  33. * @override
  34. * @export
  35. */
  36. destroy() {
  37. this.initSegments.clear();
  38. }
  39. /**
  40. * Check if the mime type and the content type is supported.
  41. * @param {string} mimeType
  42. * @param {string=} contentType
  43. * @return {boolean}
  44. * @override
  45. * @export
  46. */
  47. isSupported(mimeType, contentType) {
  48. const Capabilities = shaka.media.Capabilities;
  49. if (!this.isEc3Container_(mimeType)) {
  50. return false;
  51. }
  52. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  53. return Capabilities.isTypeSupported(
  54. this.convertCodecs(ContentType.AUDIO, mimeType));
  55. }
  56. /**
  57. * Check if the mimetype is 'audio/ec3'.
  58. * @param {string} mimeType
  59. * @return {boolean}
  60. * @private
  61. */
  62. isEc3Container_(mimeType) {
  63. return mimeType.toLowerCase().split(';')[0] == 'audio/ec3';
  64. }
  65. /**
  66. * @override
  67. * @export
  68. */
  69. convertCodecs(contentType, mimeType) {
  70. if (this.isEc3Container_(mimeType)) {
  71. return 'audio/mp4; codecs="ec-3"';
  72. }
  73. return mimeType;
  74. }
  75. /**
  76. * @override
  77. * @export
  78. */
  79. getOriginalMimeType() {
  80. return this.originalMimeType_;
  81. }
  82. /**
  83. * @override
  84. * @export
  85. */
  86. transmux(data, stream, reference, duration) {
  87. const Ec3 = shaka.transmuxer.Ec3;
  88. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  89. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  90. const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
  91. let offset = id3Data.length;
  92. for (; offset < uint8ArrayData.length; offset++) {
  93. if (Ec3.probe(uint8ArrayData, offset)) {
  94. break;
  95. }
  96. }
  97. let timestamp = reference.endTime * 1000;
  98. const frames = shaka.util.Id3Utils.getID3Frames(id3Data);
  99. if (frames.length && reference) {
  100. const metadataTimestamp = frames.find((frame) => {
  101. return frame.description ===
  102. 'com.apple.streaming.transportStreamTimestamp';
  103. });
  104. if (metadataTimestamp) {
  105. timestamp = /** @type {!number} */(metadataTimestamp.data);
  106. }
  107. }
  108. /** @type {number} */
  109. let sampleRate = 0;
  110. /** @type {!Uint8Array} */
  111. let audioConfig = new Uint8Array([]);
  112. /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
  113. const samples = [];
  114. while (offset < uint8ArrayData.length) {
  115. const frame = Ec3.parseFrame(uint8ArrayData, offset);
  116. if (!frame) {
  117. return Promise.reject(new shaka.util.Error(
  118. shaka.util.Error.Severity.CRITICAL,
  119. shaka.util.Error.Category.MEDIA,
  120. shaka.util.Error.Code.TRANSMUXING_FAILED,
  121. reference ? reference.getUris()[0] : null));
  122. }
  123. stream.audioSamplingRate = frame.sampleRate;
  124. stream.channelsCount = frame.channelCount;
  125. sampleRate = frame.sampleRate;
  126. audioConfig = frame.audioConfig;
  127. const frameData = uint8ArrayData.subarray(
  128. offset, offset + frame.frameLength);
  129. samples.push({
  130. data: frameData,
  131. size: frame.frameLength,
  132. duration: Ec3.EC3_SAMPLES_PER_FRAME,
  133. cts: 0,
  134. flags: {
  135. isLeading: 0,
  136. isDependedOn: 0,
  137. hasRedundancy: 0,
  138. degradPrio: 0,
  139. dependsOn: 2,
  140. isNonSync: 0,
  141. },
  142. });
  143. offset += frame.frameLength;
  144. }
  145. /** @type {number} */
  146. const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
  147. /** @type {shaka.util.Mp4Generator.StreamInfo} */
  148. const streamInfo = {
  149. id: stream.id,
  150. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  151. codecs: 'ec-3',
  152. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  153. timescale: sampleRate,
  154. duration: duration,
  155. videoNalus: [],
  156. audioConfig: audioConfig,
  157. videoConfig: new Uint8Array([]),
  158. hSpacing: 0,
  159. vSpacing: 0,
  160. data: {
  161. sequenceNumber: this.frameIndex_,
  162. baseMediaDecodeTime: baseMediaDecodeTime,
  163. samples: samples,
  164. },
  165. stream: stream,
  166. };
  167. const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
  168. let initSegment;
  169. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  170. if (!this.initSegments.has(initSegmentKey)) {
  171. initSegment = mp4Generator.initSegment();
  172. this.initSegments.set(initSegmentKey, initSegment);
  173. } else {
  174. initSegment = this.initSegments.get(initSegmentKey);
  175. }
  176. const segmentData = mp4Generator.segmentData();
  177. this.frameIndex_++;
  178. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  179. return Promise.resolve(transmuxData);
  180. }
  181. };
  182. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  183. 'audio/ec3',
  184. () => new shaka.transmuxer.Ec3Transmuxer('audio/ec3'),
  185. shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK);