Source: lib/transmuxer/aac_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.AacTransmuxer');
  7. goog.require('shaka.media.Capabilities');
  8. goog.require('shaka.transmuxer.ADTS');
  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.MimeUtils');
  15. goog.require('shaka.util.Mp4Generator');
  16. goog.require('shaka.util.Uint8ArrayUtils');
  17. /**
  18. * @implements {shaka.extern.Transmuxer}
  19. * @export
  20. */
  21. shaka.transmuxer.AacTransmuxer = 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. }
  33. /**
  34. * @override
  35. * @export
  36. */
  37. destroy() {
  38. this.initSegments.clear();
  39. }
  40. /**
  41. * Check if the mime type and the content type is supported.
  42. * @param {string} mimeType
  43. * @param {string=} contentType
  44. * @return {boolean}
  45. * @override
  46. * @export
  47. */
  48. isSupported(mimeType, contentType) {
  49. const Capabilities = shaka.media.Capabilities;
  50. if (!this.isAacContainer_(mimeType)) {
  51. return false;
  52. }
  53. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  54. return Capabilities.isTypeSupported(
  55. this.convertCodecs(ContentType.AUDIO, mimeType));
  56. }
  57. /**
  58. * Check if the mimetype is 'audio/aac'.
  59. * @param {string} mimeType
  60. * @return {boolean}
  61. * @private
  62. */
  63. isAacContainer_(mimeType) {
  64. return mimeType.toLowerCase().split(';')[0] == 'audio/aac';
  65. }
  66. /**
  67. * @override
  68. * @export
  69. */
  70. convertCodecs(contentType, mimeType) {
  71. if (this.isAacContainer_(mimeType)) {
  72. const codecs = shaka.util.MimeUtils.getCodecs(mimeType);
  73. return `audio/mp4; codecs="${codecs || 'mp4a.40.2'}"`;
  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 ADTS = shaka.transmuxer.ADTS;
  90. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  91. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  92. // Check for the ADTS sync word
  93. // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be
  94. // either 0 or 1
  95. // Layer bits (position 14 and 15) in header should be always 0 for ADTS
  96. // More info https://wiki.multimedia.cx/index.php?title=ADTS
  97. const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
  98. let offset = id3Data.length;
  99. for (; offset < uint8ArrayData.length; offset++) {
  100. if (ADTS.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. const info = ADTS.parseInfo(uint8ArrayData, offset);
  116. if (!info) {
  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 = info.sampleRate;
  124. stream.channelsCount = info.channelCount;
  125. /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
  126. const samples = [];
  127. while (offset < uint8ArrayData.length) {
  128. const header = ADTS.parseHeader(uint8ArrayData, offset);
  129. if (!header) {
  130. return Promise.reject(new shaka.util.Error(
  131. shaka.util.Error.Severity.CRITICAL,
  132. shaka.util.Error.Category.MEDIA,
  133. shaka.util.Error.Code.TRANSMUXING_FAILED,
  134. reference ? reference.getUris()[0] : null));
  135. }
  136. const length = header.headerLength + header.frameLength;
  137. if (offset + length <= uint8ArrayData.length) {
  138. const data = uint8ArrayData.subarray(
  139. offset + header.headerLength, offset + length);
  140. samples.push({
  141. data: data,
  142. size: header.frameLength,
  143. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  144. cts: 0,
  145. flags: {
  146. isLeading: 0,
  147. isDependedOn: 0,
  148. hasRedundancy: 0,
  149. degradPrio: 0,
  150. dependsOn: 2,
  151. isNonSync: 0,
  152. },
  153. });
  154. }
  155. offset += length;
  156. }
  157. /** @type {number} */
  158. const sampleRate = info.sampleRate;
  159. /** @type {number} */
  160. const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
  161. /** @type {shaka.util.Mp4Generator.StreamInfo} */
  162. const streamInfo = {
  163. id: stream.id,
  164. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  165. codecs: info.codec,
  166. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  167. timescale: sampleRate,
  168. duration: duration,
  169. videoNalus: [],
  170. audioConfig: new Uint8Array([]),
  171. videoConfig: new Uint8Array([]),
  172. hSpacing: 0,
  173. vSpacing: 0,
  174. data: {
  175. sequenceNumber: this.frameIndex_,
  176. baseMediaDecodeTime: baseMediaDecodeTime,
  177. samples: samples,
  178. },
  179. stream: stream,
  180. };
  181. const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
  182. let initSegment;
  183. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  184. if (!this.initSegments.has(initSegmentKey)) {
  185. initSegment = mp4Generator.initSegment();
  186. this.initSegments.set(initSegmentKey, initSegment);
  187. } else {
  188. initSegment = this.initSegments.get(initSegmentKey);
  189. }
  190. const segmentData = mp4Generator.segmentData();
  191. this.frameIndex_++;
  192. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  193. return Promise.resolve(transmuxData);
  194. }
  195. };
  196. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  197. 'audio/aac',
  198. () => new shaka.transmuxer.AacTransmuxer('audio/aac'),
  199. shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK);