最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Reducing sample rate of a Web Audio spectrum analyzer using mic input - Stack Overflow

programmeradmin7浏览0评论

I'm using the Web Audio API to create a simple spectrum analyzer using the puter microphone as the input signal. The basic functionality of my current implementation works fine, using the default sampling rate (usually 48KHz, but could be 44.1KHz depending on the browser).

For some applications, I would like to use a lower sampling rate (~8KHz) for the FFT.

It looks like the Web Audio API is adding support to customize the sample rate, currently only available on FireFox ().

Adding sample rate to the context constructor:

// create AudioContext object named 'audioCtx'
var audioCtx = new (AudioContext || webkitAudioContext)({sampleRate: 8000,});
console.log(audioCtx.sampleRate)

The console outputs '8000' (in FireFox), so it appears to be working up to this point.

The microphone is turned on by the user using a pull-down menu. This is the function servicing that pull-down:

var microphone;
function getMicInputState()
{
  let selectedValue = document.getElementById("micOffOn").value;
  if (selectedValue === "on") {
    navigator.mediaDevices.getUserMedia({audio: true})
      .then(stream => {
        microphone = audioCtx.createMediaStreamSource(stream);
        microphone.connect(analyserNode);
      })
      .catch(err => { alert("Microphone is required."); });
  } else {
    microphone.disconnect();
  }
}

In FireFox, using the pulldown to activate the microphone displays a popup requesting access to the microphone (as normally expected). After clicking to allow the microphone, the console displays:

"Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported".

The display of the spectrum analyzer remains blank.

Any ideas how to overe this error? If we can get past this, any guidance on how to specify sampleRate when the user's soundcard sampling rate is unknown?

I'm using the Web Audio API to create a simple spectrum analyzer using the puter microphone as the input signal. The basic functionality of my current implementation works fine, using the default sampling rate (usually 48KHz, but could be 44.1KHz depending on the browser).

For some applications, I would like to use a lower sampling rate (~8KHz) for the FFT.

It looks like the Web Audio API is adding support to customize the sample rate, currently only available on FireFox (https://developer.mozilla/en-US/docs/Web/API/AudioContextOptions/sampleRate).

Adding sample rate to the context constructor:

// create AudioContext object named 'audioCtx'
var audioCtx = new (AudioContext || webkitAudioContext)({sampleRate: 8000,});
console.log(audioCtx.sampleRate)

The console outputs '8000' (in FireFox), so it appears to be working up to this point.

The microphone is turned on by the user using a pull-down menu. This is the function servicing that pull-down:

var microphone;
function getMicInputState()
{
  let selectedValue = document.getElementById("micOffOn").value;
  if (selectedValue === "on") {
    navigator.mediaDevices.getUserMedia({audio: true})
      .then(stream => {
        microphone = audioCtx.createMediaStreamSource(stream);
        microphone.connect(analyserNode);
      })
      .catch(err => { alert("Microphone is required."); });
  } else {
    microphone.disconnect();
  }
}

In FireFox, using the pulldown to activate the microphone displays a popup requesting access to the microphone (as normally expected). After clicking to allow the microphone, the console displays:

"Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported".

The display of the spectrum analyzer remains blank.

Any ideas how to overe this error? If we can get past this, any guidance on how to specify sampleRate when the user's soundcard sampling rate is unknown?

Share Improve this question edited Jun 10, 2020 at 16:03 WestCoastProjects 63.4k106 gold badges364 silver badges626 bronze badges asked Oct 12, 2018 at 22:01 eadgbeeadgbe 1032 silver badges9 bronze badges 1
  • I'm curious what the reason is. You could simply discard everything above 4 kHz in the output. – Brad Commented Oct 20, 2018 at 22:25
Add a ment  | 

2 Answers 2

Reset to default 5

One approach to overe this is passing audio packets captured from microphone to analyzer node via a script processor node that re-samples the audio packets passing through it.

Brief overview of script processor node

  • Every script processor node has an input buffer and an output buffer.
  • When audio enters the input buffer, the script processor node fires onaudioprocess event.
  • Whatever is placed in the output buffer of script processor node bees its output.
  • For detailed specs, refer : Script processor node

Here is the pseudo-code:

  1. Create live media source, script processor node and analyzer node

  2. Connect live media source to analyzer node via script processor node

  3. Whenever an audio packet enters the script processor node, onaudioprocess event is fired

  4. When onaudioprocess event is fired :

    4.1) Extract audio data from input buffer

    4.2) Re-sample audio data

    4.3) Place re-sampled data in output buffer

