pactl record this app
PulseAudio is powerful
I remember when PulseAudio was this new thing coming to linux distros everywhere. People hated it, exactly like systemd, as it changed what was working. I remember having some trouble dealing with it, mostly as all documentation around was based on alsa, but now it has been accepted and there’s plenty docs and apps supporting it. Thanks to that I can make it work pretty much any way I need it to.
One of the coolest things for me is that you can mix and match inputs and outputs with it. The problem is that the default tools are too simple to make it happen. Yes, KISS (Keep it Simple Stupid ™), but what I need is also simple, at least for me.
Some time ago I had to record some webinars, but couldn’t stop working as I recorded them. Even though I was using OBS and could record my whole second screen, while working on my first, people would not want to hear telegram notifications that were not actually coming from their system. Also often a window or something else would pop in front of the recording. Just not great.
I’ve ended up learning how to record a single app (like my browser window), and just the audio from this app. First you create a “null sink” and direct the audio of the app to that sink, then you can set OBS to record that sink instead of all system sound. Works great, but then you can’t hear that app yourself. Sure you can fix it with PulseAudio, and the following commands will do that:
# create the null sink
pactl load-module module-null-sink sink_name=\"recording\" sink_properties=device.description=\"rec_passthrough\"
# find the sink ID for the application you want
pactl list | grep -B 30 firefox
# make it the input for the null sink you created
pactl move-sink-input 280 recording
# add a loopback monitor for a device that should output that sound to you (speakers or headphones)
pactl load-module module-loopback source=recording.monitor sink=alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo rate=44100
# repeat if you want to hear simultaneously from more than one device (bonus!)
pactl load-module module-loopback source=recording.monitor sink=alsa_output.pci-0000_00_1f.3.analog-stereo rate=44100
# when not needed anymore, find the IDs of the modules you created and remove them
pactl list | less
pactl unload-module 1
pactl unload-module 2
pactl unload-module 3
# note that the commands that load modules output their ID, so you could also take note of these to remove later
it’s dangerous to go alone, take this
#!/bin/bash
# Script to make it quick to create a sink that isolates
# system sound from an app sound for desktop recordings
# with OBS. It also "reappends" the application to the
# outputs so that it can be heard as well as recorded.
function usage {
echo "Usage: $0 [-h] [-s SINKNAME] [-r|-l|-p APPNAME]
-h Print this usage text and exits.
-s SINKNAME Specify a sink to be used as output excusively.
-r Remove loaded modules and sinks (undo changes).
-l Lists available processes and output sinks.
-n RECSINKNAME Specify recording sink name.
-p PROCESS Specify the process name that should have its sound
monitored."
}
COMMAND=""
RECSINKNAME="recording_sink"
while getopts "hp:ln:rs:" ARG; do
case $ARG in
r)
COMMAND="remove"
;;
p)
COMMAND="pass"
APPNAME=$OPTARG
;;
s)
OUTSINK="$OPTARG"
;;
l)
COMMAND="list"
;;
n)
RECSINKNAME="$OPTARG"
;;
h)
usage
exit 1
;;
esac
done
if [[ $# -gt 2 || $# -eq 0 || $COMMAND == "" ]]
then
usage
exit 1
fi
if [[ -z $OUTSINK ]]
then
export OUTSINK=$(pactl list sinks | grep -A 3 "Sink #" | grep "Name: " | sed 's/.*Name: //')
fi
if [[ "$COMMAND" == "pass" ]]
then
LINES=1
FOUND=0
SINKNUMBER=""
while [[ "$FOUND" -eq 0 ]]
do
BUFFER=$(pactl list sink-inputs | grep -B $LINES "application.process.binary = \"$APPNAME\"")
if [[ -z "$BUFFER" ]]
then
echo "No sink found for such process name: $APPNAME"
exit 2
fi
if [[ "$(echo $BUFFER | head -n 3)" =~ "Sink Input" ]]
then
SINKNUMBER=$(echo "$BUFFER" | grep "Sink Input" | sed 's/.*#\(.*\)$/\1/')
FOUND=1
break
else
LINES=$(( $LINES + 3 ))
fi
done
echo "Found sink as $SINKNUMBER"
pactl load-module module-null-sink sink_name=\"$RECSINKNAME\" sink_properties=device.description=\"rec_passthrough\" >> /tmp/addedmodules.list
pactl move-sink-input $SINKNUMBER $RECSINKNAME
for SINK in $OUTSINK
do
pactl load-module module-loopback source=$RECSINKNAME.monitor sink=$SINK rate=44100 >> /tmp/addedmodules.list
done
elif [[ "$COMMAND" == "remove" ]]
then
if [[ -f /tmp/addedmodules.list ]]
then
for MODULENUMBER in $(cat /tmp/addedmodules.list)
do
pactl unload-module $MODULENUMBER
done
rm /tmp/addedmodules.list
else
echo "Could not find added modules list."
fi
else
echo "Possible processes:"
pactl list sink-inputs | grep -e "application.process.binary\|Sink Input"
echo "Possible outputs:"
pactl list sinks | grep -A 3 "Sink #" | grep -v "State:\|--"
fi
It’s GPL… Adapt to your needs, if you want it…
Until the next one!