How to convert Audio buffer to MP3 in Javascript?

喜欢而已 提交于 2021-01-24 08:37:27

问题


I am using MediaRecorder in ReactJS to record audio from the microphone and storing into the blob with MIME type "audio/mp3". I want to convert this blob to MP3 and upload it in S3 bucket.

I am able to convert it into WAV by using audioContext, decodeAudioData and audioBufferToWav functions, but the size of the WAV is very large. Since the MP3 file has relatively very small in size so I want it to convert my blob to MP3. Any help?

My code for recording and converting to 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 => { });

Converting above created blob to 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);
}

I am storing wavFile into the S3. I want MP3, please help?


回答1:


I'm not using the ReactJS MediaRecorder nor do I exactly follow what's going on in your specific example, but I have a solution for converting an AudioBuffer to an mp3, way of wave.

The first function is based on russellgood.com/how-to-convert-audiobuffer-to-audio-file. The second is based on lamejs.

First, convert the AudioBuffer to a 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;
    }
}

Second, convert the wave to an 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);

}

Hope this helps!




回答2:


This is how I combined MediaRecorder and Format converter based on great CuriousChad answer. Just needed to consider MP3 encoder as Stereo to work.

First, set AudioFormat equals to 'WEBM' (Chrome), 'MP3', or '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 = [];
                    };

Second, convert Buffer to 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;
        }
    }

Third, convert WAV to MP3:

I had to switch to Stereo as I have 2 channels for MP3Encoder left & right.

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;

}


来源:https://stackoverflow.com/questions/61264581/how-to-convert-audio-buffer-to-mp3-in-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!