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