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.device.DeviceFactory');
  8. goog.require('shaka.media.Capabilities');
  9. goog.require('shaka.transmuxer.Ac3');
  10. goog.require('shaka.transmuxer.TransmuxerEngine');
  11. goog.require('shaka.util.BufferUtils');
  12. goog.require('shaka.util.Error');
  13. goog.require('shaka.util.Id3Utils');
  14. goog.require('shaka.util.ManifestParserUtils');
  15. goog.require('shaka.util.Mp4Generator');
  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. const device = shaka.device.DeviceFactory.getDevice();
  75. if (device.requiresEC3InitSegments()) {
  76. return 'audio/mp4; codecs="ec-3"';
  77. } else {
  78. return 'audio/mp4; codecs="ac-3"';
  79. }
  80. }
  81. return mimeType;
  82. }
  83. /**
  84. * @override
  85. * @export
  86. */
  87. getOriginalMimeType() {
  88. return this.originalMimeType_;
  89. }
  90. /**
  91. * @override
  92. * @export
  93. */
  94. transmux(data, stream, reference, duration) {
  95. const Ac3 = shaka.transmuxer.Ac3;
  96. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  97. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  98. const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
  99. let offset = id3Data.length;
  100. for (; offset < uint8ArrayData.length; offset++) {
  101. if (Ac3.probe(uint8ArrayData, offset)) {
  102. break;
  103. }
  104. }
  105. let timestamp = reference.endTime * 1000;
  106. const frames = shaka.util.Id3Utils.getID3Frames(id3Data);
  107. if (frames.length && reference) {
  108. const metadataTimestamp = frames.find((frame) => {
  109. return frame.description ===
  110. 'com.apple.streaming.transportStreamTimestamp';
  111. });
  112. if (metadataTimestamp) {
  113. timestamp = /** @type {!number} */(metadataTimestamp.data);
  114. }
  115. }
  116. /** @type {number} */
  117. let sampleRate = 0;
  118. /** @type {!Uint8Array} */
  119. let audioConfig = new Uint8Array([]);
  120. /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
  121. const samples = [];
  122. while (offset < uint8ArrayData.length) {
  123. const frame = Ac3.parseFrame(uint8ArrayData, offset);
  124. if (!frame) {
  125. return Promise.reject(new shaka.util.Error(
  126. shaka.util.Error.Severity.CRITICAL,
  127. shaka.util.Error.Category.MEDIA,
  128. shaka.util.Error.Code.TRANSMUXING_FAILED,
  129. reference ? reference.getUris()[0] : null));
  130. }
  131. stream.audioSamplingRate = frame.sampleRate;
  132. stream.channelsCount = frame.channelCount;
  133. sampleRate = frame.sampleRate;
  134. audioConfig = frame.audioConfig;
  135. const frameData = uint8ArrayData.subarray(
  136. offset, offset + frame.frameLength);
  137. samples.push({
  138. data: frameData,
  139. size: frame.frameLength,
  140. duration: Ac3.AC3_SAMPLES_PER_FRAME,
  141. cts: 0,
  142. flags: {
  143. isLeading: 0,
  144. isDependedOn: 0,
  145. hasRedundancy: 0,
  146. degradPrio: 0,
  147. dependsOn: 2,
  148. isNonSync: 0,
  149. },
  150. });
  151. offset += frame.frameLength;
  152. }
  153. /** @type {number} */
  154. const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
  155. /** @type {shaka.util.Mp4Generator.StreamInfo} */
  156. const streamInfo = {
  157. id: stream.id,
  158. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  159. codecs: 'ac-3',
  160. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  161. timescale: sampleRate,
  162. duration: duration,
  163. videoNalus: [],
  164. audioConfig: audioConfig,
  165. videoConfig: new Uint8Array([]),
  166. hSpacing: 0,
  167. vSpacing: 0,
  168. data: {
  169. sequenceNumber: this.frameIndex_,
  170. baseMediaDecodeTime: baseMediaDecodeTime,
  171. samples: samples,
  172. },
  173. stream: stream,
  174. };
  175. const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
  176. let initSegment;
  177. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  178. if (!this.initSegments.has(initSegmentKey)) {
  179. initSegment = mp4Generator.initSegment();
  180. this.initSegments.set(initSegmentKey, initSegment);
  181. } else {
  182. initSegment = this.initSegments.get(initSegmentKey);
  183. }
  184. const appendInitSegment = this.lastInitSegment_ !== initSegment;
  185. const segmentData = mp4Generator.segmentData();
  186. this.lastInitSegment_ = initSegment;
  187. this.frameIndex_++;
  188. if (appendInitSegment) {
  189. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  190. return Promise.resolve(transmuxData);
  191. } else {
  192. return Promise.resolve(segmentData);
  193. }
  194. }
  195. };
  196. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  197. 'audio/ac3',
  198. () => new shaka.transmuxer.Ac3Transmuxer('audio/ac3'),
  199. shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK);