如何在 Javascript 中将音频缓冲区转换为 MP3?

Vij*_*iya 4 javascript audio reactjs mediastream

我在 ReactJS 中使用 MediaRecorder 来录制来自麦克风的音频并以 MIME 类型“audio/mp3”存储到 blob 中。我想将此 blob 转换为 MP3 并将其上传到 S3 存储桶中。

我可以通过使用audioContext、decodeAudioData 和audioBufferToWav 函数将其转换为WAV,但是WAV 的大小非常大。由于 MP3 文件的大小相对非常小,所以我希望它将我的 blob 转换为 MP3。有什么帮助吗?

我用于录制和转换为 wav 的代码:

getUserMedia({ audio: true })
      .then(stream => {
        this.stream = stream;
        const mimeType = 'audio/mp3';
        this.mediaRecorder = new MediaRecorder(stream);
        this.mediaRecorder.start();
        const audioChunks = [];
        this.mediaRecorder.addEventListener('dataavailable', event => {
          audioChunks.push(event.data);
        });

        this.mediaRecorder.addEventListener('stop', () => {
          const audioBlob = new Blob(audioChunks, {
            type: mimeType});

        });
      }).catch(error => { });
Run Code Online (Sandbox Code Playgroud)

将上面创建的 blob 转换为 WAV:

const reader = new window.FileReader();
    reader.readAsDataURL(audioBlob);
    reader.onloadend = () => {
      let base64 = reader.result + '';
      base64 = base64.split(',')[1];
      const ab = new ArrayBuffer(base64.length);
      const buff = new Buffer.from(base64, 'base64');
      const view = new Uint8Array(ab);
      for (let i = 0; i < buff.length; ++i) {
        view[i] = buff[i];
      }
      const context = new AudioContext();
      context.decodeAudioData(ab, (buffer) => {
      const wavFile = toWav(buffer);
}
Run Code Online (Sandbox Code Playgroud)

我正在存储wavFile到 S3 中。我想要MP3,请帮忙?

LWS*_*had 7

我没有使用 ReactJS MediaRecorder,也没有完全遵循您的具体示例中发生的事情,但是我有一个将 AudioBuffer 转换为 mp3 的解决方案。

第一个函数基于russellgood.com/how-to-convert-audiobuffer-to-audio-file。第二个基于lamejs

首先,将AudioBuffer转换为wave blob

function audioBufferToWav(aBuffer) {
    let numOfChan = aBuffer.numberOfChannels,
        btwLength = aBuffer.length * numOfChan * 2 + 44,
        btwArrBuff = new ArrayBuffer(btwLength),
        btwView = new DataView(btwArrBuff),
        btwChnls = [],
        btwIndex,
        btwSample,
        btwOffset = 0,
        btwPos = 0;
    setUint32(0x46464952); // "RIFF"
    setUint32(btwLength - 8); // file length - 8
    setUint32(0x45564157); // "WAVE"
    setUint32(0x20746d66); // "fmt " chunk
    setUint32(16); // length = 16
    setUint16(1); // PCM (uncompressed)
    setUint16(numOfChan);
    setUint32(aBuffer.sampleRate);
    setUint32(aBuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
    setUint16(numOfChan * 2); // block-align
    setUint16(16); // 16-bit
    setUint32(0x61746164); // "data" - chunk
    setUint32(btwLength - btwPos - 4); // chunk length

    for (btwIndex = 0; btwIndex < aBuffer.numberOfChannels; btwIndex++)
        btwChnls.push(aBuffer.getChannelData(btwIndex));

    while (btwPos < btwLength) {
        for (btwIndex = 0; btwIndex < numOfChan; btwIndex++) {
            // interleave btwChnls
            btwSample = Math.max(-1, Math.min(1, btwChnls[btwIndex][btwOffset])); // clamp
            btwSample = (0.5 + btwSample < 0 ? btwSample * 32768 : btwSample * 32767) | 0; // scale to 16-bit signed int
            btwView.setInt16(btwPos, btwSample, true); // write 16-bit sample
            btwPos += 2;
        }
        btwOffset++; // next source sample
    }

    let wavHdr = lamejs.WavHeader.readHeader(new DataView(btwArrBuff));
    let wavSamples = new Int16Array(btwArrBuff, wavHdr.dataOffset, wavHdr.dataLen / 2);

    wavToMp3(wavHdr.channels, wavHdr.sampleRate, wavSamples);

    function setUint16(data) {
        btwView.setUint16(btwPos, data, true);
        btwPos += 2;
    }

    function setUint32(data) {
        btwView.setUint32(btwPos, data, true);
        btwPos += 4;
    }
}
Run Code Online (Sandbox Code Playgroud)

二、将wave转成mp3

function wavToMp3(channels, sampleRate, samples) {
    var buffer = [];
    var mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
    var remaining = samples.length;
    var samplesPerFrame = 1152;
    for (var i = 0; remaining >= samplesPerFrame; i += samplesPerFrame) {
        var mono = samples.subarray(i, i + samplesPerFrame);
        var mp3buf = mp3enc.encodeBuffer(mono);
        if (mp3buf.length > 0) {
            buffer.push(new Int8Array(mp3buf));
        }
        remaining -= samplesPerFrame;
    }
    var d = mp3enc.flush();
    if(d.length > 0){
        buffer.push(new Int8Array(d));
    }

    var mp3Blob = new Blob(buffer, {type: 'audio/mp3'});
    var bUrl = window.URL.createObjectURL(mp3Blob);

    // send the download link to the console
    console.log('mp3 download:', bUrl);

}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

  • 你刚刚节省了我几个小时的研究时间。非常感谢! (2认同)

Pol*_*ine 6

这就是我MediaRecorder根据出色的 CuriousChad 答案组合和格式转换器的方式。只需要考虑 MP3 编码器作为立体声工作。

首先,将 AudioFormat 设置为 'WEBM' (Chrome)、'MP3' 或 'WAV':


                    this.mediaRecorder.onstop = e => {
                    if (AudioFormat === 'MP3' || AudioFormat === 'WAV')
                    {
                        var data = this.chunks[0];
                        var blob = new Blob(this.chunks, { type : 'video/webm' });
    
                        const audioContext = new AudioContext();
                        const fileReader = new FileReader();
    
                        // Set up file reader on loaded end event
                        fileReader.onloadend = () => {
                              const arrayBuffer = fileReader.result;// as ArrayBuffer;
    
                              // Convert array buffer into audio buffer
                              audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {
    
                                // Do something with audioBuffer
                                console.log(audioBuffer)
                                var MP3Blob = this.audioBufferToWav(audioBuffer);
                                onStop(MP3Blob,audioBuffer);
   
                             })    
                        }
    
                        //Load blob
                        fileReader.readAsArrayBuffer(blob)
                    }
                    else {
                          var data = this.chunks[0];
                          var blob = new Blob(this.chunks, { type : 'audio/mpeg' });
                          onStop(blob,data)
                    }
                    this.chunks = [];
                    };
Run Code Online (Sandbox Code Playgroud)

二、将Buffer转Wav:

     audioBufferToWav(aBuffer) {
        let numOfChan = aBuffer.numberOfChannels,
            btwLength = aBuffer.length * numOfChan * 2 + 44,
            btwArrBuff = new ArrayBuffer(btwLength),
            btwView = new DataView(btwArrBuff),
            btwChnls = [],
            btwIndex,
            btwSample,
            btwOffset = 0,
            btwPos = 0;
        setUint32(0x46464952); // "RIFF"
        setUint32(btwLength - 8); // file length - 8
        setUint32(0x45564157); // "WAVE"
        setUint32(0x20746d66); // "fmt " chunk
        setUint32(16); // length = 16
        setUint16(1); // PCM (uncompressed)
        setUint16(numOfChan);
        setUint32(aBuffer.sampleRate);
        setUint32(aBuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
        setUint16(numOfChan * 2); // block-align
        setUint16(16); // 16-bit
        setUint32(0x61746164); // "data" - chunk
        setUint32(btwLength - btwPos - 4); // chunk length

        for (btwIndex = 0; btwIndex < aBuffer.numberOfChannels; btwIndex++)
            btwChnls.push(aBuffer.getChannelData(btwIndex));

        while (btwPos < btwLength) {
            for (btwIndex = 0; btwIndex < numOfChan; btwIndex++) {
                // interleave btwChnls
                btwSample = Math.max(-1, Math.min(1, btwChnls[btwIndex][btwOffset])); // clamp
                btwSample = (0.5 + btwSample < 0 ? btwSample * 32768 : btwSample * 32767) | 0; // scale to 16-bit signed int
                btwView.setInt16(btwPos, btwSample, true); // write 16-bit sample
                btwPos += 2;
            }
            btwOffset++; // next source sample
        }

        let wavHdr = lamejs.WavHeader.readHeader(new DataView(btwArrBuff));

        //Stereo
        let data = new Int16Array(btwArrBuff, wavHdr.dataOffset, wavHdr.dataLen / 2);
        let leftData = [];
        let rightData = [];
        for (let i = 0; i < data.length; i += 2) {
                     leftData.push(data[i]);
                     rightData.push(data[i + 1]);
        }
        var left = new Int16Array(leftData);
        var right = new Int16Array(rightData);


        if (AudioFormat === 'MP3')
        {
            //STEREO
            if (wavHdr.channels===2)
                return this.wavToMp3Stereo(wavHdr.channels, wavHdr.sampleRate,  left,right);
            //MONO
            else if (wavHdr.channels===1)
                return this.wavToMp3Mono(wavHdr.channels, wavHdr.sampleRate,  data);
        }
        else
           return new Blob([btwArrBuff], {type: "audio/wav"});

        function setUint16(data) {
            btwView.setUint16(btwPos, data, true);
            btwPos += 2;
        }

        function setUint32(data) {
            btwView.setUint32(btwPos, data, true);
            btwPos += 4;
        }
    }
Run Code Online (Sandbox Code Playgroud)

三、WAV转MP3:

我不得不切换到立体声,因为我有 2 个 MP3Encoder 左右声道。

wavToMp3(channels, sampleRate, left, right = null) {
 var buffer = [];
 var mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
 var remaining = left.length;
 var samplesPerFrame = 1152;


 for (var i = 0; remaining >= samplesPerFrame; i += samplesPerFrame) {

     if (!right)
     {
         var mono = left.subarray(i, i + samplesPerFrame);
         var mp3buf = mp3enc.encodeBuffer(mono);
     }
     else {
         var leftChunk = left.subarray(i, i + samplesPerFrame);
         var rightChunk = right.subarray(i, i + samplesPerFrame);
         var mp3buf = mp3enc.encodeBuffer(leftChunk,rightChunk);
     }
         if (mp3buf.length > 0) {
                 buffer.push(mp3buf);//new Int8Array(mp3buf));
         }
         remaining -= samplesPerFrame;
 }
 var d = mp3enc.flush();
 if(d.length > 0){
         buffer.push(new Int8Array(d));
 }

 var mp3Blob = new Blob(buffer, {type: 'audio/mp3'});
 //var bUrl = window.URL.createObjectURL(mp3Blob);

 // send the download link to the console
 //console.log('mp3 download:', bUrl);
 return mp3Blob;

}
Run Code Online (Sandbox Code Playgroud)