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