The following code snippet implements the above pseudocode:

var microphone;
// *** 1) create a script processor node
var scriptProcessorNode = audioCtx.createScriptProcessor(4096, 1, 1);

function getMicInputState()
{
 let selectedValue = document.getElementById("micOffOn").value;
 if (selectedValue === "on") {
   navigator.mediaDevices.getUserMedia({audio: true})
     .then(stream => {
       microphone = audioCtx.createMediaStreamSource(stream);
       // *** 2) connect live media source to analyserNode via script processor node
       microphone.connect(scriptProcessorNode); 
       scriptProcessorNode.connect(analyserNode);
     })
     .catch(err => { alert("Microphone is required."); });
 } else {
   microphone.disconnect();
 }
}

// *** 3) Whenever an audio packet passes through script processor node, resample it
scriptProcessorNode.onaudioprocess = function(event){
   var inputBuffer = event.inputBuffer;
   var outputBuffer = event.outputBuffer;
   for(var channel = 0; channel < outputBuffer.numberOfChannels; channel++){
     var inputData = inputBuffer.getChannelData(channel);
     var outputData = outputBuffer.getChannelData(channel);
     
     // *** 3.1) Resample inputData
     var fromSampleRate = audioCtx.sampleRate;
     var toSampleRate = 8000;
     var resampledAudio = downsample(inputData, fromSampleRate, toSampleRate);
     
     // *** 3.2) make output equal to the resampled audio
     for (var sample = 0; sample < outputData.length; sample++) {
       outputData[sample] = resampledAudio[sample];      
     }
   }
}

function downsample(buffer, fromSampleRate, toSampleRate) {
   // buffer is a Float32Array
   var sampleRateRatio = Math.round(fromSampleRate / toSampleRate);
   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;
}

Update - 03 Nov, 2020

Script Processor Node is being deprecated and replaced with AudioWorklets.

The approach to changing the sample rate remains the same.

Downsampling from the constructor and connecting an AnalyserNode is now possible in Chrome and Safari.

So the following code, taken from the corresponding MDN documentation, would work:

  const audioContext = new (window.AudioContext || window.webkitAudioContext)({
    sampleRate: 8000
  });

  const mediaStream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: false
  });
  const mediaStreamSource = audioContext.createMediaStreamSource(mediaStream);

  const analyser = audioContext.createAnalyser();
  analyser.fftSize = 256;
  const bufferLength = analyser.frequencyBinCount;
  const dataArray = new Uint8Array(bufferLength);

  analyser.getByteFrequencyData(dataArray);
  mediaStreamSource.connect(analyser);

  const title = document.createElement("div");
  title.innerText = `Sampling frequency 8kHz:`;
  const wrapper = document.createElement("div");
  const canvas = document.createElement("canvas");
  wrapper.appendChild(canvas);

  document.body.appendChild(title);
  document.body.appendChild(wrapper);
  const canvasCtx = canvas.getContext("2d");

  function draw() {
    requestAnimationFrame(draw);

    analyser.getByteFrequencyData(dataArray);

    canvasCtx.fillStyle = "rgb(0, 0, 0)";
    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

    var barWidth = canvas.width / bufferLength;
    var barHeight = 0;
    var x = 0;

    for (var i = 0; i < bufferLength; i++) {
      barHeight = dataArray[i] / 2;

      canvasCtx.fillStyle = "rgb(" + (2 * barHeight + 100) + ",50,50)";
      canvasCtx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight);

      x += barWidth + 1;
    }
  }

  draw();

See here for a demo where both 48kHz and 8kHz sampled signal frequencies are displayed: https://codesandbox.io/s/vibrant-moser-cfex33

发布评论

评论列表(0)

  1. 暂无评论