FreeBSD allows us to easily switch between audio devices using the command “sysctl hw.snd.default_unit”. We just need to check the available devices:
pcm0: <Intel Haswell (HDMI/DP 8ch)> (play)
pcm1: <Realtek ALC292 (Analog 2.0+HP/2.0)> (play/rec) default
pcm2: <Realtek ALC292 (Internal Analog Mic)> (rec)
pcm3:
Installed devices from userspace:
And perform the appropriate assignment:
The issue with this method is that each time we switch devices, we have to restart the open applications so they can recognize the new audio device.
To avoid this inconvenience, we can use virtual_oss, a service that multiplexes and demultiplexes audio devices. The point is that applications will always open the device created by virtual_oss, which then routes the audio to the final device.
We install virtual_oss:
We enable the service:
We configure the parameters for virtual_oss. In my case, I’m using a laptop where the speakers are represented by the device pcm1, which corresponds to dsp1:
-T devname Install entry in /dev/sndstat
-C num Set the maximum number of mix channels to num.
-c num Set mix channels for the subsequent commands.
-S Enable automatic DSP rate resampling.
-i priority Set real-time priority to priority. Refer to rtprio(1) for more information.
Priority is an integer between 0 and RTP_PRIO_MAX (usually 31). 0 is the highest priority 31 the lowest.
-r rate Set default sample-rate for the subsequent commands.
-b bits Set sample depth to bits for the subsequent commands. Valid values are 8, 16, 24 and 32.
-s value Set default buffer size to value. If the argument is suffixed by "ms" it is interpreted as milliseconds.
Else the argument gives number of samples. The buffer size specified is per channel. If there are multiple
channels, the total buffer size will be larger.
-f devname Set both playback and recording DSP device
-d name Create an OSS device by given name.
-t devname Set control device name.
Both input and output audio devices can be defined using a single parameter:
-f devname Set both playback and recording DSP device
Or independently:
-P devname Set playback DSP device only. Specifying /dev/null is magic and
means no playback device. Specifying a sndio(7) device
descriptor prefixed by "/dev/sndio/" is also magic, and will use
a sndio backend rather than an OSS device.
-R devname Set recording DSP device only. Specifying /dev/null is magic and
means no recording device.
We start the service:
We check the available devices and see that there is one more device under userspace section:
Installed devices:
pcm0: <Intel Haswell (HDMI/DP 8ch)> (play)
pcm1: <Realtek ALC292 (Analog 2.0+HP/2.0)> (play/rec) default
pcm2: <Realtek ALC292 (Internal Analog Mic)> (rec)
pcm3: <USB audio> (play/rec)
Installed devices from userspace:
dsp: <Virtual OSS> (play/rec)
At this point, it would be ideal to close X graphical session to ensure that all applications use the device created by virtual_oss.
Now, if we want to make a hot swap to USB headphones, for example, we just need to specify the correct DSP:
To revert back to the speakers:
A useful script can be this:
#!/usr/bin/env bash
grep 'Virtual OSS' /dev/sndstat 1>/dev/null
if [ $? -eq 0 ]; then
VIRTUAL_OSS_DETECTED=1
else
VIRTUAL_OSS_DETECTED=0
fi
LAST_DEVICE=$(grep pcm /dev/sndstat | awk -F ":" '{print$1}' | awk -F "pcm" '{print$2}' | tail -n1)
#echo "LAST_DEVICE: $LAST_DEVICE"
for NEXT_DEVICE in $(grep pcm /dev/sndstat | awk -F ":" '{print$1}' | awk -F "pcm" '{print$2}'); do
#echo "NEXT_DEVICE: $NEXT_DEVICE"
if [ $VIRTUAL_OSS_DETECTED -eq 1 ]; then
CURRENT_DEVICE=$(virtual_oss_cmd /dev/dsp.ctl|grep 'Output device name' | awk -F "/dev/dsp" '{print$2}')
else
CURRENT_DEVICE=$(grep default /dev/sndstat | awk -F ":" '{print$1}' | awk -F "pcm" '{print$2}')
fi
#echo "CURRENT_DEVICE: $CURRENT_DEVICE"
if [ $NEXT_DEVICE -le $CURRENT_DEVICE ] && [ $NEXT_DEVICE -ne $LAST_DEVICE ]; then
#echo "Continue"
continue
else
if [ $CURRENT_DEVICE -eq $LAST_DEVICE ] && [ $NEXT_DEVICE -eq $LAST_DEVICE ]; then
NEXT_DEVICE=0
#echo "NEXT_DEVICE: $NEXT_DEVICE"
fi
DEVICE_NAME=$(grep pcm$NEXT_DEVICE /dev/sndstat | awk -F "<" '{print$2}' | awk -F ">" '{print$1}')
if [ $VIRTUAL_OSS_DETECTED -eq 1 ]; then
virtual_oss_cmd /dev/dsp.ctl -f /dev/dsp$NEXT_DEVICE
else
sysctl hw.snd.default_unit=$NEXT_DEVICE
fi
if [ $? -eq 0 ]; then
notify-send "Audio device changed: PCM$NEXT_DEVICE: $DEVICE_NAME"
break
else
notify-send "Error changing audio device, reverting"
if [ $VIRTUAL_OSS_DETECTED -eq 1 ]; then
virtual_oss_cmd /dev/dsp.ctl -f /dev/dsp$CURRENT_DEVICE
else
sysctl hw.snd.default_unit=$CURRENT_DEVICE
fi
fi
fi
done