1441 |
ariadna |
1 |
// This file is part of Moodle - http://moodle.org/
|
|
|
2 |
//
|
|
|
3 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
4 |
// it under the terms of the GNU General Public License as published by
|
|
|
5 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
6 |
// (at your option) any later version.
|
|
|
7 |
//
|
|
|
8 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
9 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
10 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
11 |
// GNU General Public License for more details.
|
|
|
12 |
//
|
|
|
13 |
// You should have received a copy of the GNU General Public License
|
|
|
14 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
15 |
|
|
|
16 |
/**
|
|
|
17 |
* Convert audio to MP3.
|
|
|
18 |
*
|
|
|
19 |
* @module tiny_recordrtc/convert_to_mp3
|
|
|
20 |
* @copyright Meirza <meirza.arson@moodle.com>
|
|
|
21 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
22 |
*/
|
|
|
23 |
|
|
|
24 |
import lamejs from './lame.all';
|
|
|
25 |
|
|
|
26 |
/**
|
|
|
27 |
* Extract Pulse Code Modulation (PCM) data from an AudioBuffer to get raw channel data.
|
|
|
28 |
*
|
|
|
29 |
* @param {AudioBuffer} audioBuffer The AudioBuffer containing the audio data.
|
|
|
30 |
* @returns {Array<Int16Array>} The PCM data for each channel.
|
|
|
31 |
*/
|
|
|
32 |
const extractPCM = (audioBuffer) => {
|
|
|
33 |
const channelData = [];
|
|
|
34 |
const numberOfChannels = audioBuffer.numberOfChannels;
|
|
|
35 |
const audioBufferLength = audioBuffer.length;
|
|
|
36 |
|
|
|
37 |
for (let channel = 0; channel < numberOfChannels; channel++) {
|
|
|
38 |
const rawChannelData = audioBuffer.getChannelData(channel);
|
|
|
39 |
channelData[channel] = new Int16Array(audioBufferLength);
|
|
|
40 |
// Convert floating-point audio samples into 16-bit signed integer values.
|
|
|
41 |
for (let i = 0; i < audioBufferLength; i++) {
|
|
|
42 |
channelData[channel][i] = rawChannelData[i] * 32768;
|
|
|
43 |
}
|
|
|
44 |
}
|
|
|
45 |
|
|
|
46 |
return channelData;
|
|
|
47 |
};
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* Fetches and decodes the audio data from a given URL into an AudioBuffer.
|
|
|
51 |
*
|
|
|
52 |
* @param {string} sourceUrl - The URL of the source audio file.
|
|
|
53 |
* @returns {Promise<AudioBuffer>} - A promise that resolves with the decoded AudioBuffer object.
|
|
|
54 |
*/
|
|
|
55 |
const getAudioBuffer = async(sourceUrl) => {
|
|
|
56 |
const response = await fetch(sourceUrl);
|
|
|
57 |
const arrayBuffer = await response.arrayBuffer();
|
|
|
58 |
const audioContext = new (
|
|
|
59 |
window.AudioContext // Default.
|
|
|
60 |
|| window.webkitAudioContext // Safari and old versions of Chrome.
|
|
|
61 |
)();
|
|
|
62 |
return audioContext.decodeAudioData(arrayBuffer);
|
|
|
63 |
};
|
|
|
64 |
|
|
|
65 |
/**
|
|
|
66 |
* Converts an AudioBuffer to MP3 format using lamejs.
|
|
|
67 |
*
|
|
|
68 |
* @param {Object} lamejs - The lamejs library object.
|
|
|
69 |
* @param {number} channels - The number of audio channels (1 for mono, 2 for stereo).
|
|
|
70 |
* @param {number} sampleRate - The sample rate of the audio (e.g., 44100 Hz).
|
|
|
71 |
* @param {number} bitRate - The bitrate (in kbps) to encode the MP3.
|
|
|
72 |
* @param {Int16Array} left - The PCM data for the left channel.
|
|
|
73 |
* @param {Int16Array} [right=null] - The PCM data for the right channel (optional for stereo).
|
|
|
74 |
* @returns {Blob} - A Blob containing the MP3 audio data.
|
|
|
75 |
*/
|
|
|
76 |
const convertAudioBuffer = (lamejs, channels, sampleRate, bitRate, left, right = null) => {
|
|
|
77 |
const mp3Data = [];
|
|
|
78 |
const mp3Encoder = new lamejs.Mp3Encoder(channels, sampleRate, bitRate);
|
|
|
79 |
// Each frame represents 1152 audio samples per channel (for both mono and stereo).
|
|
|
80 |
const sampleBlockSize = 1152;
|
|
|
81 |
|
|
|
82 |
// Ensure that the same encoding logic works for both mono and stereo audio by
|
|
|
83 |
// either passing both channels or just the left channel to the MP3 encoder.
|
|
|
84 |
for (let i = 0; i < left.length; i += sampleBlockSize) {
|
|
|
85 |
const leftChunk = left.subarray(i, i + sampleBlockSize);
|
|
|
86 |
const mp3Buf = right
|
|
|
87 |
? mp3Encoder.encodeBuffer(leftChunk, right.subarray(i, i + sampleBlockSize)) // Stereo.
|
|
|
88 |
: mp3Encoder.encodeBuffer(leftChunk); // Mono.
|
|
|
89 |
|
|
|
90 |
if (mp3Buf.length) {
|
|
|
91 |
mp3Data.push(mp3Buf);
|
|
|
92 |
}
|
|
|
93 |
}
|
|
|
94 |
|
|
|
95 |
// Preventing loss of the last few samples of audio.
|
|
|
96 |
const mp3Buf = mp3Encoder.flush();
|
|
|
97 |
if (mp3Buf.length) {
|
|
|
98 |
mp3Data.push(new Int8Array(mp3Buf));
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
return new Blob(mp3Data, {type: 'audio/mp3'});
|
|
|
102 |
};
|
|
|
103 |
|
|
|
104 |
/**
|
|
|
105 |
* Main function to handle the entire process of converting an audio file to MP3 format.
|
|
|
106 |
*
|
|
|
107 |
* @param {string} sourceUrl - The URL of the source audio file to be converted.
|
|
|
108 |
* @param {number} [bitRate=128] - The bitrate (in kbps) for the MP3 conversion. Default is 128 kbps.
|
|
|
109 |
* @returns {Promise<Blob>} - A promise that resolves with the MP3 file as a Blob.
|
|
|
110 |
*
|
|
|
111 |
* @throws {Error} If the Lamejs module or audio buffer fails to load.
|
|
|
112 |
*
|
|
|
113 |
* @example
|
|
|
114 |
* const mp3Data = await convertMp3('audio-source.wav', 192);
|
|
|
115 |
* window.console.log(mp3Data); // Logs the ArrayBuffer with MP3 data.
|
|
|
116 |
*/
|
|
|
117 |
export const convertMp3 = async(sourceUrl, bitRate = 128) => {
|
|
|
118 |
const audioBuffer = await getAudioBuffer(sourceUrl);
|
|
|
119 |
const [left, right] = extractPCM(audioBuffer);
|
|
|
120 |
return convertAudioBuffer(lamejs, audioBuffer.numberOfChannels, audioBuffer.sampleRate, bitRate, left, right);
|
|
|
121 |
};
|