ALSA is powerful. Incredibly flexible. Because of that it’s almost guaranteed to be complicated. I spend a lot of time plugging and unplugging different audio devices into my machines in different places. The way ALSA identifies sound cards (USB, built-in, HDMI, whatever) is that the first one detected becomes card0. The next is card1. The next is card2 and so on. Subdevices are identified the same way. My fellow nerds call this an array. Setting up something like Direwolf to use audio is simple. Identify the device you want to use. Go in to the config file. Update the config with the numbers corresponding with the device/subdevice you want to use. Simple.
Until something changes. Plug in another USB headset? No problem. It gets assigned the next card number in sequence. If you device reboots however there is no guarantee the soundcard you pointed your software at is still assigned the same card number. You are stuck going back in to Direwolf’s (or some other software’s) config file to update the settings after you’ve ID’d the new card assignment.
The Direwolf documentation hints at another way to get the job done. ALSA’s documentation hints at another way as well. After scouring the interwebs learning the ins-and-outs of ALSA and experimenting with different ideas for a couple weeks I finally cracked the code. I feel like what follows should have long-since been documented because it seems simple once you understand it. Since I couldn’t find it here’s how to create some persistence for USB devices in ALSA.
Overview: We need to ID the socket the kernel creates for the USB audio device. We take that and write a udev rule that gives overly-complicated socket a name that is both easily read and static so it can be used in config files. In my case I wanted to label the soundcard built in to the Digirig Mobile. Start with ‘aplay -l’:
└─$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: b1 [bcm2835 HDMI 1], device 0: bcm2835 HDMI 1 [bcm2835 HDMI 1]
Subdevices: 4/4
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
card 1: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
Subdevices: 4/4
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
card 2: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
In this case since I know the Digirig is the only USB device it’s easy to identify. If you have more that one USB soundcard it may be helpful to connect just one at a time as you follow the process. I know it’s card2 now. So let’s list all the connected audio devices with ‘ls -l /dev/snd’:
└─$ ls -l /dev/snd
total 0
drwxr-xr-x 2 root root 60 Dec 22 06:26 by-id
drwxr-xr-x 2 root root 80 Dec 22 06:26 by-path
crw-rw----+ 1 root audio 116, 0 Dec 22 06:26 controlC0
crw-rw----+ 1 root audio 116, 32 Dec 22 06:26 controlC1
crw-rw----+ 1 root audio 116, 64 Dec 22 06:26 controlC2
crw-rw----+ 1 root audio 116, 16 Dec 22 06:26 pcmC0D0p
crw-rw----+ 1 root audio 116, 48 Dec 22 06:26 pcmC1D0p
crw-rw----+ 1 root audio 116, 88 Dec 22 06:26 pcmC2D0c
crw-rw----+ 1 root audio 116, 80 Jan 29 19:59 pcmC2D0p
crw-rw----+ 1 root audio 116, 1 Dec 22 06:26 seq
crw-rw----+ 1 root audio 116, 33 Dec 22 06:26 timer
These are the sound devices. Each device’s components can be ID’d by the card number. Devices with C0 are on card0. I know Digirig is card 2 so controlC2 is its control, pcmC2D0c is it’s capture hardware, and pcmC2D0p is its playback hardware. Armed with this knowledge I take one and find its kernel-given socket using ‘udevadm info -a -n /dev/snd/pcmC2D0c’:
looking at device '/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.4/1-1.4.1/1-1.4.1:1.0/sound/card2/pcmC2D0c':
KERNEL=="pcmC2D0c"
SUBSYSTEM=="sound"
DRIVER==""
ATTR{pcm_class}=="generic"
ATTR{power/control}=="auto"
ATTR{power/runtime_active_time}=="0"
ATTR{power/runtime_status}=="unsupported"
ATTR{power/runtime_suspended_time}=="0"
We can take this info and use it to name the device something meaningful. The socket is the portion from /devices thru /1-1.4.1:1.0/ and though it shows card2 right now, it could be any card number on the next boot. Using your favorite text editor (mine’s vim) create a udev rule file and enter the following. ‘sudo vim /etc/udev/rules.d/digirig_audio.rules’:
#File goes in /etc/udev/rules.d
#
SUBSYSTEMS=="sound", GOTO="digirig_audio_rules"
GOTO="digirig_audio_rules_end"
LABEL="digirig_audio_rules"
DEVPATH=="/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.4/1-1.4.1/1-1.4.1:1.0/sound/card?", ATTR{id}="DigiAudio"
LABEL="digirig_audio_rules_end"
You’ll need to substitute much of that “/devices/platform/scb …/” string with whatever you got from the udevadm command above. Notice also instead of a number there’s a question mark following “card.” This rule matches the socket regarless of what card number is assigned by ALSA. The ATTR{id}=”yourtexthere” is what name will be assigned the device that matches that rule. You can now reboot your machine and when you run ‘aplay -l’ you should see your USB sound card with whatever name you gave it.
└─$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: b1 [bcm2835 HDMI 1], device 0: bcm2835 HDMI 1 [bcm2835 HDMI 1]
Subdevices: 4/4
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
card 1: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
Subdevices: 4/4
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
card 2: DigiAudio [USB PnP Sound Device], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
In my case it’s DigiAudio. Now when referencing that device in, say, Direwolf for example, you’d enter the string:
ADEVICE plughw:DigiAudio,0
Hopefully someone else will find this useful in the future.