Diferencie várias placas de som USB pela porta USB em que estão conectadas

Dec 11 2020

O que eu quero é ser capaz de diferenciar de forma consistente várias placas de som USB, identificá-las pela porta USB em que estão conectadas e usar esse conhecimento para reproduzir um som em uma placa de som específica em meu programa Java.

Até agora, estou preso na primeira parte - identificar as placas de som pela porta USB.

A primeira coisa que fiz foi seguir o conselho nesta questão e usar as regras do Udev para atribuir nomes às placas de som com o script deste site

Estas são as regras Udev que adicionei

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}"

e estes são os conteúdos de 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;

Agora, quando ligo minha placa de som USB e /var/log/syslogvejo que não funciona exatamente:

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

Tentei modificar minhas regras Udev com base neste repositório que fornece uma regra Udev:

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"

Então, usei meu script anterior e modifiquei minha regra:

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

mas agora syslogme diz:

error opening ATTR{some_very_long_id} for writing: Permission denied

Eu também tentei esta resposta e fiz

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

Não vejo nenhum erro no syslog, o que suponho ser bom, mas quando listo os dispositivos de reprodução com aplay -l, tudo que vejo é

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

e nada muda, independentemente de qual porta USB eu o plugue. Também não vejo nenhuma informação útil / distinguível em meu programa Java usando AudioSystem.getMixerInfo()

Minha abordagem está correta e estou apenas perdendo alguns detalhes, ou essa direção é completamente errada?

Respostas

1 meuh Dec 11 2020 at 17:27

Você está no caminho certo. Existem várias coisas que podem dar errado com o udev. O motivo pelo qual as regras do udev NAME="..."não funcionam mais é que o kernel não permite mais renomear dispositivos desta forma. A criação de ligações simbólicas com o SYMLINK+=trabalho em geral, mas não sei se a alsa se interessa por elas.

Portanto, acho que a solução provavelmente correta é o conselho dado em seu artigo vinculado na seção intitulada Identificar dois dispositivos de áudio idênticos . Use uma regra com DEVPATH==para corresponder ao dispositivo e ATTR{id}="ABC"para dar um nome exclusivo a esse dispositivo que você encontrará em aplay -lou cat /proc/asound/cards.

Primeiro, tente a mesma coisa manualmente. Não tenho nenhuma placa de som USB, mas apenas um dispositivo embutido, então se eu tiver:

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

ele lista muitos itens chamados "id", mas alguns deles são diretórios e o único arquivo de interesse é /sys/devices/.../sound/card0/id. Se cateste arquivo contém o nome do dispositivo ("PCH"). Se eu escrever uma string neste pseudo-arquivo, ele mudará seu nome:

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

e isso é visto na saída de aplay -l. Isso é o que você está tentando fazer com o udev; o pseudo-arquivo sysfs idé um atributo de card0. Portanto, no udev, ATTR{id}=só funciona se você tiver encontrado no /sysdiretório correto , ou seja, /sys/devices/.../sound/card0no meu caso. É por isso que a regra do udev diz DEVPATH=="/sys/devices/.../sound/card?"(o número do cartão pode mudar, então ele é substituído pelo caractere glob curinga "?").

Para um exemplo mais completo, consulte a seção mencionada acima do link que fornece um arquivo de regras completo 85-my-usb-audio.rules.

1 mag_zbc Dec 15 2020 at 22:16

Com base na resposta do @ meuh, consegui fazer funcionar, embora um pouco diferente do que planejei originalmente.

Gravar em >/sys/devices/.../sound/card0/idarquivo era de fato o caminho a percorrer, então escrevi um pequeno script bash para esse propósito

#!/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 

Esta parte - for fragment in $(echo $file | tr "/" "\n")- provavelmente poderia ter sido feita de forma mais elegante, mas não posso apenas usar um caminho de arquivo completo, porque quero usar qualquer placa de som e identificá-los apenas via porta USB, mas atualmente só tenho uma placa de som, então posso ' t verifique se esse caminho muda para diferentes modelos ou fornecedores. Portanto, 1.2.1busco o padrão etc. que descreve a porta específica do hub USB conectado à porta USB específica do meu dispositivo.

Não consegui executá-lo usando as regras do udev - aparentemente, você precisa de acesso root para escrever /sys/devices/...(o que faz sentido), mas apesar de procurar várias respostas, não consegui fazer isso - talvez porque estou executando o Raspbian Jessie no Raspberry Pi, talvez porque não conheço muito sobre Linux.

No entanto, meu caso de uso não requer necessariamente que o script seja executado ao conectar o dispositivo - simplesmente executá-lo na inicialização é suficiente, então a última coisa necessária foi editar o crontab usando sudo crontab -ee adicionar uma linha

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

E pronto , posso obter acesso a uma placa de som USB específica em código Java usando AudioSystem.getMixerInfo()e reproduzir som usandoAudioSystem.getClip(mixerInfo)