Send arbitrary audio along with your mic sound using PulseAudio

Vincent Lepot
4 min readJan 26, 2021

--

Why?

One thing that makes me laugh is to use typical quotations of movies or series I like. Before Covid-19 put us all at home, I had the bad habit to have keyboard shortcuts triggering such sounds.

But now we are at home, I missed this capability to annoy my co-workers with my bad habit… So I wanted to achieve the same thing in Zoom 😁

What we want to achieve?

What we wish is dual:

  1. We want to send in Zoom both the mic input and the VLC output
  2. We want to have the same thing in the headphones… because we want to hear our jokes too of course :)

How do we do this?

Pre-requisite:

  • Have a recent Linux distribution,
  • Have PulseAudio properly configured (I mean, you should be able to hear sounds you play and your mic should be able to capture your voice, easy!)
  • Have a sound player installed (I am using VLC here but any sound player should do the trick)
  • Have Zoom installed (but actually this should work with other softwares too, I just haven’t tested yet :) )

Warning ! Use a headphones and a microphone separated from each other when manipulating to avoid the Larsen effect.

Source, sinks and monitors

In order to understand how we will achieve this, let’s explain some basic concepts in PulseAudio.

Source: Describes a sound input like a microphone for instance, but it can also represent a virtual source as we will see later. You can see them in the “Input Devices” tab of pavucontrol.

Sink: Describes a sound output like headphones or speakers for instance, but it can also represent a virtual output in which we will send sound. You can see them in the “Output Devices” tab of pavucontrol.

Monitor: This concept allows to take the sound sent to a sink as a source itself. You can see them in the “Input Devices” tab of pavucontrolwhen selecting “All Input Devices” in the Show list.

Let’s do it

First let’s setup some pipelines manually with some CLI commands.

# We create 2 new sinks in which we can send some sound: 1 to be used by VLC and one which will aggregate both the mic and the VLC output
pactl load-module module-null-sink sink_name=zoom sink_properties=device.description=zoom
pactl load-module module-null-sink sink_name=media sink_properties=device.description=media

# We use a loopback to send the VLC content VLC to Zoom
pactl load-module module-loopback source=media.monitor sink=zoom

# We also use a loopback to send the default input (the mic) to Zoom
pactl load-module module-loopback sink=zoom

# Because it's easier to follow, we also send the VLC content in the default output (in my case, my headphones)
pactl load-module module-loopback source=media.monitor

# Because Zoom is not able to choose a monitor input, we create a fake source cloning the monitor
pactl load-module module-remap-source master=zoom.monitor source_name=zoom_mic source_properties=device.description="zoom_mic"

We have now a configuration like this.

Now this plumbing is all set up, launch VLC and play a long sound, so that you have time to go to pavucontrol and choose for VLC to output to media

Let’s now launch Zoom and select zoom_mic as the input.

You can record your meeting to check the result.

Caveats

As you will quickly see if you record yourselves, the image and the sound may not be completely in sync. As a matter of fact, the loopback is not a 0-cost action, and it induces a delay between the source and the sink of about half a second. You can see this when looking at the level bar of the input between your default input and monitor of zoomin pavucontrol.

There is surely a better way of doing, maybe by just cloning the monitor of your default output… but this means I would not have the control on what is sent to zoom (like system sounds due to notifications for instance). So I prefer it this way for now.

Now I love this, let’s make it persistent

You can add the following lines to your default.pa configuration (either in ~/.config/pulse/ or /etc/pulse depending on your current configuration):

# We create 2 new sinks in which we can send some sound: 1 to be used by VLC and one which will aggregate both the mic and the VLC output
load-module module-null-sink sink_name=zoom sink_properties=device.description=zoom
load-module module-null-sink sink_name=media sink_properties=device.description=media

# We use a loopback to send the VLC content VLC to Zoom
load-module module-loopback source=media.monitor sink=zoom

# We also use a loopback to send the default input (the mic) to Zoom
load-module module-loopback sink=zoom

# Because it's easier to follow, we also send the VLC content in the default output (in my case, my headphones)
load-module module-loopback source=media.monitor

# Because Zoom is not able to choose a monitor input, we create a fake source cloning the monitor
load-module module-remap-source master=zoom.monitor source_name=zoom_mic source_properties=device.description="zoom_mic"

(Those are the same lines as command line but without the pactl command)

--

--

Vincent Lepot
0 Followers

Former baby, former child, former student, former young developer, now tech lead. Code and beer fan, silly sometimes.