Combining Selected Applications' Sound With Voice Into Virtual Microphone (Linux)


Sometimes I'll want to share music with friends via Skype, Discord, etc.

I don't want to share the full output of my sound card (which I can do by setting the corresponding monitor as the microphone), because then my friends can hear themselves (which is annoying), and there might be other sounds playing besides the music (e.g. notifications) that I wouldn't want them to hear either. Also, when I do this, I am no longer able to speak in the call (because my microphone is set to something else).

How can I mix the output sound of some applications with the sound of my microphone, and send that signal over voice chat?

I'm using Linux Mint.



(Below I'll assume the use of a Ubuntu-like Linux distribution --- in my case I'm using Linux Mint 20.1 Cinnamon --- and that Pulse Audio Volume Control is installed; you can install it with sudo apt install pavucontrol. This should work for any Linux distro if you're not doing something too weird, but I make no guarantees. I also figured out the following as I went, so apologies for any inaccuracies.)


First, let me go over some Pulse Audio related concepts, that will make understanding the explanation below easier:



You should also know that, when working with Pulse Audio, there are three main tools: the Pulse Audio Volume Control GUI interface, which you can start from a terminal with pavucontrol, the pactl [1] command, and the pacmd [2] command.


[1] pactl

[2] pacmd


Most of the functionality (that we're interested in) of the pactl command is achieved via built-in "modules". You can call these modules with


pactl load-module <module name> <parameters>


and deactivate them/reverse their effects with


pactl unload-module <module name>


The changes made with this aren't permanent; in a panic just log out and log in again, and you should be able to start fresh.


pactl 's feedback is also pretty poor --- it will semi-silently fail with "Failure: Module initialization failed" whenever there's something wrong with your command --- but you can find a pretty good documentation reference here [3].


[3] PulseAudio modules


OK, so with that, here are the ingredients that we have available and the game plan:



[4] null sink module

[5] loopback module


The plan, then, is the following:



So that putting the plan into action:


0. Finding out our built-in sinks/sources


pactl lets us enumerate our sinks/sources easily:


pactl list sinks


tells us about a single sink


Sink #0
   State: IDLE
   Name: alsa_output.pci-0000_00_1f.3.analog-stereo
   Description: Built-in Audio Analog Stereo
   
   (... more information ...)


which is my sound-card's output, i.e., the laptop's speaker or headphones, and we can find the sources with


pactl list sources


which yields


Source #0
   State: RUNNING
   Name: alsa_output.pci-0000_00_1f.3.analog-stereo.monitor
   Description: Monitor of Built-in Audio Analog Stereo
   
   (... more information ...)
   
   Properties:
       device.description = "Monitor of Built-in Audio Analog Stereo"
       device.class = "monitor"
       alsa.card = "0"
       alsa.card_name = "HDA Intel PCH"

   (... more information ...)

Source #1
   State: RUNNING
   Name: alsa_input.pci-0000_00_1f.3.analog-stereo
   Description: Built-in Audio Analog Stereo
       
   (... more information ...)


I have two sources setup on my computer: alsa_input.pci-0000_00_1f.3.analog-stereo, which is my built-in microphone (coming from the sound-card), and alsa_output.pci-0000_00_1f.3.analog-stereo.monitor which is the monitor of sink alsa_output.pci-0000_00_1f.3.analog-stereo, i.e., the sound coming out of my speakers or headphones as if it were coming into the PC.


1. Create the transmit sink


This is straightforward to do with module-null-sink :


pactl load-module module-null-sink sink_name=transmit


Note that we've named this new sink transmit, but if you look at pavucontrol, under the Ouput Devices tab, you'll find that there is indeed a new device, but that it's called "Null Output". This is because pavucontrol displays the device's description as the display name; if we enumerate our sinks again...


$ pactl list sinks

(... other sinks ...)

Sink #6
   State: IDLE
   Name: transmit
   Description: Null Output
   
   (... more information ...)

   Properties:
       device.description = "Null Output"
       device.class = "abstract"
       device.icon_name = "audio-card"
   Formats:
       pcm


we can see that the sink is indeed called transmit, but its device.description property says "Null Output". We can fix that with pacmd :


pacmd 'update-sink-proplist transmit device.description="Signals to Transmit"'


(If you're getting "Failed to parse proplist." back, note the single quotes [6].)


[6] "Failed to parse proplist"


When we created our null sink transmit, a corresponding monitor source, transmit.monitor, was created as well. (You can check this by calling pactl list sources again.) We should fix its name as well, which we can again do with pacmd :


pacmd 'update-source-proplist transmit.monitor device.description="Monitor of Signals to Transmit"'


Now if you play some music, for example, you'll be able to forward that signal to the new sink under the Playback tag of pavucontrol. That signal is no longer audible though, since it's being played to a null sink; let's fix that.


2. Loopback the sound from transmit


Recall that sound is played out your speakers/headphones when sent to (in my case) sink 0, alsa_output.pci-0000_00_1f.3.analog-stereo. Then if we loopback the sound going into sink transmit into this sink, we should be able to hear it again. Of course, we can't loopback signal from a sink, signal must come from a source. Luckily, monitors are precisely the signal going into a sink, presented as source.


Then let's loopback transmit.monitor to our sound-card, using module-loopback [7] :


pactl load-module module-loopback source=transmit.monitor sink=alsa_output.pci-0000_00_1f.3.analog-stereo


You should now be able to hear the sounds that you send to the "Signals to Transmit" sink again.


[7] loopback module


3. Combine transmit and the microphone


The procedure now is very similar to points 1 and 2; we'll create another null sink that receives both the transmit.monitor signal, and the microphone input. It's the monitor of this signal that will serve as the virtual microphone to use.


We start by creating the null sink combined...


pactl load-module module-null-sink sink_name=combined


... and fixing the default names that appear in pavucontrol...


pacmd 'update-sink-proplist combined device.description="Transmit+Microphone Sink"'
pacmd 'update-source-proplist combined.monitor device.description="Transmit+Microphone"'


... and finally loopbacking both our microphone and transmit monitor into the combined channel:


pactl load-module module-loopback source=alsa_input.pci-0000_00_1f.3.analog-stereo sink=combined
pactl load-module module-loopback source=transmit.monitor sink=combined


4. Profit


Now, when setting up a call, a microphone named "Transmit+Microphone" should be available --- this is the combined signal of your selected sounds and your voice.


Note that all this loopbacking and such may incur in a CPU overhead, but my laptop is not very powerful at all and I had no problems, other than some latency. To undo all of the above, call


pactl unload-module module-loopback
pactl unload-module module-null-sink


or log off and on again.


TL;DR


pactl load-module module-null-sink sink_name=transmit
pacmd 'update-sink-proplist transmit device.description="Signals to Transmit"'
pacmd 'update-source-proplist transmit.monitor device.description="Monitor of Signals to Transmit"'
pactl load-module module-null-sink sink_name=combined
pacmd 'update-sink-proplist combined device.description="Transmit+Microphone Sink"'
pacmd 'update-source-proplist combined.monitor device.description="Transmit+Microphone"'
pactl load-module module-loopback source=alsa_input.pci-0000_00_1f.3.analog-stereo sink=combined
pactl load-module module-loopback source=transmit.monitor sink=combined
pactl load-module module-loopback source=transmit.monitor sink=alsa_output.pci-0000_00_1f.3.analog-stereo


if this failed for you, read the post, but alsa_input.pci-0000_00_1f.3.analog-stereo may be something different for you.


By Miguel M., 27 Jan. 2021



/gemlog/