Skip to content

Instantly share code, notes, and snippets.

@kbravh
Last active June 6, 2024 12:44
Show Gist options
  • Save kbravh/1117a974f89cc53664e55823a55ac320 to your computer and use it in GitHub Desktop.
Save kbravh/1117a974f89cc53664e55823a55ac320 to your computer and use it in GitHub Desktop.
Switch audio output devices on Linux

Audio Output Switcher

This script will cycle to the next available audio output device. It can be tied to a hotkey to easily be triggered. This is handy, for example, for swapping between speakers and headphones.

This script will work on systems running PulseAudio or Pipewire services.

Install

  1. Download the audio-device-switch.sh script and place it in /usr/local/bin.
  2. Make the script executable: sudo chmod 755 /usr/local/bin/audio-device-switch.sh.
  3. Open the Keyboard Shortcuts settings page, add a new shortcut, tell it to execute audio-device-switch.sh, and set up your shortcut!
  4. Install the notify-send library if you want to see a popup notification when the audio device switches: sudo apt install libnotify-bin.

Customizations

Feel free to modify this script and make it your own. Some ideas for customization:

Different icon in the notification

Line 83 of the script calls notify-send with the -i flag which defines which icon is displayed. Stock icons are found in:

  • /usr/share/icons/gnome/32x32
  • /usr/share/notify-osd/icons/

Acknowledgements

This is a more modern, robust rewrite of tsvetan's solution on the Ubuntu forums.

#!/bin/bash
# Check which sound server is running
if pgrep pulseaudio >/dev/null; then
sound_server="pulseaudio"
elif pgrep pipewire >/dev/null; then
sound_server="pipewire"
else
echo "Neither PulseAudio nor PipeWire is running."
exit 1
fi
# Grab a count of how many audio sinks we have
if [[ "$sound_server" == "pulseaudio" ]]; then
sink_count=$(pacmd list-sinks | grep -c "index:[[:space:]][[:digit:]]")
# Create an array of the actual sink IDs
sinks=()
mapfile -t sinks < <(pacmd list-sinks | grep 'index:[[:space:]][[:digit:]]' | sed -n -e 's/.*index:[[:space:]]\([[:digit:]]\)/\1/p')
# Get the ID of the active sink
active_sink=$(pacmd list-sinks | sed -n -e 's/[[:space:]]*\*[[:space:]]index:[[:space:]]\([[:digit:]]\)/\1/p')
elif [[ "$sound_server" == "pipewire" ]]; then
sink_count=$(pactl list sinks | grep -c "Sink #[[:digit:]]")
# Create an array of the actual sink IDs
sinks=()
mapfile -t sinks < <(pactl list sinks | grep 'Sink #[[:digit:]]' | sed -n -e 's/.*Sink #\([[:digit:]]\)/\1/p')
# Get the ID of the active sink
active_sink_name=$(pactl info | grep 'Default Sink:' | sed -n -e 's/.*Default Sink:[[:space:]]\+\(.*\)/\1/p')
active_sink=$(pactl list sinks | grep -B 2 "$active_sink_name" | sed -n -e 's/Sink #\([[:digit:]]\)/\1/p' | head -n 1)
fi
# Get the ID of the last sink in the array
final_sink=${sinks[$((sink_count - 1))]}
# Find the index of the active sink
for index in "${!sinks[@]}"; do
if [[ "${sinks[$index]}" == "$active_sink" ]]; then
active_sink_index=$index
fi
done
# Default to the first sink in the list
next_sink=${sinks[0]}
next_sink_index=0
# If we're not at the end of the list, move up the list
if [[ $active_sink -ne $final_sink ]]; then
next_sink_index=$((active_sink_index + 1))
next_sink=${sinks[$next_sink_index]}
fi
#change the default sink
if [[ "$sound_server" == "pulseaudio" ]]; then
pacmd "set-default-sink ${next_sink}"
elif [[ "$sound_server" == "pipewire" ]]; then
# Get the name of the next sink
next_sink_name=$(pactl list sinks | grep -C 2 "Sink #$next_sink" | sed -n -e 's/.*Name:[[:space:]]\+\(.*\)/\1/p' | head -n 1)
pactl set-default-sink "$next_sink_name"
fi
#move all inputs to the new sink
if [[ "$sound_server" == "pulseaudio" ]]; then
for app in $(pacmd list-sink-inputs | sed -n -e 's/index:[[:space:]]\([[:digit:]]\)/\1/p'); do
pacmd "move-sink-input $app $next_sink"
done
elif [[ "$sound_server" == "pipewire" ]]; then
for app in $(pactl list sink-inputs | sed -n -e 's/.*Sink Input #\([[:digit:]]\)/\1/p'); do
pactl "move-sink-input $app $next_sink"
done
fi
# Create a list of the sink descriptions
sink_descriptions=()
if [[ "$sound_server" == "pulseaudio" ]]; then
mapfile -t sink_descriptions < <(pacmd list-sinks | sed -n -e 's/.*alsa.name[[:space:]]=[[:space:]]"\(.*\)"/\1/p')
elif [[ "$sound_server" == "pipewire" ]]; then
mapfile -t sink_descriptions < <(pactl list sinks | sed -n -e 's/.*Description:[[:space:]]\+\(.*\)/\1/p')
fi
# Find the index that matches our new active sink
for sink_index in "${!sink_descriptions[@]}"; do
if [[ "$sink_index" == "$next_sink_index" ]]; then
notify-send -i audio-volume-high "Sound output switched to ${sink_descriptions[$sink_index]}"
exit
fi
done
@pgillet
Copy link

pgillet commented Jun 6, 2024

AFAIK, PipeWire and/or PulseAudio can list all audio input and output devices...
See also the comment from @kbravh ^^: https://gist.github.com/kbravh/1117a974f89cc53664e55823a55ac320?permalink_comment_id=4478290#gistcomment-4478290

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment