Question

I am using Recorder.js to record audio from mic. That library can encode the PCM data in WAV and I can successfully play it back using <audio>. But, the resulting WAV data is too huge (~38MB for a 5 minute recording). I tried using libmp3lame.js available from Speech-to-Server.

In recorderWorker.js, I am importing the Lame script:

importScripts("libmp3lame.js");

Then, I changed the exportWAV() function to encode the PCM buffers into MP3 instead of 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);
}

But, the Lame.encode_buffer_ieee_float method is throwing this error:

Uncaught RangeError: Invalid array buffer length 

Here PCM data in bufferL and bufferR are Float32Array. I can not find out what exactly Lame.encode_buffer_ieee_float expects as input.

The offending line in Lame.encode_buffer_ieee_float that is throwing the error is:

var arraybuf = new ArrayBuffer(nread);

I put a breakpoint and checked the value of nread. It is -1.

So, my question is how do I use the Lame MP3 JavaScript library in this situation? Thank you.

Was it helpful?

Solution 2

I don't have a ton of time right now, so I can't actually download the libraries and put together a test, but I'd start by putting a breakpoint after this line:

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 ]) in libmp3lame.js.

Then set up a watch expression for nread. You want to see what that value is, because I'm pretty sure it's the next line (var arraybuf = new ArrayBuffer(nread);) that's throwing.

That might at least give you some insight into what's going on.

I also noticed that the encode_buffer_ieee_float method internally assumes BUFSIZE = 8192. I'm not entirely sure if that's significant or not, but it has me wondering whether or not this method might actually be intended only as a means for encoding individual Mp3 frames rather than buffers of arbitrary length.

Anyway, if you can see what the value of nread is, that should at least get you on the right track toward figuring out what's going on. But it definitely looks like the second and third parameters are intended to be Float32Arrays, so I don't think you're sending the wrong type of arguments.

OTHER TIPS

There's a library written in pure javascript, called lamejs. It is much faster than emscripten compile of libmp3lame. https://github.com/zhuker/lamejs

Example usage:

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

I have also the same error

But i solve that by doing this step: Execute init for mp3 web worker mp3worker.init() when you want to start a new recording. It's due to mp3codec value which was null, because you execute sendMessage(command: 'end'); This solution solves your problem ;)

I am building on Kevin Ennis's answer, addressing the problem arising from longer duration of wav files, what we have to do is, instead of waiting for all the data to be ready, encode the data as soon as you receive it from onaudioprocess method, the result is encoding becomes lightning fast.


I have combined Recorderjs and speech-to-server here.

the code in put in github.

my modifications,

in recorder.js

var bufferLen = 16384 // line no. 7

in 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 ]); // line no. 62866

and refactored recordWorker.js as

  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 : if you want, you can merge recordWorker.js and encoder.js into single file)

As the above answers made clear, it's obvious that the module call of lame_encode_buffer_ieee_float in the library returns an nread value of -1 indicating that the encoding failed.

It looks like the BUFSIZE variable being passed to both allocate the outbuf buffer as well as then being passed to the encode function is being set to 8192 samples, or a buffer size of 2048 samples. This would do fine for as-you-go encoding, but if you pass it a larger buffer than 2048 samples this will crap out.

The way I solved the issue was by adding this line of code before the outbuf malloc call:

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

As a result BUFSIZE will be passed to the lame_encode_buffer_ieee_float as well and nread will hold the number of data bytes in the resulting encoded buffer.

Hope this might help others that run across this issue in the future!

Solved by changing adding the code after startRecording:

  function startRecording() {

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

  }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top