2

I have a couple of amateur radios that I connect to my Linux computer over USB. The radios present themselves as sound cards, and are visible like so:

$ aplay -l [...] card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 card 2: CODEC_1 [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 $ arecord -l **** List of CAPTURE Hardware Devices **** card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 card 2: CODEC_1 [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 0/1 Subdevice #0: subdevice #0 

They show up on USB as:

$ lsusb | grep Audio $ lsusb | grep Aud Bus 001 Device 102: ID 08bb:2901 Texas Instruments PCM2901 Audio Codec Bus 001 Device 099: ID 08bb:2901 Texas Instruments PCM2901 Audio Codec 

The problem here is that I have software that wants to talk to one of them, but it doesn't know which one. The software (js8call and wsjtx) simply allows me to select the name from a dropdown, and remembers the name chosen.

The names in this dropdown are:

alsa_input.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo alsa_input.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo.2 

Other software (e.g. direwolf) wants the device in "plughw:2,0" format, where the other radio is "plughw:1,0".

But it's not consistent which is which. It depends on when Linux detected them, which in the best case is the order that I plugged them in, and in the normal case it's a race condition, since both are plugged in and are using the same power supply, so they boot up at the same time when I turn on the power.

So, how do I make Linux name these two sound devices in a consistent way, so that I don't have to edit config files, and change settings in a UI, every time they happen to have been detected in a different order?

2 Answers 2

2

The names in the dropdown look like PulseAudio source names: see pacmd dump. They would include a serial number of the USB sound device had one in the standard USB device descriptor (see lsusb -d 08bb:2901 -v | grep iSerial). If the radios have no unique identifier detectable by Linux, it might be tricky to get them consistently named.

The PulseAudio names seem to be generated based on the ID_ID property, see udevadm info -q property -p /sys/class/sound/card<N> where <N> is the number of the sound device in question (starting from card0).

You might be able to make a custom udev rule that uses e.g. the ID_PATH property to identify the radios based on which physical USB port they're plugged into, and adjust the ENV{ID_ID} property based on that to allow each radio's interface to be uniquely identified.


plughw:N,0 names are ALSA / .asoundrc / /etc/alsa/conf.d device names, see arecord -L (note upper-case L). The N number is equal to the udev attribute ATTR{number}, visible with udevadm info -q all -a -p /sys/class/sound/card<N>.

You might be able to use names like plughw:CARD=<name>,DEV=0 instead if the OS version is not too old. The <name> part is based on udev attribute ATTR{id}, visible with udevadm info -q all -a -p /sys/class/sound/card<N>.

Whether or not you can modify the ATTR{number} or ATTR{id} attributes by udev rules seems to depend on which version of udev your system has: the newer versions of udev seem to be stricter than older ones, or perhaps newer systems have more complicated udev rulesets and I just haven't found the correct way to set them.

The ordering of udev rules is important: you might need to study your Linux distribution's existing udev rules to find out whether you should set your own rules to activate before or after the distribution's standard rules in order to have your rules actually take effect. Old distributions used to have all the udev rules in a single directory: modern ones have /etc/udev/rules.d/ for local customizations, while the system standard rules are located in [/usr]/lib/udev/rules.d/.

With the modern distributions, if a rule file with the same name exists both in /etc/udev/rules.d/ and in the system standard rules directory, the file in /etc/udev/rules.d/ will override the corresponding standard file, so you should never need to modify any files in the standard rules directory... and so you won't ever get your customizations overwritten by package updates either.

As an alternative solution for ALSA, if you find you cannot modify the required udev attributes, you could make udev generate a system-wide /etc/alsa/conf.d/*.conf file for you, that defines suitable custom ALSA device names for your radios after identifying them by some suitable udev attribute.


The ALSA documentation has an old example on how to assign ALSA device numbers to USB devices by USB port they're connected to:
https://alsa.opensrc.org/Udev#A_working_example

It's rather complex and requires compiling a small program to act as an udev helper, and is designed to handle audio output rather than input devices, but it should be a workable starting point. It also includes some functionality you don't need, i.e. setting up a combined output device that includes all the plugged-in USB sound devices.

5
  • Unfortunately they don't have a serial number. lsusb -v shows exact same output. ATTR{id} is CODEC and CODEC_1 respectively, so that's probably just assigned as they connect. Maybe the ATTR{devpath} will be consistent, assuming I keep them in the same USB ports? Unfortunately I can't get it to work. Yes, the binary is called, but the symlink is not created (NAME= is deprecated, so I used SYMLINK+=). But putting that to one side, if I create the symlink it'll show up in aplay -l as 10. But how do I create a PulseAudio device from that? Via /etc/asound.conf? Commented Oct 31, 2021 at 17:02
  • If you can identify the device in udev, you can assign its ENV{ID_ID} to something distinguishable, which should automatically be passed to PulseAudio to become part of the PulseAudio device name. Just make sure your rule executes after the standard ENV{ID_ID} rules for your distribution, so the standard rules won't override your custom setting. Commented Nov 1, 2021 at 1:07
  • You mean something like KERNEL=="pcmC[D0-9cp]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", SYMLINK+="snd/%c",ENV{ID_ID}="%c"? The script does print out "pcmC9D0p" to stdout for my example device. But I don't see that string in pacmd dump. Commented Nov 1, 2021 at 9:44
  • Interestingly the symlink actually does work. It's just that despite snd/%c it's actually created directly in /dev/, not in /dev/snd/. Commented Nov 1, 2021 at 9:48
  • I have updated the question with the progress and multiple steps. Thanks, by the way, I really appreciate your great help. Commented Nov 1, 2021 at 10:26
0

The easiest solution is to overwrite the id attribute based on the USB path.

It seems that the simple solution is to place this in /etc/udev/rules.d/99-myrules.conf:

SUBSYSTEM=="sound",KERNELS=="1-1.4.4:1.0",ATTR{id}="CODEC_7300" SUBSYSTEM=="sound",KERNELS=="1-1.3.4:1.0",ATTR{id}="CODEC_9700" 

Take the KERNELS from udevadm info -ap /sys/class/sound/controlC2, where 2 is the index of the audio card.

The above is the short answer, and solves it for programs with a dropdown that exposes the ID.

Good questions and answers on these other threads:

While this doesn't set a consistent ALSA sound card number, it does make it obvious which is which. Per aplay -l:

**** List of PLAYBACK Hardware Devices **** [...] card 2: CODEC_7300 [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 card 3: CODEC_9700 [USB Audio CODEC], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 

If you want to go further and get consistent ALSA numbering you can create a set of symlinks. E.g.

KERNEL=="controlC[0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.sh %k", SYMLINK+="%c" KERNEL=="hwC[D0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.sh %k", SYMLINK+="%c" KERNEL=="midiC[D0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.sh %k", SYMLINK+="%c" KERNEL=="pcmC[D0-9cp]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.sh %k", SYMLINK+="%c" 

And have a script that checks the path and returns a suitable name. E.g.

#!/bin/bash NAME="$1" if echo "$DEVPATH" | grep 1-1.4; then NAME="$(echo "$NAME" | sed -r 's/(.*)C([0-9]+)(.*)/\1C11\3/')" fi if echo "$DEVPATH" | grep 1-1.3; then NAME="$(echo "$NAME" | sed -r 's/(.*)C([0-9]+)(.*)/\1C12\3/')" fi exec echo "snd/$NAME" 

You can then create a consistent PulseAudio device like so:

N=11 DEV="radio-7300" pacmd load-module module-alsa-card \ device_id="${N}" name="${DEV}" \ card_name="alsa_card.platform-${DEV}_audio" \ namereg_fail=false tsched=no fixed_latency_range=no \ ignore_dB=no deferred_volume=yes use_ucm=yes \ card_properties="module-udev-detect.discovered=1" pacmd suspend-sink alsa_output.${DEV}.analog-stereo no pacmd suspend-source alsa_input.${DEV}.analog-stereo no 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.