resample audio buffer from 44100 to 16000

13,718

Solution 1

You can use an OfflineAudioContext to do the resampling, but you need to convert your data-uri to an ArrayBuffer first. This solution works in the browser, not on the server, as it's better to send lower quality audio (lower sample rate) on the network, than send a lot of data and resample on the server.

// `source` is an AudioBuffer instance of the source audio
// at the original sample rate.

var TARGET_SAMPLE_RATE = 16000;

var offlineCtx = new OfflineAudioContext(source.numberOfChannels,
                                         source.duration * TARGET_SAMPLE_RATE,
                                         TARGET_SAMPLE_RATE);

// Play it from the beginning.
var offlineSource = offlineCtx.createBufferSource();
offlineSource.buffer = source;
offlineSource.connect(offlineCtx.destination);
offlineSource.start();
offlineCtx.startRendering().then((resampled) => {
  // `resampled` contains an AudioBuffer resampled at 16000Hz.
  // use resampled.getChannelData(x) to get an Float32Array for channel x.
});

Solution 2

No answers are correct. Here is the perfect code.

// `sourceAudioBuffer` is an AudioBuffer instance of the source audio
// at the original sample rate.
const DESIRED_SAMPLE_RATE = 16000;
const offlineCtx = new OfflineAudioContext(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.duration * DESIRED_SAMPLE_RATE, DESIRED_SAMPLE_RATE);
const cloneBuffer = offlineCtx.createBuffer(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.length, sourceAudioBuffer.sampleRate);
// Copy the source data into the offline AudioBuffer
for (let channel = 0; channel < sourceAudioBuffer.numberOfChannels; channel++) {
    cloneBuffer.copyToChannel(sourceAudioBuffer.getChannelData(channel), channel);
}
// Play it from the beginning.
const source = offlineCtx.createBufferSource();
source.buffer = cloneBuffer;
source.connect(offlineCtx.destination);
offlineCtx.oncomplete = function(e) {
  // `resampledAudioBuffer` contains an AudioBuffer resampled at 16000Hz.
  // use resampled.getChannelData(x) to get an Float32Array for channel x.
  const resampledAudioBuffer = e.renderedBuffer;
}
offlineCtx.startRendering();
source.start(0);

Solution 3

if you are using chrome browser you can directly specify sample rate in AudioContext .

1.You can directly record sound via microphone .

var context = new AudioContext({
    sampleRate: 16000,
});

2.If you already has a file or ArrayBuffer .Then you can resample it using the same audio context

    const fileReader = new FileReader();
    fileReader.readAsArrayBuffer(target.files[0]);
    
    fileReader.onload =  (e) => {
        //e.target.result is an ArrayBuffer
        context.decodeAudioData(e.target.result, async function(buffer) {
        console.log(buffer)
    })
        
    

Solution 4

This is just a copy of the answer from padenot, which I updated to avoid confusion for others who may find this post and have problems with missing variable definitions or how to get the final resampled float32array. This works for me in firefox quantum 64.0:

  var sourceAudioBuffer = e.inputBuffer;  // directly received by the audioprocess event from the microphone in the browser

  var TARGET_SAMPLE_RATE = 8000;
  var offlineCtx = new OfflineAudioContext(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.duration * sourceAudioBuffer.numberOfChannels * TARGET_SAMPLE_RATE, TARGET_SAMPLE_RATE);
  var buffer = offlineCtx.createBuffer(sourceAudioBuffer.numberOfChannels, sourceAudioBuffer.length, sourceAudioBuffer.sampleRate);
  // Copy the source data into the offline AudioBuffer
  for (var channel = 0; channel < sourceAudioBuffer.numberOfChannels; channel++) {
      buffer.copyToChannel(sourceAudioBuffer.getChannelData(channel), channel);
  }
  // Play it from the beginning.
  var source = offlineCtx.createBufferSource();
  source.buffer = sourceAudioBuffer;
  source.connect(offlineCtx.destination);
  source.start(0);
  offlineCtx.oncomplete = function(e) {
    // `resampled` contains an AudioBuffer resampled at 16000Hz.
    // use resampled.getChannelData(x) to get an Float32Array for channel x.
    var resampled = e.renderedBuffer;
    var leftFloat32Array = resampled.getChannelData(0);
    // use this float32array to send the samples to the server or whatever
  }
  offlineCtx.startRendering();

In my case, the raw resampled 8000 pcm data is piped into ffmpeg via udp broadcasts like this

ffmpeg -fflags nobuffer -analyzeduration 1M -f f32le -ar 8000 -ac 1 -i udp://127.0.0.1:12000 -ar 44100 -ac 2 -f alsa hw:0

So a websocket server just receives the base64 encoded pcm data, decodes the base64 string and just broadcasts via udp. The result is played back by ffmpeg on the speaker.

Share:
13,718
Mahendra Garg
Author by

Mahendra Garg

Updated on June 04, 2022

Comments

  • Mahendra Garg
    Mahendra Garg about 2 years

    I have audio data in format of data-uri, then I converted this data-uri into a buffer now I need this buffer data in new samplerate, currently audio data is in 44.1khz and I need data in 16khz, and If I recorded the audio using RecordRTC API and if I record audio in low sample rate then I got distorted audio voice, So I am not getting how to resample my audio buffer,

    If any of you any idea regarding this then please help me out.

    Thanks in advance :)

  • notthetup
    notthetup over 9 years
    Quick relavant qn. What's the resampling algorithm used by FF internally?
  • padenot
    padenot over 9 years
    We use the resampler from the speex codec: dxr.mozilla.org/mozilla-central/source/media/libspeex_resamp‌​ler/…, that is optimized for speed and good perceptual quality.
  • Admin
    Admin about 9 years
    A small nitpick, but important: note that this floats[i] * Math.pow(2, 16) / 2 will produce wrong value when floats[i] = 1. Suggesting using: ints[i] = floats[i] < 0 ? floats[i] * 32768 : floats[i] * 32767; (or course, caching floats[i] may help performance wise to avoid double array lookup).
  • Shoib Mohammed A
    Shoib Mohammed A over 8 years
    hi, thanks for the code, i have doubt, I am using recordrtc.js for audio recording, if i convert blob to arraybuffer and intergrate with your code, then oncomplete callback, if i again convert audiobuffer to blob, will it have valid audio file?
  • skunkwerk
    skunkwerk over 8 years
    this code doesn't work for me - I end up with an empty audio file.
  • Dietrich Epp
    Dietrich Epp about 4 years
    The linked library uses linear interpolation, without any bandlimiting, so it is going to introduce aliasing artifacts and may sound quite bad.
  • Vectorjohn
    Vectorjohn almost 4 years
    This sample code has bugs which makes it hard for someone new to the audio APIs to follow. "o.startRendering()" // what is o? "offlineCtx.createBuffer(... buffer.length...)" // what is buffer? That variable doesn't exist. It might seem obvious, but we have multiple buffers holding the same thing only slightly different, so getting the variables correct helps understanding.
  • jxmorris12
    jxmorris12 over 3 years
    This code contains some severe errors. @Satoshi's answer has a functional version.
  • padenot
    padenot over 3 years
    Sorry for not noticing before. This is now fixed, verified, and runs faster (one less copy).
  • sourabh gupta
    sourabh gupta about 3 years
    worked like a charm!! You saved my day. I was trying all the other complicated ways.