1

What I want is to be able to consistently tell apart multiple USB sound cards, identify them by the USB port they're plugged in and use that knowledge to play a sound on a speciffic sound card in my Java program.

So far I'm stuck on the first part - identify sound cards by the USB port.

First thing I did is to follow advice in this question and use the Udev rules to assign names to sound cards with the script from this site

These are the Udev rules I added

KERNEL=="controlC[0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", NAME="snd/%c{1}"
KERNEL=="hwC[D0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", NAME="snd/%c{1}"
KERNEL=="midiC[D0-9]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", NAME="snd/%c{1}"
KERNEL=="pcmC[D0-9cp]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", NAME="snd/%c{1}"

and these are the contents of alsa_name.pl

use strict;
use warnings;
#
my $alsaname = $ARGV[0]; #udev called us with this argument (%k)
my $physdevpath = $ENV{PHYSDEVPATH}; #udev put this in our environment
my $alsanum = "cucu";
#you can find the physdevpath of a device with "udevinfo -a -p $(udevinfo -q path -n /dev/snd/pcmC0D0c)"
#
#
$physdevpath =~ s/.*\/([^\/]*)/$1/; #eliminate until last slash (/)
$physdevpath =~ s/([^:]*):.*/$1/; #eliminate from colon (:) to end_of_line
#
if($physdevpath eq "1-1.3.1")
{
       $alsanum="11";
}
if($physdevpath eq "1-1.3.2")
{
       $alsanum="12";
}
if($physdevpath eq "1-1.3.3")
{
       $alsanum="13";
}
if($physdevpath eq "1-1.3.4")
{
       $alsanum="14";
}

#
if($alsanum ne "cucu")
{
       $alsaname=~ s/(.*)C([0-9]+)(.*)/$1C$alsanum$3/;
}
#
print $alsaname;
exit 0;

Now, when I plug my USB sound card and look at /var/log/syslog I see that it doesn't exactly work:

NAME="snd/%c{1}" ignored, kernel device nodes cannot be renamed; please fix it in /etc/udev/rules.d/99-com.rules:16

I tried to modify my Udev rules based on this repository which provides an Udev rule:

SUBSYSTEM!="sound", GOTO="my_usb_audio_end"
ACTION!="add", GOTO="my_usb_audio_end"

DEVPATH=="/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/sound/card?", ATTR{id}="SPEAKER"
DEVPATH=="/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0/sound/card?", ATTR{id}="HEADSET"

LABEL="my_usb_audio_end"

So I used my previous script and modified my rule:

KERNEL=="pcmC[D0-9cp]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", ATTR{id}="snd/%c{1}

but now syslog tells me:

error opening ATTR{some_very_long_id} for writing: Permission denied

I also tried this answer and did

KERNEL=="pcmC[D0-9cp]*", DRIVERS=="usb", PROGRAM="/usr/bin/alsa_name.pl %k", SYMLINK+="snd/%c{1}

I don't see any errors in syslog, which I suppose is good, but when I list playback devices with aplay -l, all I see is

card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0

and nothing changes, regardles of which USB port I plug it in. I also see no useful/distinguishable info in my Java program using AudioSystem.getMixerInfo()

Is my approach correct and I'm just missing some detail, or this is completely wrong direction?

1

You are on the right track. There are a lot of things that can go wrong with udev. The reason that udev rules withNAME="..." do not work anymore is that the kernel no longer allows you to rename devices in this way. The creation of symbolic links with SYMLINK+= work in general, but I don't know if alsa is interested in them.

So I think the likely correct solution is the advice given in your linked article in the section titled Identify two identical audio devices. Use a rule with DEVPATH== to match the device, and ATTR{id}="ABC" to give a unique name to that device that you will then find in aplay -l or cat /proc/asound/cards.

First, try the same thing manually. I don't have any usb sound cards but only a built-in device, so if I do:

find /sys/devices/ -name id | grep sound

it lists many items named "id", but some of these are directories, and the only file of interest is /sys/devices/.../sound/card0/id. If I cat this file it holds the name of the device ("PCH"). If I write a string into this pseudo-file it changes its name:

sudo sh -c 'printf "%s" MYCARD >/sys/devices/.../sound/card0/id'

and this is seen in the output of aplay -l. This is what you are trying to do with udev; the sysfs pseudo-file id is an attribute of card0. So in udev, ATTR{id}= only works if you have matched in the right /sys directory, i.e. /sys/devices/.../sound/card0 in my case. This is why the udev rule says DEVPATH=="/sys/devices/.../sound/card?" (the card number may change so it is replaced by the wild-card glob character "?").

For a more complete example see the above-mentioned section of the link which provides you a complete rules file 85-my-usb-audio.rules.

1
  • 1
    Your tip to just write some ID to a speciffic file put me on the right track, although in the end I went with running script on startup instead of using udev rules. – mag_zbc Dec 15 '20 at 15:21
1

Based on @meuh's answer, I managed to get it to work, although a little differently that I originally planned.

Writing to >/sys/devices/.../sound/card0/id file was indeed the way to go, so I wrote a little bash script for that purpose

#!/bin/bash
for file in $(find /sys/devices/ -name id | grep sound | grep usb)
do
    for fragment in $(echo $file | tr "/" "\n")
    do
        if [[ $fragment == *"1.2.1"* ]]
        then
            printf "%s" "EXT_B1" > "$file"
        fi
        if [[ $fragment == *"1.2.2"* ]]
        then
            printf "%s" "EXT_B2" > "$file"
        fi
        # etc
    done
done 

This part - for fragment in $(echo $file | tr "/" "\n") - probably could have been done more elegantly, but I cannot just use a full file path, because I want to use any sound card and identify them only via USB port, but currently I only have one sound card so I can't check if that path changes for different models or vendors. Hence searching for the pattern 1.2.1 etc. describing speciffic port of USB hub connected to speciffic USB port of my device.

I haven't managed to run it using udev rules - apparently you need root access to write to /sys/devices/... (which makes sense) but despite looking through several answers, I couldn't get it done - maybe because I'm running Raspbian Jessie on Raspberry Pi, maybe because I don't know much about Linux.

However, my use case doesn't necessarily require that script to be run upon connecting the device - simply running it on boot is sufficient, so the last thing necessary was to edit crontab using sudo crontab -e and add a line

@reboot /path/to/my/script.sh

And voila, I can get access a speciffic USB sound card in Java code using AudioSystem.getMixerInfo() and play sound using AudioSystem.getClip(mixerInfo)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.