12

I'm trying to use recorderjs on an app engine site where users upload short audio recordings (say, 1 to a dozen seconds long). I've noticed that the WAV files I'm uploading are much larger than I expected. For example, I just created a recording that lasts roughly 9 seconds, and the uploaded blob is 1736769 bytes, which is > 1.5 megabytes.

Question:

How do I modify the recorderjs code (or my own code -- maybe I'm using recorderjs incorrectly) so that my audio blobs have a lower bitrate? I'd like a 10 second recording to be safely under 1 MB.

My guess is that I would need to modify the encodeWAV function in here, or maybe exportWAV, but I'm not sure how. Would it make sense to just drop every other element of the interleaved buffer in exportWAV? Is there a more intelligent way to do it? How does the bitrate of the exported WAV depend on properties of my computer (e.g. the sampling rate of my soundcard)?

I can add some details on my own code if it might be helpful.

Edit: if you'd like to see a live example, install google chrome beta and try this page. On my computer, a recording 5-10 seconds long is over 1 MB.

Many thanks,

Adrian

1

3 Answers 3

23

In my case Chrome records audio at 96kHz and Firefox at 44.1kHz, that makes huge WAV files. I implemented a downsampling function inside recorderWorker.js where you can select the sample ratio you want, like 16000.

function downsampleBuffer(buffer, rate) {
    if (rate == sampleRate) {
        return buffer;
    }
    if (rate > sampleRate) {
        throw "downsampling rate show be smaller than original sample rate";
    }
    var sampleRateRatio = sampleRate / rate;
    var newLength = Math.round(buffer.length / sampleRateRatio);
    var result = new Float32Array(newLength);
    var offsetResult = 0;
    var offsetBuffer = 0;
    while (offsetResult < result.length) {
        var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
        var accum = 0, count = 0;
        for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
            accum += buffer[i];
            count++;
        }
        result[offsetResult] = accum / count;
        offsetResult++;
        offsetBuffer = nextOffsetBuffer;
    }
    return result;
}

and i call it when exporting the wav file:

function exportWAV(rate, type) {
    var bufferL = mergeBuffers(recBuffersL, recLength);
    var bufferR = mergeBuffers(recBuffersR, recLength);
    var interleaved = interleave(bufferL, bufferR);
    var downsampledBuffer = downsampleBuffer(interleaved, rate);
    var dataview = encodeWAV(rate, downsampledBuffer, false);
    var audioBlob = new Blob([ dataview ], {
        type : type
    });

    this.postMessage(audioBlob);
}
9
  • It depends on your final sample rate. You can try another strategy, not to calculate the average bitrate, only consider one sample per step: result[offsetResult] = buffer[offsetBuffer]; Oct 28, 2014 at 13:32
  • It needs to be 8khz, for Asterisk
    – nafg
    Oct 29, 2014 at 17:48
  • 1
    just call exportWAV(8000, 'audio/wav') Nov 3, 2014 at 3:30
  • 1
    They are attributes in recorderWorker.js. Be careful, there's a new version of Recorder.js and now the number of channels is configurable recBuffers = [], you will have to adapt my solution if you use this version of the library. Jul 28, 2015 at 10:33
  • 5
    People like you make the world a better place. Nov 2, 2015 at 14:01
4

You could try a couple of things. First off, I think you're on to something about "dropping every other element of the interleaved buffer" (converting the sound to mono).

For that you could choose to keep the left or the right channel. You could change the "interleave" function to be:

function interleave(inputL, inputR){
  return inputL; // or inputR
}

If you wanted to keep both channels, but "pan" them both center (to the single mono channel), you could do something like:

function interleave(inputL, inputR){
  var result = new Float32Array(inputL.length);
  for (var i = 0; i < inputL.length; ++i)
    result[i] = 0.5 * (inputL[i] + inputR[i]);
  return result;
}

That being said, there are potentially a lot of other placed you would have to change the encoded audio from being denoted as stereo to mono. However, my guess is (and I haven't used recorder.js, so I don't know it's inner workings), line 113/114 in the recorderWorker could probably changed to 1.

My guess is that you could get away with just changing the two places mentioned here (the interleave function, and the place where channel-count is set [line 114]) because: interleave and encodeWAV are only called through the exportWAV function, so not touching how the original worker has been recorder audio (and it's been recording stereo) hopefully won't break it. We would in that case only be making changes to the audio that was stored.

4
  • 3
    Thank you, this worked. I used your 2nd interleave(L,R) function, which averages the two inputs, and I set the channel count in line 114 to 1. This cuts the file sizes in half.
    – Adrian
    May 5, 2013 at 11:58
  • 2
    Ah, just realized that you also have to modify the block align (channel count * bytes per sample) as well, in case anyone else is reading
    – Adrian
    May 8, 2013 at 10:45
  • @Adrian I am, and you might want to post your solution here so we can +1 it :)
    – icedwater
    Feb 26, 2014 at 11:16
  • Is it possible to set recorder.js to sample at 11.025 khz instead of the default 44.100 khz. When I change this, the resulting file plays back at 1/4 the usual speed - like a record player on slow. I just want smaller lower quality sound. Mar 25, 2014 at 22:19
3

I'm using the same recorder code and I needed to lower the bit rate. My solution produces a 11025Hz mono file. it's not very elegant so I'll be glad if someone has corrections for me.

First I change sample rate in the init function to be 11025 instead of the audio context's bit rate (this is the non elegant part since the context might not be 44100Hz).

I replace the interleave function content with this

var length = inputL.length / 4;
var result = new Float32Array(length);

var index = 0,
  inputIndex = 0;

while (index < length) {
    result[index++] = 0.25 * (inputL[inputIndex++] + inputL[inputIndex++] +
                              inputL[inputIndex++] + inputL[inputIndex++]);
}

return result;

This takes only the left channel and turns every 4 buffer samples into 1 in the result so it takes up less memory. if the bit rate is changed by the same ratio (divided by 4 e.g. 11025), the file will sound the same but will be much smaller.

I also changed the channel count in encodeWAV to one

/* channel count */
 view.setUint16(22, 1, true);

The recording will be 1/8 in size compared to originally produced file.

2
  • Just tried this and it worked. But my recorded voice became higher; interesting effect.
    – icedwater
    Feb 26, 2014 at 10:42
  • 1
    This cuts the file length. I record a 15s audio and the result is a 3s audio :/
    – Zerquix18
    Oct 21, 2015 at 16:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.