Routing and mixing audio in Linux
Sound server: PipeWire with pipewire-pulse
Audio router (patchbay): qpwgraph
Audio mixer: PulseAudio Volume Control
media.class
Audio/Sink
: Can be selected as output in PulseAudio applicationsAudio/Source/Virtual
: Can be selected as input in PulseAudio applications
Topography
Microphone -\
-> Virtual sink -> Virtual source -> Voice software
Music player -> Virtual monitor sink -<
-> Speaker
Create the virtual sink
Combines the audio from all the desired sources.
pactl load-module module-null-sink media.class=Audio/Sink sink_name=virtual-sink channel_map=stereo
Create the virtual source
Sends the combined audio to the desired applications.
pactl load-module module-null-sink media.class=Audio/Source/Virtual sink_name=virtual-mic channel_map=front-left,front-right
Create the virtual monitor sink
For audio applications to be mixed, sends the audio to the virtual sink as well as the speaker.
pactl load-module module-null-sink media.class=Audio/Sink sink_name=virtual-monitor-sink channel_map=stereo
Then, in PulseAudio Volume Control, go to the Playback tab and set virtual-monitor-sink Audio/Sink sink
as the playback device of the applications to be mixed.
Connect the heck out of it in qpwgraph.
Alternative persistent configuration
Create ~/.config/pipewire/pipewire.conf.d/99-route-n-mix.conf
with the following:
# Daemon config file for PipeWire version "1.2.7" #
#
# Copy and edit this file in /etc/pipewire for system-wide changes
# or in ~/.config/pipewire for local changes.
#
# It is also possible to place a file with an updated section in
# /etc/pipewire/pipewire.conf.d/ for system-wide changes or in
# ~/.config/pipewire/pipewire.conf.d/ for local changes.
#
context.objects = [
#{ factory = <factory-name>
# ( args = { <key> = <value> ... } )
# ( flags = [ ( nofail ) ] )
# ( condition = [ { <key> = <value> ... } ... ] )
#}
#
# Creates an object from a PipeWire factory with the given parameters.
# If nofail is given, errors are ignored (and no object is created).
# If condition is given, the object is created only when the context properties
# all match the match rules.
#
#{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } }
#{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
#{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } }
#{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } }
#{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } }
#{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }
# This creates a new Source node. It will have input ports
# that you can link, to provide audio for this source.
#{ factory = adapter
# args = {
# factory.name = support.null-audio-sink
# node.name = "my-mic"
# node.description = "Microphone"
# media.class = "Audio/Source/Virtual"
# audio.position = "FL,FR"
# monitor.passthrough = true
# }
#}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "mix-sink"
node.description = "Sink"
media.class = "Audio/Sink"
audio.position = "FL,FR"
monitor.channel-volumes = true
monitor.passthrough = true
adapter.auto-port-config = {
mode = dsp
monitor = true
position = preserve
}
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "mix-monitored-sink"
node.description = "Monitored Sink"
media.class = "Audio/Sink"
audio.position = "FL,FR"
monitor.channel-volumes = true
monitor.passthrough = true
adapter.auto-port-config = {
mode = dsp
monitor = true
position = preserve
}
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = "b1"
node.description = "B1"
media.class = "Audio/Source/Virtual"
audio.position = "FL,FR"
monitor.channel-volumes = true
monitor.passthrough = true
adapter.auto-port-config = {
mode = dsp
monitor = true
position = preserve
}
}
}
]
In qpwgraph > Graph > General, check “Start minimized to system tray”. Then, activate the patchbay.
Add qpwgraph to startup applications.
Log out and log in again to take effect.
Reference
https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices
https://linuxmusicians.com/viewtopic.php?t=27623
ChatGPT