如何在JavaScript中将PCM数据编码为MP3?

时间:2014-02-13 20:19:12

标签: javascript mp3 html5-audio web-audio

我正在使用Recorder.js录制来自麦克风的音频。该库可以在WAV中编码PCM数据,我可以使用<audio>成功播放它。但是,由此产生的WAV数据太大(5分钟录制时约为38MB)。我尝试使用Speech-to-Server提供的libmp3lame.js。

recorderWorker.js中,我正在导入Lame脚本:

importScripts("libmp3lame.js");

然后,我更改了exportWAV()函数,将PCM缓冲区编码为MP3而不是WAV。

function exportWAV(type){
    var bufferL = mergeBuffers(recBuffersL, recLength);
    var bufferR = mergeBuffers(recBuffersR, recLength);
    //var interleaved = interleave(bufferL, bufferR);

    console.log("Start MP3 encoding");
    var mp3codec = Lame.init();
    Lame.set_mode(mp3codec, Lame.JOINT_STEREO);
    Lame.set_num_channels(mp3codec, 2);
    Lame.set_out_samplerate(mp3codec, sampleRate);
    Lame.set_bitrate(mp3codec, 128);
    Lame.init_params(mp3codec);

    var mp3data = Lame.encode_buffer_ieee_float(mp3codec, bufferL, bufferR);
    audioBlob = new Blob([mp3data.data], { type: "audio/mp3" });
    console.log("Done MP3 encoding");

    this.postMessage(audioBlob);
}

但是,Lame.encode_buffer_ieee_float方法会抛出此错误:

Uncaught RangeError: Invalid array buffer length 

此处bufferLbufferR中的PCM数据是Float32Array。我无法确定Lame.encode_buffer_ieee_float到底需要什么作为输入。

Lame.encode_buffer_ieee_float中引发错误的违规行是:

var arraybuf = new ArrayBuffer(nread);

我放了一个断点并检查了nread的值。它是-1。

所以,我的问题是如何在这种情况下使用Lame MP3 JavaScript库?谢谢。

6 个答案:

答案 0 :(得分:6)

有一个用纯javascript编写的库,叫做lamejs。 它比libmp3lame的emscripten编译快得多。 https://github.com/zhuker/lamejs

使用示例:

lib = new lamejs();
mp3encoder = new lib.Mp3Encoder(1, 44100, 128); //mono 44.1khz encode to 128kbps
samples = new Int16Array(44100); //one second of silence
var mp3 = mp3encoder.encodeBuffer(samples); //encode mp3

答案 1 :(得分:2)

我现在没有太多时间,所以我实际上无法下载这些库并进行测试,但我首先要在这一行之后设置一个断点:

var nread = Module.ccall('lame_encode_buffer_ieee_float', 'number', [ 'number', 'number', 'number', 'number', 'number', 'number' ], [ handle, inbuf_l, inbuf_r, channel_l.length, outbuf, BUFSIZE ])中的

libmp3lame.js

然后为nread设置监视表达式。你想看看那个值是什么,因为我很确定它是下一行(var arraybuf = new ArrayBuffer(nread);)抛出的。

这至少可以让你深入了解正在发生的事情。

我还注意到encode_buffer_ieee_float方法内部假设为BUFSIZE = 8192。我不完全确定这是否重要,但它让我想知道这种方法是否真的只是作为编码单个Mp3帧而不是任意长度的缓冲区的手段。

无论如何,如果你能看到nread的价值是什么,那至少应该让你走上正确的轨道去弄清楚发生了什么。但它肯定看起来像第二个和第三个参数是Float32Arrays,所以我认为你不会发送错误类型的参数。

答案 2 :(得分:1)

我也有同样的错误

但我通过这一步解决了这个问题: 当您想要开始新录制时,执行mp3 web worker mp3worker.init()的init。 这是由于mp3codec值为null,因为您执行sendMessage(command: 'end'); 此解决方案可以解决您的问题;)

答案 3 :(得分:0)

我正在建立Kevin Ennis的答案,解决因wav文件持续时间较长而引起的问题,我们要做的是,不要等待所有数据准备好,而是在收到数据后立即对数据进行编码onaudioprocess方法,结果是编码变得闪电般快。


我已将Recorderjsspeech-to-server合并在一起。

放入github的代码。

我的修改,

在recorder.js

  

var bufferLen = 16384 //行号7

libmp3lame.js中的

  

var nread = Module.ccall('lame_encode_buffer_ieee_float','number',['number','number','number','number','number','number'],[handle,inbuf_l,inbuf_r ,channel_l.length,outbuf,channel_l.length * 4]); //行号62866

并将recordWorker.js重构为

  var WORKER_PATH = 'encoder.js';


var encoder, data=[];    


this.onmessage = function(e){
  switch(e.data.command){
    case 'init':
      init(e.data.config);
      break;
    case 'record':
      record(e.data.buffer);
      break;
    case 'exportMP3':
      exportMP3();
      break;
    case 'clear':
      clear();
      break;
  }
};

function init(config){
    sampleRate = config.sampleRate;
    encoder = new Worker(WORKER_PATH);
    encoder.onmessage = function(e){
    switch(e.data.cmd){
        case 'data' : 
                        var length = e.data.buf.length;
                        for(var i=0;i<length;i++)
                            data.push(e.data.buf[i]);
                        console.log('data = '+e.data.buf);break;
        case 'end' :    
                        var audioBlob = new Blob([new Uint8Array(data)], { type: 'audio/mp3' });
                        self.postMessage(audioBlob);
                        break;
        }  
    }
    encoder.postMessage({
      cmd: 'init',
      config: {
        samplerate: sampleRate,
        channels: 2,
        mode: 1, // setting mode as Stereo.
        bitrate: 64
      }
    });
}

function record(inputBuffer){
      encoder.postMessage({
        cmd: 'encode',
        rbuf: inputBuffer[0],
        lbuf: inputBuffer[1]
      });
}

function exportMP3(){
  encoder.postMessage({cmd: 'finish'});      
}

function clear(){
    data=[];
}

(p.s:如果需要,可以将recordWorker.js和encoder.js合并为单个文件)

答案 4 :(得分:0)

正如上面的答案所表明的那样,显而易见的是,库中lame_encode_buffer_ieee_float的模块调用返回的nread值为-1,表示编码失败。

看起来传递的BUFSIZE变量既分配outbuf缓冲区,又传递给encode函数,设置为8192个样本,或缓冲区大小为2048个样本。对于即插即用编码,这样做会很好,但是如果你传递的缓冲区大于2048个样本,那么这将会废弃。

我解决问题的方法是在outbuf malloc调用之前添加这行代码:

BUFSIZE = channel_l.length * 4;  // add this line
var outbuf = _malloc(BUFSIZE);

因此,BUFSIZE也将传递给lame_encode_buffer_ieee_float,而nread将保存生成的编码缓冲区中的数据字节数。

希望这可能有助于将来遇到此问题的其他人!

答案 5 :(得分:0)

通过在startRecording:

之后更改添加代码来解决
  function startRecording() {

    // this code
    var worker = new Worker('js/mp3Worker.js');
    worker.postMessage({
        command: 'init'
    });  
    //    

  }