Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guest271314/53e00c6765aa256362fb52c08e82d189 to your computer and use it in GitHub Desktop.
Save guest271314/53e00c6765aa256362fb52c08e82d189 to your computer and use it in GitHub Desktop.
Capture monitor devices with navigator.mediaDevices.getUsermedia() at Chromium and Chrome on Linux

Chromium and Chrome refus to list or capture monitor devices on Linux

Virtual microphone using GStreamer and PulseAudio includes a section that describes a workaround using PulseAudio to create a virtual microphone where the source is a monitor device

Remap source

While the null sink automatically includes a "monitor" source, many programs know to exclude monitors when listing microphones. To work around that, the module-remap-source module lets us clone that source to another one not labeled as being a monitor:

pactl load-module module-remap-source \ master=virtmic.monitor source_name=virtmic \ source_properties=device.description=Virtual_Microphone

After we run

$ pactl load-module module-remap-source master="$(pactl list | grep -A2 '^Source #' |  grep 'Name: .*\.monitor$' | awk '{print $NF}' | tail -n1)" source_name=virtmic source_properties=device.description=Virtual_Microphone

we can call navigator.mediaDevices.getUserMedia({audio: true}) to get permission to access device labels, filter the devices from navigator.mediaDevices.enumerateDevices() to get the device 'Virtual_Microphone' then call navigator.mediaDevices.getUserMedia({audio: {deviceId: {exact: deviceId}}}) again, deviceId is the deviceId of 'Virtual_Microphone' with the source of theat virtual microphone set to the monitor device that we want to capture.

@benwiley4000
Copy link

I've been looking everywhere for this, thank you

@guest271314
Copy link
Author

@benwiley4000 Note, during experiments the quality of audio from getUserMedia() is sub-par compared to processing raw PCM https://github.com/guest271314/captureSystemAudio/tree/master/native_messaging/capture_system_audio. I suggest trying both approaches yourself.

@benwiley4000
Copy link

benwiley4000 commented Nov 2, 2021

@guest271314 did you try processing raw PCM from getUserMedia using a media stream source/AudioWorklet(or ScriptProcessorNode) with web audio API? That's what I'm doing although I don't know if there's somehow a loss of quality in the middle. I'm not using a MediaRecorder.

@benwiley4000
Copy link

BTW what is the purpose of the "exact" field? What happens if I pass the device ID without "exact"?

@benwiley4000
Copy link

I can try to compare results with the python script later but in case you're curious here is my app, it lets you record an up-to-10 second clip from any input device and also lets you save the wav recording as a file (which comes from raw PCM): https://benwiley4000.github.io/volca-sampler/

For the source code see:
Main thread: https://github.com/benwiley4000/volca-sampler/blob/master/src/utils/recording.js
AudioWorklet: https://github.com/benwiley4000/volca-sampler/blob/master/public/recorderWorkletProcessor.js

@guest271314
Copy link
Author

did you try processing raw PCM from getUserMedia using a media stream source/AudioWorklet(or ScriptProcessorNode) with web audio API? That's what I'm doing although I don't know if there's somehow a loss of quality in the middle. I'm not using a MediaRecorder.

Yes. https://github.com/guest271314/AudioWorkletStream.

BTW what is the purpose of the "exact" field? What happens if I pass the device ID without "exact"?

To unambiguously set the exact deviceId https://w3c.github.io/mediacapture-main/#constrainable-properties. Monitor devices are not listed by default on Chromium on Linux. We are sure to not get the 'Default' device, or other microphone.

What is the result of your test?

For the source code see:
Main thread: https://github.com/benwiley4000/volca-sampler/blob/master/src/utils/recording.js

Note, I have found using a Blob to store data to be far faster than using Array.prototype.reduce(), see https://bugs.chromium.org/p/chromium/issues/detail?id=1260519.

@benwiley4000
Copy link

benwiley4000 commented Nov 2, 2021 via email

@hasrack
Copy link

hasrack commented Nov 6, 2021

Thanks for the gist. I have a query. I am using pipewire only now. Don't want to use pulseaudio at all. I found something similar for pipewire called pw-loopback. What should be the command for pipewire?

I have created a virtual sink with pw-loopback -m '[ FL FR]' --capture-props='media.class=Audio/Sink node.description=discord-sink node.name=my-sink'
and virtual source using pw-loopback -m '[ FL FR]' --playback-props='media.class=Audio/Source node.description=discord-source node.name=my-source'.

Now patchbay looks like this. I have selected discord source as mic and discord-sink as output device in discord. I want to stream audio from Lollipop music player to discord(google chrome input). How to route this?

Again thanks a lot for the gist.

@guest271314
Copy link
Author

@hasrack I have no experience using Discord, see https://github.com/Lightcord/Lightcord/issues/31.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment