Nikita Skobov - July 05, 2020
Recently I took on a fun side project where I would like to record the audio of some program while listening to it at the same time. I am going to show you two ways of accomplishing this:
We can accomplish this using parec
.
I am currently on an arch-based distribution, and on my system parec
comes from the libpulse library, which is a dependency of pulseaudio, as well as many other programs. However, I believe parec
should be available on most linux distributions by default.
With parec
, we have several ways of recording audio. Probably the easiest, and most powerful, is to record the audio of a single sink input
, which is typically just a program. To find out what sink inputs are available, start running some software that plays audio (don't use a browser because I believe some browsers bypass pulseaudio and play their sound directly through ALSA), and then do:
pacmd list-sink-inputs
Here is some sample output:
1 sink input(s) available.
index: 289
driver: <protocol-native.c>
flags: START_CORKED
state: CORKED
sink: 0 <alsa_output.pci-0000_00_1b.0.analog-stereo>
volume: front-left: 0 / 0% / -inf dB, front-right: 0 / 0% / -inf dB
balance 0.00
muted: no
current latency: 0.00 ms
requested latency: 75.00 ms
sample spec: s16le 2ch 48000Hz
channel map: front-left,front-right
Stereo
resample method: speex-float-1
module: 9
client: 527 <VirtualBox>
properties:
media.name = "VirtualBox Front"
application.name = "VirtualBox"
native-protocol.peer = "UNIX socket client"
native-protocol.version = "33"
application.process.id = "231011"
application.process.user = "admin"
application.process.host = "admin-az42ce6"
application.process.binary = "VirtualBoxVM"
application.language = "en_US.UTF-8"
window.x11.display = ":0"
application.process.machine_id = "13dab296a66b2f35aa308c4ad730d9f2"
application.process.session_id = "2"
module-stream-restore.id = "sink-input-by-application-name:VirtualBox"
The important thing we want here is the index at the top, in this case it is 289.
We can then record the audio that is coming from this program, without any other sounds from any other program using:
parec --monitor-stream=289 --file-format=wav sample1.wav
# then hit ctrl+c to stop the recording
This by itself is pretty powerful!
This lets us record the audio of a program, while being able to listen to it, and while being able to listen to other sounds simultaneously, and without the extra sounds showing up in our recording.
The above method works fine, but it has one issue that was bothering me:
If you change the volume of that program, the volume change shows up in the recording
This is inconvenient, and made me look into alternative ways of recording program output. After some searching around, I found that you can tell pulseaudio to redirect sink inputs to different sinks (by default you probably just have one sink which is your master volume). You can then redirect a sink into a series of "null" sinks, and finally out into your default sink where you will actually be able to hear the output. The magic of this method is that we will be able to record our audio from one of the first sinks, and then adjust volume on the sink that it feeds into. Any volume adjustment on the downstream sink will not have any effect on the recording. Consider this diagram:
we can record here and adjust the volume here
| |
v v
+------------+ +------------------+ +------------------+ +--------------+
| sink input |-->| null_sink_record |-->| null_sink_listen |-->| default_sink |
+------------+ +------------------+ +------------------+ +--------------+
because the null_sink_listen
comes after the null_sink_record
, the volume adjustments will not be reflected in the recording.
We will first find the name of our default sink:
pacmd list-sinks
The output looks something like this:
1 sink(s) available.
* index: 0
name: <alsa_output.pci-0000_00_1b.0.analog-stereo>
driver: <module-alsa-card.c>
flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY DYNAMIC_LATENCY
state: RUNNING
# more stuff below omitted for brevity
You will probably just have one sink, but if you have more, the default one should be the one with an asterisk (*) by it.
We remember its name (everything between the angle brackets) and proceed to make two null sinks:
pacmd load-module module-null-sink sink_name=null_sink_record sink_properties=device.description=nullsinkrecord
pacmd load-module module-null-sink sink_name=null_sink_listen sink_properties=device.description=nullsinklisten
The sink_properties=device.description
is optional, but I think it's good to have because some applications will use this when displaying the sink. You can then look at this property to tell which sink is which.
When making null sinks, pacmd
will also create a source, and name it <null_sink_name>.monitor
, you can verify this by running
pacmd list-sources
You should see at least 2 sources there, one being named null_sink_record.monitor
and the other null_sink_listen.monitor
. We will be using these named sources for our recording soon, as well as for redirecting our output.
We start by pointing the listen sink into our default sink:
pacmd load-module module-loopback source="null_sink_listen.monitor" sink="alsa_output.pci-0000_00_1b.0.analog-stereo"
Then we do the same, but we point the record sink into the listen sink:
pacmd load-module module-loopback source="null_sink_record.monitor" sink="null_sink_listen"
Now, we can move our actual sink input to use the record null sink instead of the default sink:
# remember 289 is the index of the sink input we wish
# to redirect
pacmd move-sink-input 289 null_sink_record
And finally, let's record our audio. parec
has different ways of recording the audio. Previously, we used the --monitor-stream=
option which lets us choose a sink input to record via its index. But numbers are hard to remember, and inconvenient to script, so now since we have named sinks and similarly named sources, lets use the -d
or --device
option to record from the source:
# remember to use the NAME of the null sink source, not the description
parec -d null_sink_record.monitor --file-format=wav sample2.wav
And once again, if you hit ctrl+c
you can stop the recording, and test the output. But wait, we haven't done the interesting part yet!
As of right now, everything should work the same as before, but let's try adjusting the volume and seeing if the volume change shows up in the recording.
Unfortunately, you cannot use the program's volume slider since that will affect the volume of the sink input. Instead, you will need to modify the volume of the null_sink_listen
null sink. You can do that with pacmd, but that is verbose and nobody (at least I hope!) changes their volume by calling pacmd set-sink-volume
. Instead, let's use a user interface with a slider to adjust the volume.
Unfortunately there are few programs that let you adjust sink volume instead of sink-input volume, but I found a pretty good one that does allow that: pulsemixer.
just follow their download/install instructions, or find it on your distributions package manager (on arch it is here).
Then simply run it with pulsemixer
And here you will see something like this if you followed the directions:
Under nullsinkrecord
mine says 440 Hz Sine because thats the 'program' that I was testing with, but yours will be different.
You can now adjust the volume of the nullsinklisten
sink, and your recording volume should not change.
You can change either the volume of the null sink: nullsinklisten
or its source monitor that appears under it. Both should work, but Ive noticed when adjusting the monitor volume I get a scratchy sound, but I don't get that sound when adjusting the sink volume.
Also, you might be thinking: why even have a record sink, and not just record from the sink input like we did in the first example?
Well, you're right. We don't need it. But I included it in this example to give you a slightly better understanding of the things you can do with pulseaudio. Also I liked making that diagram above
:)
About me:
I am Nikita Skobov.
Contact me via email: skobo002@umn.edu.
Check out my projects: https://github.com/nikita-skobov.
Check out my other blog posts: https://blog.nikitas.link.