从ALSA USB硬件设备获取USB设备文件路径。

huangapple go评论94阅读模式
英文:

Obtain USB Device File Path from ALSA USB Hardware Device

问题

我目前正在开发一个用于管理Focusrite Scarlett USB音频设备的Rust库/CLI/UI,特别是在Linux上。这些设备通常有很多不同的内部设置(我的18i8几乎有300个独立的控制项),所以我正在尝试在设计中考虑所有这些,以使用户可以轻松地更改路由配置、输入/输出/混音增益、静音开关等。

值得庆幸的是,Linux已经通过一些modprobe配置和最近的内核支持这些设备:

/etc/modprobe.d/scarlett.conf:

options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1

对于不同型号和可能不同的主要硬件版本,上述值需要设置为不同的USB产品ID,以支持这些设备。

我已经研究了ALSA API感觉像是_几个星期_,我学到了很多东西:

  1. 有人用C编写了一个用于管理Scarlett设备的GTK4 UI,我基本上阅读了整个代码库,尽管很痛苦,但我能够弄清楚它到底在做什么/使用了哪些ALSA API。
  2. ALSA文档最多糟糕不堪,所以在这个项目之后,我将写博客文章描述我的苦恼,解释其中的微妙之处,同时尝试为ALSA文档做出贡献,以节省其他人尝试弄清楚这些事情的时间。
  3. ALSA用户空间库通常是_对内核系统调用的typedefs_,这意味着对于像snd_ctl_tsnd_hctl_tsnd_mixer_t API这样的东西,用户空间基本上没有代码。所有结构都是不透明的,函数(系统调用)与这些结构引用一起用于获取详细信息。内核在内存上进行一些魔法操作,然后将其提供给用户空间。
  4. 值得庆幸的是,一个勇敢和慷慨的人已经将大部分ALSA API移植到了Rust。我也将为这个项目贡献文档,以帮助其他人走上这条道路。

不管怎样,这是我的困境:我可以列出ALSA设备并对它们进行迭代,通过snd_ctl_card_info_t,我可以获得一些有用的属性,比如:

  • name:Scarlett 18i8 USB
  • id:USB
  • longname:Focusrite Scarlett 18i8 USB at usb-0000:00:14.0-2.2, high speed
  • mixername:USB Mixer
  • components:USB1235:8214
  • driver:USB-Audio

然而,这似乎是我可以从可用的API中获得的最多信息。我可以通过从components字段中提取USB供应商/产品ID来获取型号类型,并且我也许可以通过解析longname找到实际的USB句柄,但是:

  1. 可能会连接多个具有相同USB供应商/产品ID的设备:这是将设备链接在一起以添加输入/输出的常见技术。
  2. 我担心从usb-*标识符和USB供应商/产品ID中提取信息的_稳定性_,也许这会随着内核版本的更改而无效。
  3. 我需要_识别_每个设备,希望能够使用某种类型的序列号来进行配置,以确保卡A(18i8)的配置永远不会错误地应用到卡B(18i8),我假设USB设备中包含了一些类型的序列号字段。

因此,是否有Linux API允许我从某种ALSA句柄中获取USB设备文件? 或者,我是否可能遗漏了一些允许从ALSA API中获取序列号的内容?


编辑:我能够通过lsusb找到序列号,字段名称是iSerial

$ sudo lsusb -d 1235:8214 -vv | grep iSerial
  iSerial          2 4K1A0P443EPEW7

看起来我需要成为root才能获取实际的字段值,但我可以通过在udev规则中授予设备对我的用户的权限来解决这个问题。如果我只能从ALSA API中获取稳定的USB设备路径,然后可以使用libusb或其他API来提取iSerial字段。

英文:

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:

  1. Someone wrote a GTK4 UI in C for managing Scarlett devices, and I have basically read the entire codebase and in spite of all of the pain, I was able to figure out what exactly it is doing/which ALSA APIs are being utilized.
  2. The ALSA documentation is abysmal at best, so after this project I will be writing blog posts describing my woes and explaining the subtleties, as well as attempting to contribute back to the ALSA documentation to save others days/weeks/months of their lives trying to figure these things out.
  3. The ALSA userspace library is often simply typedefs of syscalls to the kernel, meaning that for things like the 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.
  4. Thankfully, a brave and generous soul has ported most of the ALSA API to Rust. I will also be contributing documentation to this project to help others along their way.

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 USB
  • id: USB
  • longname: Focusrite Scarlett 18i8 USB at usb-0000:00:14.0-2.2, high speed
  • mixername: USB Mixer
  • components: USB1235:8214
  • driver: USB-Audio

However, 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:

  1. There may be multiple devices connected of the same USB vendor/product IDs: this is a common technique to chain devices together to add inputs/outputs.
  2. I'm concerned about the stability of extracting the usb-* slug and the USB vendor/product IDs, perhaps this may change with kernel versions and become invalid.
  3. I need to identify each device, hopefully with a serial number of some kind, so that configuration for card A (18i8) will never be inadvertently applied to card B (18i8), and I assume there is some sort of USB device field which contains a serial number for each device.

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.

答案1

得分: 3

以下是翻译好的内容:

"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 <code>/dev/snd/controlC<i>⟨idx⟩</i></code> 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(&#39;GUdev&#39;, &#39;1.0&#39;)
from gi.repository import GUdev
from pyalsa import alsacard

client = GUdev.Client()

for index in alsacard.card_list():
    print(f&#39;index={index!r} longname={alsacard.card_get_longname(index)!r}&#39;)

    device = client.query_by_subsystem_and_name(&#39;sound&#39;, &#39;controlC&#39; + str(index))
    usb_device = device.get_parent_with_subsystem(&#39;usb&#39;, &#39;usb_device&#39;)
    if not usb_device:
        print(&#39;    seems not to be an USB device&#39;)
        continue

    vidpid = f&#39;&#39;&#39;{
        usb_device.get_sysfs_attr(&#39;idVendor&#39;)
    }:{
        usb_device.get_sysfs_attr(&#39;idProduct&#39;)
    }&#39;&#39;&#39;
    serial = usb_device.get_sysfs_attr(&#39;serial&#39;)

    print(f&#39;    VID:PID={vidpid} serial={serial!r}&#39;)

英文:

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 <code>/dev/snd/controlC<i>⟨idx⟩</i></code> 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(&#39;GUdev&#39;, &#39;1.0&#39;)
from gi.repository import GUdev
from pyalsa import alsacard

client = GUdev.Client()

for index in alsacard.card_list():
    print(f&#39;index={index!r} longname={alsacard.card_get_longname(index)!r}&#39;)

    device = client.query_by_subsystem_and_name(&#39;sound&#39;, &#39;controlC&#39; + str(index))
    usb_device = device.get_parent_with_subsystem(&#39;usb&#39;, &#39;usb_device&#39;)
    if not usb_device:
        print(&#39;    seems not to be an USB device&#39;)
        continue

    vidpid = f&#39;&#39;&#39;{
        usb_device.get_sysfs_attr(&#39;idVendor&#39;)
    }:{
        usb_device.get_sysfs_attr(&#39;idProduct&#39;)
    }&#39;&#39;&#39;
    serial = usb_device.get_sysfs_attr(&#39;serial&#39;)

    print(f&#39;    VID:PID={vidpid} serial={serial!r}&#39;)

huangapple
  • 本文由 发表于 2023年8月5日 02:33:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76838423.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定