fix(qbz-audio): resolve ALSA card by id, not by long name
The CARD= parameter in ALSA device strings refers to the card's short
id (e.g. "C20", "HDMI", "Generic"), not its long descriptive name
("Cambridge Audio USB Audio 2.0"). The previous resolver called
Card::get_name() and silently swallowed errors, so every CARD=<id>
lookup failed and acquire() degraded gracefully -- meaning the
reservation has been non-functional for users with named-card device
strings since Task 3 landed.
Reported by the maintainer on Cambridge DacMagic Plus exposed as
front:CARD=C20,DEV=0, 2026-05-07. Smoke-test scenario 2 had been a
false positive: audio worked because we degraded silently, not because
the reservation succeeded.
In alsa-rs 0.10, Card::get_name() wraps snd_card_get_name and despite
the name returns the LONG descriptive string. The short id (what
aplay -l shows in brackets and what CARD= references) is exposed via
ctl::CardInfo::get_id() (snd_ctl_card_info_get_id). Open a Ctl per
card, read the id from its CardInfo, and surface ALSA enumeration
errors as ReservationError::AlsaError instead of unwrap_or_default()
into an empty string.
cc
This commit is contained in:
1 file changed
+27
-3
@@ -475,12 +475,36 @@ pub(crate) fn parse_card_index(hw_device: &str) -> Result<u32, ReservationError>
|
||||
Err(ReservationError::InvalidDevice(hw_device.to_string()))
|
||||
}
|
||||
|
||||
/// Resolve a symbolic ALSA card name (e.g., `"DacMagic"`) to its kernel index
|
||||
/// by iterating over `alsa::card::Iter`.
|
||||
/// Resolve a symbolic ALSA card name (e.g., `"C20"`, `"DacMagic"`, `"PCH"`)
|
||||
/// to its kernel index by iterating over `alsa::card::Iter`.
|
||||
///
|
||||
/// ALSA's `CARD=` parameter takes the card's *id* (short identifier like
|
||||
/// `"C20"`, `"PCH"`, `"Generic"`, `"HDMI"`) — NOT the long descriptive name
|
||||
/// (`"Cambridge Audio USB Audio 2.0"`). Verified against `aplay -l` output
|
||||
/// on the user's host:
|
||||
///
|
||||
/// ```text
|
||||
/// card 1: C20 [Cambridge Audio USB Audio 2.0], device 0: ...
|
||||
/// ^id ^long name
|
||||
/// ```
|
||||
///
|
||||
/// In `alsa-rs` 0.10, the short id is exposed as `ctl::CardInfo::get_id()`
|
||||
/// (a wrapper around `snd_ctl_card_info_get_id`). The convenience
|
||||
/// `Card::get_name()` actually returns the long name (it wraps
|
||||
/// `snd_card_get_name`, which despite the name returns the descriptive
|
||||
/// "Cambridge Audio USB Audio 2.0" string), so we cannot use it here. We
|
||||
/// open a `Ctl` per card and read the id from its `CardInfo`.
|
||||
fn resolve_card_index_by_name(name: &str) -> Result<u32, ReservationError> {
|
||||
for card in alsa::card::Iter::new() {
|
||||
let card = card.map_err(|e| ReservationError::AlsaError(e.to_string()))?;
|
||||
let id = card.get_name().unwrap_or_default();
|
||||
let ctl = alsa::ctl::Ctl::from_card(&card, true)
|
||||
.map_err(|e| ReservationError::AlsaError(e.to_string()))?;
|
||||
let info = ctl
|
||||
.card_info()
|
||||
.map_err(|e| ReservationError::AlsaError(e.to_string()))?;
|
||||
let id = info
|
||||
.get_id()
|
||||
.map_err(|e| ReservationError::AlsaError(e.to_string()))?;
|
||||
if id == name {
|
||||
return Ok(card.get_index() as u32);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user