background

The last article on Web Audio showed you how to parse Audio data and map the frequency spectrum of Audio. In this article, I will continue my exploration of Web Audio and show you how to collect microphone Audio data in the browser. And how to draw real-time dynamic audio map during the acquisition process.

Implementation approach

Again, let’s get to the general idea. Collection of audio, first of all, we through the navigator. MediaDevices. GetUserMedia method to get the microphone and voice recording. Navigator is an implementation of the browser’s NavigatorID standard interface that can be used to query information about the application running the current script. We won’t talk about this object here, just how to record audio with it.) getUserMedia Records sound to get the audio stream. We then convert the Audio stream to real-time Audio data using Web Audio related apis. Finally, real-time audio data will be drawn into a map by canvas, and the effect of dynamic real-time map will be realized through repeated this process. The overall process is as follows:

Obtain microphone permission to record sound

Browser security policy rules, the navigator. MediaDevices. GetUserMedia method can only effective under the HTTPS protocol or localhost domain name. So here we first use Node to build a minimal version of the local Web server, and then obtain the microphone permission and record sound.

// server.js
const http = require('http');
const path = require('path');
const fs = require('fs');

http.createServer((request, response) = > {
  / / request body
  console.log(new Date(), ':'.JSON.stringify(request));
  // The HTML file to load
  const file = path.resolve(__dirname, './demo2.html');
  // Check whether the file exists
  fs.exists(file, exists= > {
    if(! exists)console.log ('File does not exist, sand sculpture! ');
    else {
      response.writeHeader(200, { "Content-Type" : "text/html" });
      response.end(fs.readFileSync(file, 'utf-8')); }}); }).listen(8090); // Listen on port 8090


/* * demo2.html * Get the microphone and record the sound */
let audioCtx = null; // Audio context
let source = null; / / audio source
let audioStream = null; // The audio stream generated by recording
let analyserNode = null; // A node for analyzing audio real-time data
let animationFrame = null; / / timer

function recordSound () {
  navigator.mediaDevices
    .getUserMedia({ 'audio': true })
    .then(initAudioData)
    .catch(e= > {
      console.log('Something's wrong, sand sculpture :', e);
    });
}

// Stop recording
function stopRecord () {
  // Turn off the microphone
  const tracks = audioStream.getAudioTracks();
  for (let i = 0, len = tracks.length; i < len; i++) {
    tracks[i].stop();
  }
  // Disconnect the audio node
  analyserNode.disconnect();
  source.disconnect();
  analyserNode = null;
  source = null;
  // Clear the timer
  clearInterval(animationFrame);
}

// Event binding
document.querySelector('#record').onclick = recordSound;

document.querySelector('#stop').onclick = stopRecord;



Copy the code

Process Audio stream data using the Web Audio Api

After we get the audio stream, we create the audio source using the AudioContext AudioContext. Choose MediaStreamAudioSourceNode here, and it receives a MediaStream object to create the audio source. Then we insert an audio node between the audio source and destination, which is used to acquire and process the audio data, and then use the data to draw the waveform diagram. AnalyserNode is selected here. Of course, ScriptProcessorNode and AudioWorkletNode can also obtain and process real-time audio data. For details, please refer to the relevant Api.

// Audio data processing
function initAudioData (stream) {
  audioStream = stream;
  // Create audio context
  audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  // Create an audio source
  source = audioCtx.createMediaStreamSource(audioStream);
  // Create an audio analysis node
  analyserNode = audioCtx.createAnalyser();
  // fftSize determines the amount of audio data that can be retrieved
  analyserNode.fftSize = 4096;      
  // Connect the audio source to analyserNode
  source.connect(analyserNode);
  // analyserNode then connects to the loudspeaker to play
  analyserNode.connect(audioCtx.destination);
  // Simply use the timer to draw the waveform, or you can use requestAnimationFrame to repeatedly execute the drawing function at the rate of screen refresh
  animateFrame = setInterval(drawWaver, 60);
}

Copy the code

Real-time acquisition of audio data, waveform drawing

After AnalyserNode is created, you can set the length to capture the audio time domain data in real time at the current point in time, and then repeatedly retrieve the data through a timer or requestAnimationFrame callback to perform the drawing function to achieve the dynamic waveform effect. Again, a Float32Array array object receives audio data (values in the range -1 to 1) and samples the data. In this case, we draw a graph with a maximum value and a minimum value for each 12 data items.

// Draw a graph
function drawWaver () {
  const originData = new Float32Array(analyserNode.fftSize);
  const positives = [];
  const negatives = [];
  // Get current real-time audio data
  analyserNode.getFloatTimeDomainData(originData);
  // Set a maximum value and a minimum value 4096/12 = 341.3333 for each 12 bits of data
  for (let i = 0; i < 341; i++) {
    let temp = originData.slice(i * 12, (i + 1) * 12);
    positives.push(Math.max.apply(null, temp));
    negatives.push(Math.min.apply(null, temp));
  }
  // Create canvas context
  let canvas = document.querySelector('#canvas');
  if (canvas.getContext) {
    let ctx = canvas.getContext('2d');
    canvas.width = positives.length * 4;
    let x = 0;
    let y = 100;
    ctx.fillStyle = '#fa541c';
    // Canvas height is 200, and the abscissa is 100px in the middle of the canvas. Draw positive data above the abscissa and negative data below it
    for (let k = 0; k < positives.length; k++) {
      // Each rectangle is 3px wide and 1px apart, so the total length of the shape is equal to length * 4
      ctx.fillRect(x + 4 * k, y - (100 * positives[k]), 3.100 * positives[k]);
      ctx.fillRect(x + 4 * k, 100.3.100 * Math.abs(negatives[k])); }}}Copy the code

Simple audio capture and real-time mapping are done. Finally, post the effect picture: