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.Mp4Generator');
  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.MimeUtils');
  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. /** @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.isAacContainer_(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/aac'.
  61. * @param {string} mimeType
  62. * @return {boolean}
  63. * @private
  64. */
  65. isAacContainer_(mimeType) {
  66. return mimeType.toLowerCase().split(';')[0] == 'audio/aac';
  67. }
  68. /**
  69. * @override
  70. * @export
  71. */
  72. convertCodecs(contentType, mimeType) {
  73. if (this.isAacContainer_(mimeType)) {
  74. const codecs = shaka.util.MimeUtils.getCodecs(mimeType);
  75. return `audio/mp4; codecs="${codecs || 'mp4a.40.2'}"`;
  76. }
  77. return mimeType;
  78. }
  79. /**
  80. * @override
  81. * @export
  82. */
  83. getOriginalMimeType() {
  84. return this.originalMimeType_;
  85. }
  86. /**
  87. * @override
  88. * @export
  89. */
  90. transmux(data, stream, reference, duration) {
  91. const ADTS = shaka.transmuxer.ADTS;
  92. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  93. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  94. // Check for the ADTS sync word
  95. // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be
  96. // either 0 or 1
  97. // Layer bits (position 14 and 15) in header should be always 0 for ADTS
  98. // More info https://wiki.multimedia.cx/index.php?title=ADTS
  99. const id3Data = shaka.util.Id3Utils.getID3Data(uint8ArrayData);
  100. let offset = id3Data.length;
  101. for (; offset < uint8ArrayData.length; offset++) {
  102. if (ADTS.probe(uint8ArrayData, offset)) {
  103. break;
  104. }
  105. }
  106. let timestamp = reference.endTime * 1000;
  107. const frames = shaka.util.Id3Utils.getID3Frames(id3Data);
  108. if (frames.length && reference) {
  109. const metadataTimestamp = frames.find((frame) => {
  110. return frame.description ===
  111. 'com.apple.streaming.transportStreamTimestamp';
  112. });
  113. if (metadataTimestamp) {
  114. timestamp = /** @type {!number} */(metadataTimestamp.data);
  115. }
  116. }
  117. const info = ADTS.parseInfo(uint8ArrayData, offset);
  118. if (!info) {
  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 = info.sampleRate;
  126. stream.channelsCount = info.channelCount;
  127. /** @type {!Array<shaka.transmuxer.Mp4Generator.Mp4Sample>} */
  128. const samples = [];
  129. while (offset < uint8ArrayData.length) {
  130. const header = ADTS.parseHeader(uint8ArrayData, offset);
  131. if (!header) {
  132. return Promise.reject(new shaka.util.Error(
  133. shaka.util.Error.Severity.CRITICAL,
  134. shaka.util.Error.Category.MEDIA,
  135. shaka.util.Error.Code.TRANSMUXING_FAILED,
  136. reference ? reference.getUris()[0] : null));
  137. }
  138. const length = header.headerLength + header.frameLength;
  139. if (offset + length <= uint8ArrayData.length) {
  140. const data = uint8ArrayData.subarray(
  141. offset + header.headerLength, offset + length);
  142. samples.push({
  143. data: data,
  144. size: header.frameLength,
  145. duration: ADTS.AAC_SAMPLES_PER_FRAME,
  146. cts: 0,
  147. flags: {
  148. isLeading: 0,
  149. isDependedOn: 0,
  150. hasRedundancy: 0,
  151. degradPrio: 0,
  152. dependsOn: 2,
  153. isNonSync: 0,
  154. },
  155. });
  156. }
  157. offset += length;
  158. }
  159. /** @type {number} */
  160. const sampleRate = info.sampleRate;
  161. /** @type {number} */
  162. const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
  163. /** @type {shaka.transmuxer.Mp4Generator.StreamInfo} */
  164. const streamInfo = {
  165. id: stream.id,
  166. type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
  167. codecs: info.codec,
  168. encrypted: stream.encrypted && stream.drmInfos.length > 0,
  169. timescale: sampleRate,
  170. duration: duration,
  171. videoNalus: [],
  172. audioConfig: new Uint8Array([]),
  173. videoConfig: new Uint8Array([]),
  174. hSpacing: 0,
  175. vSpacing: 0,
  176. data: {
  177. sequenceNumber: this.frameIndex_,
  178. baseMediaDecodeTime: baseMediaDecodeTime,
  179. samples: samples,
  180. },
  181. stream: stream,
  182. };
  183. const mp4Generator = new shaka.transmuxer.Mp4Generator([streamInfo]);
  184. let initSegment;
  185. const initSegmentKey = stream.id + '_' + reference.discontinuitySequence;
  186. if (!this.initSegments.has(initSegmentKey)) {
  187. initSegment = mp4Generator.initSegment();
  188. this.initSegments.set(initSegmentKey, initSegment);
  189. } else {
  190. initSegment = this.initSegments.get(initSegmentKey);
  191. }
  192. const appendInitSegment = this.lastInitSegment_ !== initSegment;
  193. const segmentData = mp4Generator.segmentData();
  194. this.lastInitSegment_ = initSegment;
  195. this.frameIndex_++;
  196. if (appendInitSegment) {
  197. const transmuxData = Uint8ArrayUtils.concat(initSegment, segmentData);
  198. return Promise.resolve(transmuxData);
  199. } else {
  200. return Promise.resolve(segmentData);
  201. }
  202. }
  203. };
  204. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  205. 'audio/aac',
  206. () => new shaka.transmuxer.AacTransmuxer('audio/aac'),
  207. shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK);