Stephen Ostermiller's Blog

USB sound in Ubuntu – automatically selecting a USB audio device when it is plugged in

Ubuntu has started to support USB audio.    When I plug in a USB speaker or headset (such as my Pyle USB gaming headset), it gets recognized and installed automatically.  However there are some bugs:

  • The USB sound device does not get selected automatically.   This is especially annoying for headphones.  When you plug in analog headphones, the sound automatically switches to them and the main speakers turn off.
  • The volume settings are turned way up for the device.  Ideally it would remember the volume settings from the last time the device was plugged in.
  • When the computer goes to sleep, the volume of usb devices gets turned way up on wake.

I discovered that I could address these shortcomings by creating a script.

  • PulseAudio provides command line control through pacmd.  I can use that to Query for USB audio devices (both speakers and microphones) that got installed automatically
  • Select default devices (both speakers and microphones)
  • Set volumes for devices (both speakers and microphones)
    • The udev system provides a mechanism for running a script when a USB sound device is plugged in<
    • The pm-utils system provides a mechanism for running a script on wake.

I have included the text of the script below.  To start using it:

  1. Copy and paste the script to a file in a permanent location (for example into /opt/usb-audio-select.sh)
    • sudo editor /opt-usb-audio-select.sh
    • Copy, paste,  save, then exit
    • Make the script executable
    • sudo chmod a+rx /opt/usb-audio-select.sh
  2. As a super user, run the script with the --install flag.  This will install the play command if you don't have it, setup the udev rule, restart the udev system, and install the pm-utils script.
    • sudo /opt/usb-audio-select.sh --install
  3. If you want to test the script without  fully installing  (or run it manually for any reason) you may do so.
    • /opt/usb-audio-select.sh
  4. The script sets volume levels that are appropriate for my headset.  You might need to edit the script to adjust the volume levels to your preferences.

#!/bin/bash
#
# Automatically select USB sound devices
# Copyright (C) 2013-2020 Stephen Ostermiller
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA  02110-1301, USA.

# To install:
# Put this script somewhere permanent like /opt/usb-audio-use.sh
# then run:
# sudo /opt/usb-audio-use.sh --install

# To uninstall run:
# sudo rm -f /lib/udev/rules.d/99-usb-audio-auto-select.rules /usr/lib/pm-utils/sleep.d/99usbaudio

# The name of any device you want to give priority over other devices
# A case-insensitive substring from your cards will work.  For your
# options run:
#     pacmd list-cards | grep 'name:'
priority="Media_Electronics"

# Volume at which to set the speakers
# (1 to 100000, 20000 is 20%)
# comment this line out not to touch the volume
speakervolume=20000

# Volume at which to set the microphone
# (1 to 100000, 80000 is 80%)
# comment this line out not to touch the volume
micvolume=80000

#####################################################################

install=0
sleep=0
verbose=0

for i in "$@"
do
    case $i in
        --install)
            install=1
        ;;
        --sleep)
            sleep=1
        ;;
        --verbose)
            verbose=1
        ;;
        *)
            echo "Unknown option $i"
            exit 1;
        ;;
    esac
done

if [ $install == 1 ]
then
    if [ $EUID -ne 0 ]
    then
        echo "Error you are not the root user."
        echo "Run this install as root (or use sudo)"
        exit 1
    fi
    if [ ! `which play` ]
    then
         apt-get -y install sox
    fi
    script=`readlink -f $0`
    rulefile=/lib/udev/rules.d/99-usb-audio-auto-select.rules
    if [ -e $rulefile ]
    then
        echo "udev rule already exists: $rulefile"
    else
        echo "Creating udev rule: $rulefile"
        echo "ACTION==\"add|remove\", ENV{ID_TYPE}==\"audio\", RUN+=\"$script\"" > $rulefile
        service udev restart
        echo "Installed udev rule"
    fi
    rulefile=/usr/lib/pm-utils/sleep.d/99usbaudio
    if [ -e $rulefile ]
    then
        echo "pm-utils sleep/wake rule already exists: $rulefile"
    else
        echo "Creating pm-utils sleep/wake rule: $rulefile"
        echo -e "#!/bin/sh \n case \"\$1\" in \n 'resume' | 'thaw') \n $script \n ;; \n esac" > $rulefile
        chmod a+x $rulefile
        echo "Installed pm-utils sleep/wake rule"
    fi
    exit 0
fi

if [ $sleep == 1 ]
then
    if [ $verbose == 1 ]
    then
        echo "Sleeping 1 second"
    fi
    sleep 1
fi

if [ "$UID" == "0" ]
then
    if [ $verbose == 1 ]
    then
        echo "Checking process table for users running PulseAudio"
    fi
    for user in `ps axc -o user,command | grep pulseaudio | cut -f1 -d' ' | sort | uniq`
    do
        verbosearg=""
        if [ $verbose == 1 ]
        then
            echo "Forking to run as PulseAudio user: $user"
            verbosearg="--verbose"
        fi
        # tell it to sleep for a second to let pulseaudio install the usb device
        su $user -c "bash $0 --sleep $verbosearg" &
    done
