Access microphone from a browser - Javascript
Asked Answered
D

4

63

Is it possible to access the microphone (built-in or auxiliary) from a browser using client-side JavaScript?

Ideally, it would store the recorded audio in the browser. Thanks!

Dad answered 8/1, 2015 at 17:47 Comment(1)
@poisonlocc Check this out: html5rocks.com/en/tutorials/getusermedia/introMackinnon
L
80

Here we capture microphone audio as a Web Audio API event loop buffer using getUserMedia() ... time domain and frequency domain snippets of each audio event loop buffer are printed (viewable in browser console just hit key F12 or ctrl+shift+i )

<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>capture microphone audio into buffer</title>
 
<script type="text/javascript">
    

  var webaudio_tooling_obj = function () {

    var audioContext = new AudioContext();

    console.log("audio is starting up ...");

    var BUFF_SIZE = 16384;

    var audioInput = null,
        microphone_stream = null,
        gain_node = null,
        script_processor_node = null,
        script_processor_fft_node = null,
        analyserNode = null;

    if (!navigator.getUserMedia)
            navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia || navigator.msGetUserMedia;

    if (navigator.getUserMedia){

        navigator.getUserMedia({audio:true}, 
          function(stream) {
              start_microphone(stream);
          },
          function(e) {
            alert('Error capturing audio.');
          }
        );

    } else { alert('getUserMedia not supported in this browser.'); }

    // ---

    function show_some_data(given_typed_array, num_row_to_display, label) {

        var size_buffer = given_typed_array.length;
        var index = 0;
        var max_index = num_row_to_display;

        console.log("__________ " + label);

        for (; index < max_index && index < size_buffer; index += 1) {

            console.log(given_typed_array[index]);
        }
    }

    function process_microphone_buffer(event) { // invoked by event loop

        var i, N, inp, microphone_output_buffer;

        microphone_output_buffer = event.inputBuffer.getChannelData(0); // just mono - 1 channel for now

        // microphone_output_buffer  <-- this buffer contains current gulp of data size BUFF_SIZE

        show_some_data(microphone_output_buffer, 5, "from getChannelData");
    }

    function start_microphone(stream){

      gain_node = audioContext.createGain();
      gain_node.connect( audioContext.destination );

      microphone_stream = audioContext.createMediaStreamSource(stream);
      microphone_stream.connect(gain_node); 

      script_processor_node = audioContext.createScriptProcessor(BUFF_SIZE, 1, 1);
      script_processor_node.onaudioprocess = process_microphone_buffer;

      microphone_stream.connect(script_processor_node);

      // --- enable volume control for output speakers
          
      document.getElementById('volume').addEventListener('change', function() {

          var curr_volume = this.value;
          gain_node.gain.value = curr_volume;

          console.log("curr_volume ", curr_volume);
      });

      // --- setup FFT

      script_processor_fft_node = audioContext.createScriptProcessor(2048, 1, 1);
      script_processor_fft_node.connect(gain_node);

      analyserNode = audioContext.createAnalyser();
      analyserNode.smoothingTimeConstant = 0;
      analyserNode.fftSize = 2048;

      microphone_stream.connect(analyserNode);

      analyserNode.connect(script_processor_fft_node);

      script_processor_fft_node.onaudioprocess = function() {

        // get the average for the first channel
        var array = new Uint8Array(analyserNode.frequencyBinCount);
        analyserNode.getByteFrequencyData(array);

        // draw the spectrogram
        if (microphone_stream.playbackState == microphone_stream.PLAYING_STATE) {

            show_some_data(array, 5, "from fft");
        }
      };
    }

  }(); //  webaudio_tooling_obj = function()



</script>

</head>
<body>

    <p>Volume</p>
    <input id="volume" type="range" min="0" max="1" step="0.1" value="0.5"/>

    <button onclick="webaudio_tooling_obj()">Listen to mic</button>

</body>
</html>

Since this code exposes microphone data as a buffer you could add ability to stream using websockets or simply aggregate each event loop buffer into a monster buffer then download the monster to a file

Notice the call to

    var audioContext = new AudioContext();

which indicates its using the Web Audio API which is baked into all modern browsers (including mobile browsers) to provide an extremely powerful audio platform of which tapping into the mic is but a tiny fragment ... NOTE the CPU usage jumps up due to this demo writing each event loop buffer into browser console log which is for testing only so actual use is far less resource intensive even when you mod this to stream audio to elsewhere

Links to some Web Audio API documentation

Lido answered 9/1, 2015 at 1:14 Comment(8)
Hi @scott-stensland, this is great. Did you write this code? Is there an original source or repo or license for it? Thanks!Nitwit
@Nitwit Yes its my code ... feel free to pillage at will ... I taught myself above tricks while writing github.com/scottstensland/websockets-streaming-audio which streams audio from the server using websockets to then render in the browser using Web Audio API using a webworker interstitial layer in-between to babysit the network traffic ... I need to reactor that code ;-) as it was strictly a learning exerciseLido
What is a good way to learn all this, i Know javascritp and dom. I dont know audio api and everything that goes with it like buffer size, channels, stream etcPronghorn
Note that now MDN recommends using "the newer navigator.mediaDevices.getUserMedia() instead"Triptolemus
Fyi this snippet causes "The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page." in my Chrome, can be fixed by adding button, for example <button onclick="webaudio_tooling_obj()">Listen to mic</button>Plasm
There is a pair of opening-closing ellipses at the end of the webaudio_tooling_obj () function that must be removed for the code to work, but SO doesn't allow edits of less than 6 chars.Gid
@Gid to execute above code just copy then paste into a filename ending with html say foo.html then open up that file using a browser ... my laptop is Ubuntu 22.04 where it runs fine additionally I just borrowed a Microsoft windows box and confirmed its OK there using same technique ... dunno where you are picking up ellipses ... for this avoid using any bulky over engineered IDE like vscode and stick to a super simple bare bone editor when pasting the code into the html file ... on windows I just used notepad and it worked ... have funLido
@ScottStensland that's exactly what I did. If you copy&paste the code as-is into a .html file and open it in Chrome it will refuse to run it with this message: "mictest.html:9 The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page". The reason is that the line " }(); // webaudio_tooling_obj..." auto-executes when loading the page and as the message says, that's not allowed, it should only be done when doing a user interaction , like pressing a button. Tested on Windows and Fedora.Gid
C
3

Yes you can.

Using the getUserMedia() API, you can capture raw audio input from your microphone.

Cilla answered 8/1, 2015 at 17:49 Comment(1)
I know linking another URL is bad, but it looks like this is the new place: ulistentome.wordpress.com/2014/10/24/…Haywire
M
1

In a secure context, to query the devices.

getUserMedia() is a powerful feature which can only be used in secure contexts; in insecure contexts, navigator.mediaDevices is undefined, preventing access to getUserMedia(). A secure context is, in short, a page loaded using HTTPS or the file:/// URL scheme, or a page loaded from localhost.

async function getMedia(constraints) {
  let stream = null;
  try {
    stream = await navigator.mediaDevices.getUserMedia(constraints);
    console.log(stream)
  } catch(err) {
   document.write(err)
  }
}

getMedia({ audio: true, video: true })

https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia

Microphone answered 17/3, 2022 at 18:33 Comment(0)
E
0

This is a simple way:

//event:
const micButtonClicked = () => {

    //check the access:
    isMicrophoneAllowed(isAllowed => {
        if(isAllowed)
            record();
        else
            navigator.mediaDevices.getUserMedia({audio: true})
            .then(stream => record())
            .catch(err => alert('need permission to use microphone'));
    });
}

//isMicrophoneAllowed:
const isMicrophoneAllowed = callback => {
    navigator.permissions.query({name: 'microphone'})
    .then(permissionStatus => Strings.runCB(callback, permissionStatus.state === 'granted'));
}

//record:
const record = () => {
    // start recording...
}
Everson answered 7/12, 2022 at 12:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.