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 command, and the pacmd command.
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.
OK, so with that, here are the ingredients that we have available and the game plan:
The plan, then, is the following:
So that putting the plan into action:
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.
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.)
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.
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 :
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.
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
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.
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.