I'm currently working on a Rust library/CLI/UI for managing Focusrite Scarlett USB audio devices specifically on Linux. These devices often have many different internal settings (my 18i8 has nearly 300 individual controls), so I'm trying to account for all of these in my design to make it easy for users to change routing configuration, input/output/mix gain, mute toggles, etc.
Thankfully, Linux already supports these devices with a bit of modprobe configuration and a recent kernel:
/etc/modprobe.d/scarlett.conf:
options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1
For different models and possibly different major hardware versions, the above values need to be set to different USB product IDs to enable support for those devices as well.
I have been studying the ALSA API for what feels like weeks now, and I've learned quite a lot:
snd_ctl_t
, snd_hctl_t
, and snd_mixer_t
APIs, there is essentially no code in userspace. All structs are opaque, and functions (syscalls) are used with these struct references to obtain details. The kernel does some magic with memory and gives it to userspace.In any case, here is my predicament: I can list ALSA devices and iterate over them, and via a snd_ctl_card_info_t
I can get a few properties which are helpful like:
name
: Scarlett 18i8 USBid
: USBlongname
: Focusrite Scarlett 18i8 USB at usb-0000:00:14.0-2.2, high speedmixername
: USB Mixercomponents
: USB1235:8214driver
: USB-AudioHowever, this seems to be the most I can get out of the available APIs. I can get the model type by extracting that from the USB vendor/product IDs in the components
field, and I can perhaps find the actual USB handle by parsing the longname
, but:
usb-*
slug and the USB vendor/product IDs, perhaps this may change with kernel versions and become invalid.Therefore, is there a Linux API which will allow me to get a USB device file from an ALSA handle of some kind? Alternatively, is there something I may be missing which will allow getting the serial number from within the ALSA API?
EDIT: I'm able to find the serial number via lsusb
, the field name is iSerial
:
$ sudo lsusb -d 1235:8214 -vv | grep iSerial
iSerial 2 4K1A0P443EPEW7
It does seem that I need to be root
to get the actual field value, but I can probably fix this by granting permissions to the device to my user in udev
rules. If I can only get the stable USB device path from an ALSA API, I can then use libusb
or another API to extract the iSerial
field.
Use the index of the card, as returned by snd_ctl_card_info_get_card
. The card index is the number that appears in the name of the sound card’s /dev/snd/controlC⟨idx⟩
device node (and other device nodes associated with the card in /dev/snd/
). From this, you can obtain the corresponding node in the sysfs tree and walk the chain of parent nodes to obtain any identifier you want; either by opening files under /sys
directly or going via udev.
Below is a demonstration using PyALSA and GUdev bindings to Python, but you should be able to write something analogous in any language with bindings to ALSA (libasound) and udev (libudev or GUdev).
#!/usr/bin/env python3
import gi
gi.require_version('GUdev', '1.0')
from gi.repository import GUdev
from pyalsa import alsacard
client = GUdev.Client()
for index in alsacard.card_list():
print(f'index={index!r} longname={alsacard.card_get_longname(index)!r}')
device = client.query_by_subsystem_and_name('sound', 'controlC' + str(index))
usb_device = device.get_parent_with_subsystem('usb', 'usb_device')
if not usb_device:
print(' seems not to be an USB device')
continue
vidpid = f'''{
usb_device.get_sysfs_attr('idVendor')
}:{
usb_device.get_sysfs_attr('idProduct')
}'''
serial = usb_device.get_sysfs_attr('serial')
print(f' VID:PID={vidpid} serial={serial!r}')