USB audio peripherals still don't "just work" in Linux, but they're starting to
get a little easier to use.

The last time I tried using
PulseAudio, a little over two years
ago, I experienced no end of frustration. This probably had as much to do with
a lack of understanding as anything else, but regardless, I have been using
ALSA since
then. However, ALSA's USB support is not very good. Changing the default audio
device requires an application to be restarted to recognize the new default
device. So, I figured I'd give PulseAudio another try and see if I could make
my USB headset "just work" this time around. To my surprise, it was quite easy
to do so, and in this post I will detail the steps I took. I did this on Arch
Linux, but the steps should apply in most Linux distributions.

Before we start, we need to ensure that PulseAudio is A) installed, and B)
running. Most distributions will take care of both of these for you, but due to
the do-it-yourself nature of Arch Linux, in my case it needed to be installed:

# pacman -S pulseaudio pulseaudio-alsapavucontrol

Notice that I also installed two other packages, pulseaudio-alsa and
pavucontrol. The former contains the ALSA plugin for PulseAudio, the latter
is a helpful application for configuring PulseAudio and visualizing your
PulseAudio configuration. Most Linux distributions will include the ALSA plugin
(and may name the package differently), but you may need to manually install
pavucontrol.

So, now that I have PulseAudio installed, it needs to be running. If you are
using a desktop environment like KDE, Gnome, etc., PulseAudio should be
automatically started when you login. To see if PulseAudio is running, check
for it in the process table.

$ ps aux | grep pulseaudio | grep -q start &&echo yes ||echo no
yes

OK, PulseAudio is both installed and running. Open pavucontrol and click on
the Output Devices tab. In the upper-right corner of each device will be a
few buttons. The green one with a check mark on it (outlined in red in the
example below, click for a full-size image) shows which device is the default
output device. The Input Devices tab contains its own default as well, but
for now we will focus on the output devices, for the sake of simplicity.

Start playing something, preferably in a PulseAudio-compatible application. For
this example, I started a movie file in mplayer from the command-line, forcing
PulseAudio output using the -ao (audio output) option:

$ mplayer -ao pulse foo.mp4

With something now generating audio output, plug in the USB headset, and you'll
notice a new device show up in the Output Devices tab. But you'll also
notice that the audio isn't going to the headset yet.

This can be fixed by clicking the green button next to the headset device, back
on the Output Devices tab. Once clicked (see below), the USB headset will now
be the default audio device for PulseAudio.

However, this would have to be repeated every time the headset is plugged in,
which would be an inconvenience to say the very least.

The solution to this is to write a udev rule which will detect when this device
is plugged in, and then run a script to set the headset as the default input
and output device. I'll explain how to do this in a bit, but first we need to
understand what it is that we want to do with this script.

PulseAudio has a command-line tool called pacmd which allows you to view
and change the PulseAudio configuration. Plug in the USB headset, and then run
pacmd dump to view the current configuration. There is quite a bit of
output, so I have cut out all but the relevant parts:

PulseAudio calls output devices "sinks", and input devices "sources". We can
see from this output that the default sink and source belong to the onboard
audio card. To set the headset as the default device, we can use pacmd to
set the default sink and source to the ones for the headset. This would be done
with the following two commands:

Try runnng these commands yourself (replacing the sink/source names with the
ones you observed when running pacmd dump), and you will notice that the
headset is now the default input and output device if you check the
pavucontrol window.

Now that we know how to set the headset as the default device via the command line, we can write a script that will do this for us. However, because this script will eventually be run as root by udev, we need to use the su command to run it as the user. The following script will check the process table and then, for each user who is running PulseAudio, will run the two commands needed to set the headset as the default input and output device.

Save this script to /usr/local/bin/usb-headset-set-default. It should be
executable, and owned by root. Of course, you should also make sure to replace
the sink and source in the script with the ones you observed when you ran
pacmd dump, if they differ. Unplug the headset and plug it back in, then
run the script as root, and in pavucontrol you should see that it has been
set as the default input and output device. Note that there are a couple
commands commented out in the script. These can be uncommented for debugging
later on, if needed.

Next, we need a way to detect when the headset is plugged in. We can do this
using udev.

Udev is what handles setting up device
nodes for Linux. When you insert a USB flash drive, and it gets assigned a
device name (like /dev/sdb), udev is what takes care of allocating this
device name. Plugging in the headset will generate a number of device nodes. We
can use udevadm to see each of them. Unplug the USB headset, then run the
command below. After running it, plug in the USB headset and you should see
some output appear.

Using another udevadm command, we can find the udev attributes for one of
these device nodes. I have chosen /dev/audio1.

# udevadm info --attribute-walk --name /dev/audio1

Udevadm info starts with the device specified by the devpath and then walks up
the chain of parent devices. It prints for every device found, all possible
attributes in the udev rules key format. A rule to match, can be composed by
the attributes of the device and the attributes from one single parent device.

Wow, that's a lot of output. Before proceeding to write a udev rule, we need to
know how to utilize the attributes above to come up with a unique rule. This
page contains an excellent
walkthrough on how to write a udev rule, so go ahead and read it before
continuing. The information about the udev commands is a little outdated (for
instance, most of the commands referenced are now part of udevadm), but the
information on rule syntax is very good.

So, we need to come up with a set of attributes that will match, but we also
would like to find a set of attributes that are unique. Multiple matches will
result in the script being run multiple times. In this case, where we're just
running a couple commands to set the default audio devices, there aren't really
any consequences to running the script multiple times, but it's still a good
idea to come up with a unique set of attributes. The rule I came up with was:

# Set the USB headset as default sink/source when it is plugged inKERNEL=="audio?", SUBSYSTEM=="sound", SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a0b", ACTION=="add", RUN+="/usr/local/bin/run-script-in-background /usr/local/bin/usb-headset-set-default"

I saved this rule to /etc/udev/rules.d/85-usb-headset.rules. Notice that
the RUN parameter is set to /usr/local/bin/run-script-in-background
/usr/local/bin/usb-headset-set-default. In the process of debugging this
rule, I noticed that the script was running, but the headset wasn't being set
as the default sink/source. This is where the commented-out debugging lines
come in. With the debugging commands uncommented, I noticed in the log file
that the headset wasn't showing up in the pacmd dump output. It turns out
that PulseAudio doesn't know anything about the headset until udev completes
setting up all the device nodes. This makes perfect sense. However, if udev has
matched the rule and is running the script, this suspends further udev
processing until the script completes. So, what will happen is that the script
tries to set devices that don't yet exist, and in effect nothing happens. To
get around this, we need a way for the command executed by udev to exit
quickly, but allow for a short delay so that udev can finish doing it's thing
before pacmd is invoked. So, I created the following script and saved it to
/usr/local/bin/run-script-in-background:

#!/bin/bash## Name: run-script-in-background# Description: takes all command-line arguments and runs them as a background command#$@ &

This script simply takes the arguments fed to it and runs them as a separate
process, in the background. This allows run-script-in-background to quickly
exit so that udev can finish.

With the rule and scripts now in place, plug in the USB headset again and you
should notice in pavucontrol that the headset is now the default device. If
this is not the case, then use the debugging lines in the
usb-headset-set-default script while tailing the log file to determine the
problem. If the headset doesn't show up in the pacmd dump output, then you
might need to increase the sleep time at the beginning of the script.

I've been using this udev rule and script for several days now and the headset
is working flawlessly. Hopefully, my instructions can help you get it working
for yourself. All in all, I am very impressed with how well PulseAudio is
working for me, given the nightmares I experienced in the past.