Look up how to make a loopback with the audio system that you have. I use Ubuntu Studio 22.04 LTS, which is the last if I remember right, to not use PipeWire by default. So I have a script that sets up a bunch of loopbacks/bridges in PulseAudio, one end of which connects to JACK.
(
pactl load-module module-jack-sink ...
or
pactl load-module module-jack-source ...
, depending on which direction it's going)
The DAW (Ardour for me, which comes preinstalled in UStudio and "just works", along with a TON of good plugins!) then connects to the JACK side of those loopbacks just like any other device, and OBS and everything else connects to the PulseAudio side of the same loopbacks just like any other device.
Bash:
#!/bin/bash
###########################################
### HOUSEKEEPING AND ERROR-CHECKING ###
###########################################
AUDIO_CHECK="$1"
shift
AUDIO_SETUP="$1"
shift
AUDIO_TEARDOWN="$1"
shift
###############################################
### MORE HOUSEKEEPING AND ERROR-CHECKING ###
###############################################
#
# Wait for Mic, Spk Present
#
echo
"$AUDIO_CHECK"
RESULT=$?
if [[ "$RESULT" != "0" ]]
then
exit $RESULT
fi
#
# Start Jack
#
echo
echo "Start Jack"
qjackctl &
PID_JACK=$!
sleep 5
#
# Create Bridges to/from PulseAudio
#
echo
echo "Create Bridges to/from PulseAudio"
INDEX_SINK_0=$( pactl load-module module-jack-sink channels=2 sink_name=PA_out_Playback client_name=PA_out_Playback )
INDEX_SINK_1=$( pactl load-module module-jack-sink channels=2 sink_name=PA_out_Meet_Rtrn client_name=PA_out_Meet_Rtrn )
INDEX_SOURCE_0=$( pactl load-module module-jack-source channels=2 source_name=PA_in_Mics client_name=PA_in_Mics )
INDEX_SOURCE_1=$( pactl load-module module-jack-source channels=2 source_name=PA_in_Meet_Send client_name=PA_in_Meet_Send )
INDEX_SOURCE_2=$( pactl load-module module-jack-source channels=2 source_name=PA_in_Record client_name=PA_in_Record )
#
# Remove Automatic Connections
#
disconnect_all ()
{
for CONNECTION in $(jack_lsp --connections "$1")
do
if [[ "$CONNECTION" = "$1" ]]
then
continue
fi
jack_disconnect "$1" "$CONNECTION"
done
}
disconnect_all system:capture_1
disconnect_all system:capture_2
disconnect_all system:playback_1
disconnect_all system:playback_2
#
# Save and Set Default Connections
#
PA_DEFAULT_SINK=$( pactl get-default-sink )
PA_DEFAULT_SOURCE=$( pactl get-default-source )
pactl set-default-sink PA_out_Playback
pactl set-default-source PA_in_Mics
#
# Create Bridges to/from Mic, Spk
#
echo
"$AUDIO_SETUP" "/tmp/pid_audio"
PID_AUDIO=$(<"/tmp/pid_audio")
rm "/tmp/pid_audio"
###########################
### START APPS, ###
### WAIT FOR DONE, ###
### CLOSE APPS ###
###########################
#
# Remove Bridges to/from Mic, Spk
#
echo
"$AUDIO_TEARDOWN" $PID_AUDIO
#
# Restore Default Connections
#
echo
echo "Unload Bridges between Jack and PulseAudio"
pactl set-default-sink "$PA_DEFAULT_SINK"
pactl set-default-source "$PA_DEFAULT_SOURCE"
#
# Remove Bridges to/from PulseAudio
#
pactl unload-module $INDEX_SOURCE_2
pactl unload-module $INDEX_SOURCE_1
pactl unload-module $INDEX_SOURCE_0
pactl unload-module $INDEX_SINK_1
pactl unload-module $INDEX_SINK_0
#
# Stop Jack
#
echo
echo "Stop Jack"
kill -TERM "$PID_JACK"
sleep 1
#
# End
#
AUDIO_CHECK
,
AUDIO_SETUP
, and
AUDIO_TEARDOWN
are other scripts. Their filenames/paths are passed as arguments to the main script above, so I can use the same structure with different physical mics and speakers. As an example of how they work:
Audio Check:
Bash:
#!/bin/bash
MIC0=hw:CARD=PowerConf,DEV=0
SPK0=hw:CARD=NVidia,DEV=3
echo
echo "Looking for MIC0: $MIC0"
until arecord -L | grep "$MIC0" > /dev/null
do
sleep 1
done
echo "Found MIC0: $MIC0"
echo "Looking for SPK0: $SPK0"
until aplay -L | grep $SPK0 > /dev/null
do
sleep 1
done
echo "Found SPK0: $SPK0"
Audio Setup:
Bash:
#!/bin/bash
MIC0=hw:CARD=PowerConf,DEV=0
# MIC1=hw:CARD=Receiver_1,DEV=0
# MIC2=hw:CARD=Receiver,DEV=0
SPK0=hw:CARD=NVidia,DEV=3
echo "Use MIC0: $MIC0"
zita-a2j -j MIC0 -d $MIC0 -c 1 -r 16000 &
echo $! >> "$1"
sleep 1
# echo "Additional MIC1: $MIC1"
# if arecord -L | grep "$MIC1" > /dev/null
# then
# echo "Found! Using."
# zita-a2j -j MIC1 -d $MIC1 -c 1 -r 48000 &
# echo $! >> "$1"
# sleep 1
# else
# echo "Not found. Will be unavailable."
# fi
# echo "Additional MIC2: $MIC2"
# if arecord -L | grep "$MIC2" > /dev/null
# then
# echo "Found! Using."
# zita-a2j -j MIC2 -d $MIC2 -c 2 -r 48000 &
# echo $! >> "$1"
# sleep 1
# else
# echo "Not found. Will be unavailable."
# fi
echo "Use SPK0: $SPK0"
zita-j2a -j SPK0 -d $SPK0 -c 2 -r 48000 -p 4096 &
echo $! >> "$1"
sleep 1
The commented-out code is from trying to use several different mics scattered around. Never completely got that to work, and it turned out I didn't need them anyway. But I kept it there so you can see the option.
Audio Teardown:
Bash:
#!/bin/bash
echo "Unuse MIC, SPK"
until [[ "$1" = "" ]]
do
kill -TERM "$1"
shift
sleep 1
done