【问题标题】:How to convert Audio buffer to MP3 in Javascript?如何在 Javascript 中将音频缓冲区转换为 MP3?
【发布时间】:2020-07-30 12:56:11
【问题描述】:

我在 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 => { });

将上面创建的 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);
}

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

【问题讨论】:

    标签: javascript reactjs audio mediastream


    【解决方案1】:

    我没有使用 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;
        }
    }
    

    其次,将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);
    
    }
    

    希望这会有所帮助!

    【讨论】:

    • 您刚刚为我节省了数小时的研究时间。非常感谢!
    • 这是一个很好的解决方案,但在我的测试中,在 Firefox 中录制的音频无法转换为 mp3。 @polyline 的解决方案适用于 Chrome、Firefox 和 Edge。
    【解决方案2】:

    这就是我结合 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 = [];
                        };
    

    二、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;
            }
        }
    

    三、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;
    
    }
    

    【讨论】:

    • 这很好,但我对示例第一部分中 this.chunks 的来源感到困惑
    • @Polyline 我收到此错误 RangeError: Offset is outside the bounds of the DataView on Mac
    猜你喜欢
    • 2012-12-03
    • 2018-03-26
    • 2015-08-13
    • 2017-07-21
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 2021-06-19
    • 2019-07-29
    相关资源
    最近更新 更多