Arbitrary routing in PulseAudio

Recently, I needed to connect people chatting on Skype with another person that I connect to using trx, while keeping me (on the physical microphone and headphones) in the conversation. So we have a three-way chat problem here, with three pairs of input-output devices, where everyone wants to hear everyone else (but not themself).

Fortunately, Emma Joe Anderson wrote a fantastic guide on how to do arbitrary routing in PulseAudio in a very clear and systematic way.

Compared to Emma's excellent post, this blog post just adds a bit of convenience by setting up the virtual devices and the loopback arrows in one shell script, and by making the names of virtual devices visible in configuration tools. This means that the "internal plumbing" will be created completely automatically, and since the devices are now named, you don't have to worry about the order in which you create them.

You will still have to connect your applications to the internal plumbing manually in pulsemixer or pavucontrol. However, this should be easier now that you can see the names of the virtual devices.

Follow Emma's guide

Follow Emma's guide, namely steps Draw the graph you want, Identify the devices, Replace non-devices with virtual sinks, Remove unnecessary virtual sinks.

Stop before Applying a solution.

My routing graph

Here's the graph I obtained by following Emma's procedure.

https://i.imgur.com/ISNXf44.png

It contains two real devices (mic and spk), four virtual devices (v1 .. v4), four applications (rx, tx, skype in, skype out), and six edges between devices. Your graph will be different but I include the picture to illustrate how it relates to the shell script below.

The script

We'll start the configuration script by defining the functions that create virtual sinks and edges (loopbacks) between them:

#!/bin/sh

virt() {
  pactl load-module module-null-sink \
    sink_name="$1" \
    sink_properties=device.description="$1"
}

edge() {
  pactl load-module module-loopback \
    source="$1" \
    sink="$2"
}

Then we define the local microphone & headphone source and sink. You can find yours by running pactl list sources and pactl list sinks. Mine look like this:

mic="alsa_input.usb-0d8c_USB_Sound_Device-00.analog-stereo"
spk="alsa_output.usb-0d8c_USB_Sound_Device-00.analog-stereo"

That's the existing devices defined. Now we create the devices that do not exist yet:

virt v1
virt v2
virt v3
virt v4

Unlike in Emma's guide, you don't need to remember their numbers or ordering because you just give them names.

Finally, make connections between the devices. Microphones are sources, so you can draw an edge from them directly. Our virtual sinks are... well, sinks, so you need to draw edges from their monitors, as shown below:

edge v1.monitor v2
edge v1.monitor $spk
edge $mic v2
edge $mic v3
edge v4.monitor v3
edge v4.monitor $spk

And that's it for the shell script!

When you run it, it'll create all the plumbing. If you want to start over, just restart pulseaudio.

The manual bit

In pulsemixer or pavucontrol, move the output of Skype to v4, the input of Skype to (the monitor of) v2, the output of rx to v1, and the input of tx to (the monitor of) v3.

That's it!