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