Audio Routing on Linux

19 Jun 24


Goal

I'm trying out running linux on my main desktop. One of the programs I have to replace is Voicemeeter. This program routes audio with software. The main point is having audio come out of my speakers and headphones at the same time so I don't have to keep switching the output in a settings menu. I can also route other inputs and program audio into a virtual microphone. This lets me listen to my cassette deck through the computer or even pipe it into my microphone so everyone on Discord with me can hear.

There is no version of voicemeeter for linux, so I had to find another solution. The solution needs to have a virtual input and output that apps will default to. I should then be able to route these virtual devices to the actual hardware inputs or outputs. Having other features like effects wasn't super important, but something to consider.

pipewire & qpwgraph

The solution I found was a program called qpwgraph. This is a GUI for routing PipeWire connections. PipeWire is the software that actually handles the audio.

I found the PipeWire documentation page that lists ways to make virtual devices. The commands listed appear to start a process that handles everything. If you close the terminal or press ctrl-C, the device disappears. My solution is to put the commands in a script that runs them in the background. I also added this script to the autostart list.

pw-loopback -m '[ FL FR ]' --capture-props='media.class=Audio/Sink node.name=virt-out node.description=virtual-output' &
pw-loopback -m '[ FL FR ]' --playback-props='media.class=Audio/Source node.name=virt-mic node.description=virtual-mic' &

With these running, I set the default audio devices to the virtual ones. In qpwgraph, I routed the virtual devices to hardware devices. For whatever reason, qpwgraph will not save/remember/connect the 2nd hardware output. I had to open it and reconnect one of them every time I boot. To solve this, I added onto the startup script a few lines to explicitly connect devices. This was behaving weird until I put a sleep statement in. I guess something has to be running already for these to work. Here is the final script:

sleep 3
# make virtual devices for normal mixing/splitting
pw-loopback -m '[ FL FR ]' --name='virtual-speaker' --capture-props='media.class=Audio/Sink' &
pw-loopback -m '[ FL FR ]' --name='virtual-mic' --playback-props='media.class=Audio/Source' &
sleep 3

# connect virtual output to real speakers
pw-link virtual-speaker:output_FL alsa_output.usb-Generic_USB_Audio-00.analog-stereo:playback_FL
pw-link virtual-speaker:output_FR alsa_output.usb-Generic_USB_Audio-00.analog-stereo:playback_FR
pw-link virtual-speaker:output_FL alsa_output.pci-0000_0b_00.6.analog-stereo:playback_FL
pw-link virtual-speaker:output_FR alsa_output.pci-0000_0b_00.6.analog-stereo:playback_FR

# connect headphone microphone to virtual input
pw-link alsa_input.usb-Generic_USB_Audio-00.analog-stereo:capture_FL virtual-mic:input_FL
pw-link alsa_input.usb-Generic_USB_Audio-00.analog-stereo:capture_FR virtual-mic:input_FR

# start PipeWire graph GUI tool
qpwgraph -m


Comments