else
    export PULSE_RUNTIME_PATH="/run/user/$UID/pulse/"
    if pacmd list-cards 2>&1 | grep 'No PulseAudio daemon running'
    then
        echo "For user: `whoami`"
        exit 1
    fi
    # List the sound cards, put USB sound cards first
    cards=`pacmd list-cards | grep 'name:' | sed -r "s/.*<//g;s/>.*//g;s/.*usb.*/1-\0/gi;s/.*($priority).*/0-\0/gi" | sort | sed -r  's/^([01]-)*//g'`
    # The first card
    card=`echo "$cards" | head -n 1`
    # Find a profile for it, preferrably something with output and input, but fall back to just output
    profile=`pacmd list-cards | sed -n "/$card/,/^\s*Index:/p" | sed -n '/^\s*profiles:/,/^\s*off/p' | tail -n+2 | grep -v 'available: no' | sed -r 's/^\s+//g;s/: .*//g;s/.*output.*input.*/0-\0/g;s/.*output.*/0-\0/g' | sort | sed -r 's/^(0-)+//g' | head -n 1`
    if [ $verbose == 1 ]
    then
        echo "Enabling: $card $profile"
    fi
    pacmd set-card-profile "$card" "$profile" | grep -vE 'Welcome|>>> $'
    # For each of the other cards
    echo "$cards" | tail -n+2 | while read card
    do
        if [ $verbose == 1 ]
        then
            echo "Disabling: $card"
        fi
        pacmd set-card-profile "$card" off | grep -vE 'Welcome|>>> $'
    done

    # List the speakers, put USB speakers first
    speakers=`pacmd list-sinks | grep 'name:' | sed -r "s/.*<//g;s/>.*//g;s/.*usb.*/1-\0/gi;s/.*($priority).*/0-\0/gi" | sort | sed -r  's/^([01]-)*//g'`
    # The first speaker
    speaker=`echo "$speakers" | head -n 1`
    if [ $verbose == 1 ]
    then
        echo "Setting default speaker: $speaker"
    fi
    pacmd set-default-sink "$speaker" | grep -vE 'Welcome|>>> $'
    if [ $verbose == 1 ]
    then
        echo "Unmuting speaker: $speaker"
    fi
    pacmd set-sink-mute "$speaker" 0 | grep -vE 'Welcome|>>> $'
    if [ "z$speakervolume" != "z" ]
    then
        if [ $verbose == 1 ]
        then
            let volume=$speakervolume/1000
            echo "Setting $volume% volume: $speaker"
        fi
        pacmd set-sink-volume "$speaker" $speakervolume | grep -vE 'Welcome|>>> $'
    fi
    # For each of the other speakers
    echo "$speakers" | tail -n+2 | while read speaker
    do
        if [ $verbose == 1 ]
        then
            echo "Muting speaker: $speaker"
        fi
        pacmd set-sink-mute "$speaker" 1 | grep -vE 'Welcome|>>> $'
    done

    mics=`pacmd list-sources | grep 'name:' | grep input | sed -r "s/.*<//g;s/>.*//g;s/.*usb.*/1-\0/gi;s/.*($priority).*/0-\0/gi" | sort | sed -r  's/^([01]-)*//g'`
    # The first mic
    mic=`echo "$mics" | head -n 1`
    if [ $verbose == 1 ]
    then
        echo "Setting default source: $mic"
    fi
    pacmd set-default-source "$mic" | grep -vE 'Welcome|>>> $'
    if [ $verbose == 1 ]
    then
        echo "Unmuting mic: $mic"
    fi
    pacmd set-source-mute "$mic" 0 | grep -vE 'Welcome|>>> $'
    if [ "z$micvolume" != "z" ]
    then
        if [ $verbose == 1 ]
        then
            let volume=$micvolume/1000
            echo "Setting volume to $volume%: $mic"
        fi
        pacmd set-source-volume "$mic" $micvolume | grep -vE 'Welcome|>>> $'
    fi
    # For each of the other mics
    echo "$mics" | tail -n+2 | while read mic
    do
        if [ $verbose == 1 ]
        then
            echo "Muting mic: $mic"
        fi
        pacmd set-source-mute "$mic" 1 | grep -vE 'Welcome|>>> $'
    done

    if [ -e "/usr/lib/libreoffice/share/gallery/sounds/train.wav" ]
    then
        if [ $verbose == 1 ]
        then
            echo "Playing sound: /usr/lib/libreoffice/share/gallery/sounds/train.wav"
        fi
        play /usr/lib/libreoffice/share/gallery/sounds/train.wav 2> /dev/null
    else
        echo "Sound file does not exist: /usr/lib/libreoffice/share/gallery/sounds/train.wav"
        exit 1
    fi
fi

exit 0

This script is licensed under the GNU General Public License 2

Leave a comment

Your email address will not be published. Required fields are marked *

14 thoughts on “USB sound in Ubuntu – automatically selecting a USB audio device when it is plugged in”