Source: lib/transmuxer/mpeg_audio.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.transmuxer.MpegAudio');
  7. /**
  8. * MPEG parser utils
  9. *
  10. * @see https://en.wikipedia.org/wiki/MP3
  11. */
  12. shaka.transmuxer.MpegAudio = class {
  13. /**
  14. * @param {!Uint8Array} data
  15. * @param {!number} offset
  16. * @return {?{sampleRate: number, channelCount: number,
  17. * frameLength: number, samplesPerFrame: number}}
  18. */
  19. static parseHeader(data, offset) {
  20. const MpegAudio = shaka.transmuxer.MpegAudio;
  21. const mpegVersion = (data[offset + 1] >> 3) & 3;
  22. const mpegLayer = (data[offset + 1] >> 1) & 3;
  23. const bitRateIndex = (data[offset + 2] >> 4) & 15;
  24. const sampleRateIndex = (data[offset + 2] >> 2) & 3;
  25. if (mpegVersion !== 1 && bitRateIndex !== 0 &&
  26. bitRateIndex !== 15 && sampleRateIndex !== 3) {
  27. const paddingBit = (data[offset + 2] >> 1) & 1;
  28. const channelMode = data[offset + 3] >> 6;
  29. let columnInBitrates = 0;
  30. if (mpegVersion === 3) {
  31. columnInBitrates = 3 - mpegLayer;
  32. } else {
  33. columnInBitrates = mpegLayer === 3 ? 3 : 4;
  34. }
  35. const bitRate = MpegAudio.BITRATES_MAP_[
  36. columnInBitrates * 14 + bitRateIndex - 1] * 1000;
  37. const columnInSampleRates =
  38. mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
  39. const sampleRate = MpegAudio.SAMPLINGRATE_MAP_[
  40. columnInSampleRates * 3 + sampleRateIndex];
  41. // If bits of channel mode are `11` then it is a single channel (Mono)
  42. const channelCount = channelMode === 3 ? 1 : 2;
  43. const sampleCoefficient =
  44. MpegAudio.SAMPLES_COEFFICIENTS_[mpegVersion][mpegLayer];
  45. const bytesInSlot = MpegAudio.BYTES_IN_SLOT_[mpegLayer];
  46. const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
  47. const frameLength =
  48. Math.floor((sampleCoefficient * bitRate) / sampleRate + paddingBit) *
  49. bytesInSlot;
  50. const userAgent = navigator.userAgent || '';
  51. // This affect to Tizen also for example.
  52. const result = userAgent.match(/Chrome\/(\d+)/i);
  53. const chromeVersion = result ? parseInt(result[1], 10) : 0;
  54. const needChromeFix = !!chromeVersion && chromeVersion <= 87;
  55. if (needChromeFix && mpegLayer === 2 &&
  56. bitRate >= 224000 && channelMode === 0) {
  57. // Work around bug in Chromium by setting channelMode
  58. // to dual-channel (01) instead of stereo (00)
  59. data[offset + 3] = data[offset + 3] | 0x80;
  60. }
  61. return {sampleRate, channelCount, frameLength, samplesPerFrame};
  62. }
  63. return null;
  64. }
  65. /**
  66. * @param {!Uint8Array} data
  67. * @param {!number} offset
  68. * @return {boolean}
  69. */
  70. static isHeaderPattern(data, offset) {
  71. return (
  72. data[offset] === 0xff &&
  73. (data[offset + 1] & 0xe0) === 0xe0 &&
  74. (data[offset + 1] & 0x06) !== 0x00
  75. );
  76. }
  77. /**
  78. * @param {!Uint8Array} data
  79. * @param {!number} offset
  80. * @return {boolean}
  81. */
  82. static isHeader(data, offset) {
  83. // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either
  84. // 0 or 1 and Y or Z should be 1
  85. // Layer bits (position 14 and 15) in header should be always different
  86. // from 0 (Layer I or Layer II or Layer III)
  87. // More info http://www.mp3-tech.org/programmer/frame_header.html
  88. return offset + 1 < data.length &&
  89. shaka.transmuxer.MpegAudio.isHeaderPattern(data, offset);
  90. }
  91. /**
  92. * @param {!Uint8Array} data
  93. * @param {!number} offset
  94. * @return {boolean}
  95. */
  96. static probe(data, offset) {
  97. const MpegAudio = shaka.transmuxer.MpegAudio;
  98. // same as isHeader but we also check that MPEG frame follows last
  99. // MPEG frame or end of data is reached
  100. if (offset + 1 < data.length &&
  101. MpegAudio.isHeaderPattern(data, offset)) {
  102. // MPEG header Length
  103. const headerLength = 4;
  104. // MPEG frame Length
  105. const header = MpegAudio.parseHeader(data, offset);
  106. let frameLength = headerLength;
  107. if (header && header.frameLength) {
  108. frameLength = header.frameLength;
  109. }
  110. const newOffset = offset + frameLength;
  111. return newOffset === data.length ||
  112. MpegAudio.isHeader(data, newOffset);
  113. }
  114. return false;
  115. }
  116. };
  117. /**
  118. * @private {!Array.<number>}
  119. */
  120. shaka.transmuxer.MpegAudio.BITRATES_MAP_ = [
  121. 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56,
  122. 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80,
  123. 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144,
  124. 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144,
  125. 160,
  126. ];
  127. /**
  128. * @private {!Array.<number>}
  129. */
  130. shaka.transmuxer.MpegAudio.SAMPLINGRATE_MAP_ = [
  131. 44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000,
  132. ];
  133. /**
  134. * @private {!Array.<!Array.<number>>}
  135. */
  136. shaka.transmuxer.MpegAudio.SAMPLES_COEFFICIENTS_ = [
  137. // MPEG 2.5
  138. [
  139. 0, // Reserved
  140. 72, // Layer3
  141. 144, // Layer2
  142. 12, // Layer1
  143. ],
  144. // Reserved
  145. [
  146. 0, // Reserved
  147. 0, // Layer3
  148. 0, // Layer2
  149. 0, // Layer1
  150. ],
  151. // MPEG 2
  152. [
  153. 0, // Reserved
  154. 72, // Layer3
  155. 144, // Layer2
  156. 12, // Layer1
  157. ],
  158. // MPEG 1
  159. [
  160. 0, // Reserved
  161. 144, // Layer3
  162. 144, // Layer2
  163. 12, // Layer1
  164. ],
  165. ];
  166. /**
  167. * @private {!Array.<number>}
  168. */
  169. shaka.transmuxer.MpegAudio.BYTES_IN_SLOT_ = [
  170. 0, // Reserved
  171. 1, // Layer3
  172. 1, // Layer2
  173. 4, // Layer1
  174. ];
  175. /**
  176. * @const {number}
  177. */
  178. shaka.transmuxer.MpegAudio.MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;