1

I'm working on an audio recording feature. I usually work with my macbook closed and noticed a bug where if the mac is closed, the built-in microphone still shows up as an audio input but when i record i just get a stream of zeros.

I'm trying to test for this before recording. I do this to get a list of audio inputs:

await navigator.mediaDevices.getUserMedia({ audio: true });
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputs = devices.filter(device => device.kind === 'audioinput');navigator.mediaDevices.getUserMedia({ audio: true });

At this point, audioInputs looks like this:


(2) [InputDeviceInfo, InputDeviceInfo]
0: InputDeviceInfo {
  deviceId: '1da38adda99ecb1795a4860369035175feae346e3793be1ed725f17e9a67d592', 
  kind: 'audioinput', 
  label: 'MacBook Air Microphone (Built-in)', 
  groupId: 'd7b07897adfabc32abc58c44d372664304e0f53d3590f47f8fafc02615a62de8'
}
1: InputDeviceInfo {
  deviceId: 'default', 
  kind: 'audioinput', 
  label: 'Default - MacBook Air Microphone (Built-in)', 
  groupId: 'd7b07897adfabc32abc58c44d372664304e0f53d3590f47f8fafc02615a62de8'
}
length: 2
[[Prototype]]: Array(0)

I don't know why it's showing up twice but i don't mind that for now. What i want to know is how i can tell if one of these is actually on, ie is going to actually record some data.

I thought about doing a short test recording and seeing if the audio stream is all zeros, but i thought there might be a cleaner way to find out in advance. Any suggestions welcome.

2
  • This is a very annoying feature of mac laptops - it's a hardware mute that you can't query, although you can use an undocumented trick to discover if the laptop is closed. However your solution of looking for zeroes is fine and has the added bonus of detecting when a user has muted their mic, either in software or hardware. Commented Aug 18 at 10:37
  • @GordonChilds That's good to know, thanks. I did wonder if it was a 'mac thing' Commented Aug 18 at 10:43

1 Answer 1

1

I found a solution for this but i don't know if it will work in all cases. Based on this very useful blog post https://www.webrtc-developers.com/how-to-know-if-my-microphone-works/

This should be refactored to work in a nicer, more reusable way, but works as a proof of concept i guess. run with testMicInput()

function inspectAudioMeterReadings(audioMeterReadings){
  console.log("audioMeterReadings = ",audioMeterReadings);
  //if this is an array of zeros then recording isn't actually working even if
  //getUserMedia thinks the mic is enabled and not muted etc
  const uniqMeterReadings = [... new Set(audioMeterReadings)];
  // if audioMeterReadings is an array like [0,0,0,0,0,0,etc] 
  // then uniqMeterReadings will be [0], so test for that
  if(uniqMeterReadings.length == 1 && uniqMeterReadings[0] == 0){
    //we got a bad set of data - all zeros
    console.log("audioMeterReadings is all zeros");
    alert("it looks like you don't have an active microphone");
    return false;
  } else {
    console.log("audioMeterReadings looks like a valid range of values");
    return true;
  }
}

async function testMicInput(){
    // Get the stream
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true});
    // Create and configure the audio pipeline
    const audioContext = new AudioContext();
    const analyzer = audioContext.createAnalyser();
    analyzer.fftSize = 512;
    analyzer.smoothingTimeConstant = 0.1;
    const sourceNode = audioContext.createMediaStreamSource(stream);
    sourceNode.connect(analyzer);
    
    //set some vars around recording the sample data
    const sampleLength = 500;
    var startTime, endTime, runTime, audioMeter, frequencyRangeData, sum;
    var audioMeterReadings = [];
    
    startTime = new Date();
    //record at set intervals for sampleLength
    var timer = setInterval(function() {
      // don't run this callback anymore after sampleLength is reached
      endTime = new Date();
      runTime = (endTime.getTime() - startTime.getTime());
      if(runTime > sampleLength){
        console.log("runTime = "+runTime+", stopping recording");
        clearInterval(timer);
        inspectAudioMeterReadings(audioMeterReadings);
      } else {
        // analyse the audio stream data and get a value to push
        // to audioMeterReadings
        frequencyRangeData = new Uint8Array(analyzer.frequencyBinCount);
        analyzer.getByteFrequencyData(frequencyRangeData);
        sum = frequencyRangeData.reduce((p, c) => p + c, 0);
        // audioMeter varies from 0 to 10
        audioMeter = Math.sqrt(sum / frequencyRangeData.length);
        console.log("audioMeter = "+audioMeter);
        audioMeterReadings.push(audioMeter);
      }
    }, 10);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.