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.<number, !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.isAc3Container_(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/ac3'.
  58. * @param {string} mimeType
  59. * @return {boolean}
  60. * @private
  61. */
  62. isAc3Container_(mimeType) {
  63. return mimeType.toLowerCase().split(';')[0] == 'audio/ac3';
  64. }
  65. /**
  66. * @override
  67. * @export
  68. */
  69. convertCodecs(contentType, mimeType) {
  70. if (this.isAc3Container_(mimeType)) {
  71. return 'audio/mp4; codecs="ac-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 Ac3 = shaka.transmuxer.Ac3;
  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 (Ac3.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 = Ac3.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. }
  122. stream.audioSamplingRate = frame.sampleRate;
  123. stream.channelsCount = frame.channelCount;
  124. sampleRate = frame.sampleRate;
  125. audioConfig = frame.audioConfig;
  126. const frameData = uint8ArrayData.subarray(
  127. offset, offset + frame.frameLength);
  128. samples.push({
  129. data: frameData,
  130. size: frame.frameLength,
  131. duration: Ac3.AC3_SAMPLES_PER_FRAME,
  132. cts: 0,
  133. flags: {
  134. isLeading: 0,
  135. isDependedOn: 0,
  136. hasRedundancy: 0,
  137. degradPrio: 0,
  138. dependsOn: 2,
  139. isNonSync: 0,
  140. },
  141. });
  142. offset += frame.frameLength;
  143. }
  144. /** @type {number} */
  145. const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
  146. /** @type {shaka.util.Mp4Generator.StreamInfo} */
  147. const streamInfo = {
  148. id: stream.id,
  149. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  150. codecs: 'ac-3',
  151. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  152. timescale: sampleRate,
  153. duration: duration,
  154. videoNalus: [],
  155. audioConfig: audioConfig,
  156. videoConfig: new Uint8Array([]),
  157. data: {
  158. sequenceNumber: this.frameIndex_,
  159. baseMediaDecodeTime: baseMediaDecodeTime,
  160. samples: samples,
  161. },
  162. stream: stream,
  163. };
  164. const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
  165. let initSegment;
  166. if (!this.initSegments.has(stream.id)) {
  167. initSegment = mp4Generator.initSegment();
  168. this.initSegments.set(stream.id, initSegment);
  169. } else {
  170. initSegment = this.initSegments.get(stream.id);
  171. }
  172. const segmentData = mp4Generator.segmentData();
  173. this.frameIndex_++;
  174. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  175. return Promise.resolve(transmuxData);
  176. }
  177. };
  178. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  179. 'audio/ac3',
  180. () => new shaka.transmuxer.Ac3Transmuxer('audio/ac3'),
  181. shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK);