UNPKG

shaka-player

Version:
197 lines (178 loc) 5.53 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.transmuxer.MpegAudio'); /** * MPEG parser utils * * @see https://en.wikipedia.org/wiki/MP3 */ shaka.transmuxer.MpegAudio = class { /** * @param {!Uint8Array} data * @param {!number} offset * @return {?{sampleRate: number, channelCount: number, * frameLength: number, samplesPerFrame: number}} */ static parseHeader(data, offset) { const MpegAudio = shaka.transmuxer.MpegAudio; const mpegVersion = (data[offset + 1] >> 3) & 3; const mpegLayer = (data[offset + 1] >> 1) & 3; const bitRateIndex = (data[offset + 2] >> 4) & 15; const sampleRateIndex = (data[offset + 2] >> 2) & 3; if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) { const paddingBit = (data[offset + 2] >> 1) & 1; const channelMode = data[offset + 3] >> 6; let columnInBitrates = 0; if (mpegVersion === 3) { columnInBitrates = 3 - mpegLayer; } else { columnInBitrates = mpegLayer === 3 ? 3 : 4; } const bitRate = MpegAudio.BITRATES_MAP_[ columnInBitrates * 14 + bitRateIndex - 1] * 1000; const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2; const sampleRate = MpegAudio.SAMPLING_RATE_MAP_[ columnInSampleRates * 3 + sampleRateIndex]; // If bits of channel mode are `11` then it is a single channel (Mono) const channelCount = channelMode === 3 ? 1 : 2; const sampleCoefficient = MpegAudio.SAMPLES_COEFFICIENTS_[mpegVersion][mpegLayer]; const bytesInSlot = MpegAudio.BYTES_IN_SLOT_[mpegLayer]; const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot; const frameLength = Math.floor((sampleCoefficient * bitRate) / sampleRate + paddingBit) * bytesInSlot; const userAgent = navigator.userAgent || ''; // This affect to Tizen also for example. const result = userAgent.match(/Chrome\/(\d+)/i); const chromeVersion = result ? parseInt(result[1], 10) : 0; const needChromeFix = !!chromeVersion && chromeVersion <= 87; if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) { // Work around bug in Chromium by setting channelMode // to dual-channel (01) instead of stereo (00) data[offset + 3] = data[offset + 3] | 0x80; } return {sampleRate, channelCount, frameLength, samplesPerFrame}; } return null; } /** * @param {!Uint8Array} data * @param {!number} offset * @return {boolean} */ static isHeaderPattern(data, offset) { return ( data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00 ); } /** * @param {!Uint8Array} data * @param {!number} offset * @return {boolean} */ static isHeader(data, offset) { // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either // 0 or 1 and Y or Z should be 1 // Layer bits (position 14 and 15) in header should be always different // from 0 (Layer I or Layer II or Layer III) // More info http://www.mp3-tech.org/programmer/frame_header.html return offset + 1 < data.length && shaka.transmuxer.MpegAudio.isHeaderPattern(data, offset); } /** * @param {!Uint8Array} data * @param {!number} offset * @return {boolean} */ static probe(data, offset) { const MpegAudio = shaka.transmuxer.MpegAudio; // same as isHeader but we also check that MPEG frame follows last // MPEG frame or end of data is reached if (offset + 1 < data.length && MpegAudio.isHeaderPattern(data, offset)) { // MPEG header Length const headerLength = 4; // MPEG frame Length const header = MpegAudio.parseHeader(data, offset); let frameLength = headerLength; if (header && header.frameLength) { frameLength = header.frameLength; } const newOffset = offset + frameLength; return newOffset === data.length || MpegAudio.isHeader(data, newOffset); } return false; } }; /** * @private {!Array<number>} */ shaka.transmuxer.MpegAudio.BITRATES_MAP_ = [ 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, ]; /** * @private {!Array<number>} */ shaka.transmuxer.MpegAudio.SAMPLING_RATE_MAP_ = [ 44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000, ]; /** * @private {!Array<!Array<number>>} */ shaka.transmuxer.MpegAudio.SAMPLES_COEFFICIENTS_ = [ // MPEG 2.5 [ 0, // Reserved 72, // Layer3 144, // Layer2 12, // Layer1 ], // Reserved [ 0, // Reserved 0, // Layer3 0, // Layer2 0, // Layer1 ], // MPEG 2 [ 0, // Reserved 72, // Layer3 144, // Layer2 12, // Layer1 ], // MPEG 1 [ 0, // Reserved 144, // Layer3 144, // Layer2 12, // Layer1 ], ]; /** * @private {!Array<number>} */ shaka.transmuxer.MpegAudio.BYTES_IN_SLOT_ = [ 0, // Reserved 1, // Layer3 1, // Layer2 4, // Layer1 ]; /** * @const {number} */ shaka.transmuxer.MpegAudio.MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;