AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Convert audio to MP3.
*
* @module tiny_recordrtc/convert_to_mp3
* @copyright Meirza <meirza.arson@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import lamejs from './lame.all';
/**
* Extract Pulse Code Modulation (PCM) data from an AudioBuffer to get raw channel data.
*
* @param {AudioBuffer} audioBuffer The AudioBuffer containing the audio data.
* @returns {Array<Int16Array>} The PCM data for each channel.
*/
const extractPCM = (audioBuffer) => {
const channelData = [];
const numberOfChannels = audioBuffer.numberOfChannels;
const audioBufferLength = audioBuffer.length;
for (let channel = 0; channel < numberOfChannels; channel++) {
const rawChannelData = audioBuffer.getChannelData(channel);
channelData[channel] = new Int16Array(audioBufferLength);
// Convert floating-point audio samples into 16-bit signed integer values.
for (let i = 0; i < audioBufferLength; i++) {
channelData[channel][i] = rawChannelData[i] * 32768;
}
}
return channelData;
};
/**
* Fetches and decodes the audio data from a given URL into an AudioBuffer.
*
* @param {string} sourceUrl - The URL of the source audio file.
* @returns {Promise<AudioBuffer>} - A promise that resolves with the decoded AudioBuffer object.
*/
const getAudioBuffer = async(sourceUrl) => {
const response = await fetch(sourceUrl);
const arrayBuffer = await response.arrayBuffer();
const audioContext = new (
window.AudioContext // Default.
|| window.webkitAudioContext // Safari and old versions of Chrome.
)();
return audioContext.decodeAudioData(arrayBuffer);
};
/**
* Converts an AudioBuffer to MP3 format using lamejs.
*
* @param {Object} lamejs - The lamejs library object.
* @param {number} channels - The number of audio channels (1 for mono, 2 for stereo).
* @param {number} sampleRate - The sample rate of the audio (e.g., 44100 Hz).
* @param {number} bitRate - The bitrate (in kbps) to encode the MP3.
* @param {Int16Array} left - The PCM data for the left channel.
* @param {Int16Array} [right=null] - The PCM data for the right channel (optional for stereo).
* @returns {Blob} - A Blob containing the MP3 audio data.
*/
const convertAudioBuffer = (lamejs, channels, sampleRate, bitRate, left, right = null) => {
const mp3Data = [];
const mp3Encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitRate);
// Each frame represents 1152 audio samples per channel (for both mono and stereo).
const sampleBlockSize = 1152;
// Ensure that the same encoding logic works for both mono and stereo audio by
// either passing both channels or just the left channel to the MP3 encoder.
for (let i = 0; i < left.length; i += sampleBlockSize) {
const leftChunk = left.subarray(i, i + sampleBlockSize);
const mp3Buf = right
? mp3Encoder.encodeBuffer(leftChunk, right.subarray(i, i + sampleBlockSize)) // Stereo.
: mp3Encoder.encodeBuffer(leftChunk); // Mono.
if (mp3Buf.length) {
mp3Data.push(mp3Buf);
}
}
// Preventing loss of the last few samples of audio.
const mp3Buf = mp3Encoder.flush();
if (mp3Buf.length) {
mp3Data.push(new Int8Array(mp3Buf));
}
return new Blob(mp3Data, {type: 'audio/mp3'});
};
/**
* Main function to handle the entire process of converting an audio file to MP3 format.
*
* @param {string} sourceUrl - The URL of the source audio file to be converted.
* @param {number} [bitRate=128] - The bitrate (in kbps) for the MP3 conversion. Default is 128 kbps.
* @returns {Promise<Blob>} - A promise that resolves with the MP3 file as a Blob.
*
* @throws {Error} If the Lamejs module or audio buffer fails to load.
*
* @example
* const mp3Data = await convertMp3('audio-source.wav', 192);
* window.console.log(mp3Data); // Logs the ArrayBuffer with MP3 data.
*/
export const convertMp3 = async(sourceUrl, bitRate = 128) => {
const audioBuffer = await getAudioBuffer(sourceUrl);
const [left, right] = extractPCM(audioBuffer);
return convertAudioBuffer(lamejs, audioBuffer.numberOfChannels, audioBuffer.sampleRate, bitRate, left, right);
};