Skip to content

Commit f43d36e

Browse files
authored
ALSA: better card enumeration (#916)
- better device name - list physical devices - inject most common virtual ones (default, pipewire, pulse, ...)
1 parent 02b454c commit f43d36e

File tree

2 files changed

+67
-27
lines changed

2 files changed

+67
-27
lines changed

src/host/alsa/enumerate.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,79 @@ use std::sync::{Arc, Mutex};
55

66
/// ALSA's implementation for `Devices`.
77
pub struct Devices {
8-
hint_iter: alsa::device_name::HintIter,
8+
builtin_pos: usize,
9+
card_iter: alsa::card::Iter,
910
}
1011

1112
impl Devices {
1213
pub fn new() -> Result<Self, DevicesError> {
1314
Ok(Devices {
14-
hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
15+
builtin_pos: 0,
16+
card_iter: alsa::card::Iter::new(),
1517
})
1618
}
1719
}
1820

1921
unsafe impl Send for Devices {}
2022
unsafe impl Sync for Devices {}
2123

24+
const BUILTINS: [&'static str; 5] = ["default", "pipewire", "pulse", "jack", "oss"];
25+
2226
impl Iterator for Devices {
2327
type Item = Device;
2428

2529
fn next(&mut self) -> Option<Device> {
30+
while self.builtin_pos < BUILTINS.len() {
31+
let pos = self.builtin_pos;
32+
self.builtin_pos += 1;
33+
let name = BUILTINS[pos];
34+
35+
if let Ok(handles) = DeviceHandles::open(&name) {
36+
return Some(Device {
37+
name: name.to_string(),
38+
pcm_id: name.to_string(),
39+
handles: Arc::new(Mutex::new(handles)),
40+
});
41+
}
42+
}
43+
2644
loop {
27-
match self.hint_iter.next() {
28-
None => return None,
29-
Some(hint) => {
30-
let name = match hint.name {
31-
None => continue,
32-
// Ignoring the `null` device.
33-
Some(name) if name == "null" => continue,
34-
Some(name) => name,
35-
};
45+
let Some(res) = self.card_iter.next() else {
46+
return None;
47+
};
48+
let Ok(card) = res else { continue };
49+
50+
let ctl_id = format!("hw:{}", card.get_index());
51+
let Ok(ctl) = alsa::Ctl::new(&ctl_id, false) else {
52+
continue;
53+
};
54+
let Ok(cardinfo) = ctl.card_info() else {
55+
continue;
56+
};
57+
let Ok(card_name) = cardinfo.get_name() else {
58+
continue;
59+
};
3660

37-
if let Ok(handles) = DeviceHandles::open(&name) {
38-
return Some(Device {
39-
name,
40-
handles: Arc::new(Mutex::new(handles)),
41-
});
42-
}
43-
}
61+
// Using plughw adds the ALSA plug layer, which can do sample type conversion,
62+
// sample rate convertion, ...
63+
// It is convenient, but at the same time not suitable for pro-audio as it hides
64+
// the actual device capabilities and perform audio manipulation under your feet,
65+
// for example sample rate conversion, sample format conversion, adds dummy channels,
66+
// ...
67+
// For now, many hardware only support 24bit / 3 bytes, which isn't yet supported by
68+
// cpal. So we have to enable plughw (unfortunately) for maximum compatibility.
69+
const USE_PLUGHW: bool = true;
70+
let pcm_id = if USE_PLUGHW {
71+
format!("plughw:{}", card.get_index())
72+
} else {
73+
ctl_id
74+
};
75+
if let Ok(handles) = DeviceHandles::open(&pcm_id) {
76+
return Some(Device {
77+
name: card_name.to_string(),
78+
pcm_id: pcm_id.to_string(),
79+
handles: Arc::new(Mutex::new(handles)),
80+
});
4481
}
4582
}
4683
}
@@ -50,6 +87,7 @@ impl Iterator for Devices {
5087
pub fn default_input_device() -> Option<Device> {
5188
Some(Device {
5289
name: "default".to_owned(),
90+
pcm_id: "default".to_owned(),
5391
handles: Arc::new(Mutex::new(Default::default())),
5492
})
5593
}
@@ -58,6 +96,7 @@ pub fn default_input_device() -> Option<Device> {
5896
pub fn default_output_device() -> Option<Device> {
5997
Some(Device {
6098
name: "default".to_owned(),
99+
pcm_id: "default".to_owned(),
61100
handles: Arc::new(Mutex::new(Default::default())),
62101
})
63102
}

src/host/alsa/mod.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ struct DeviceHandles {
185185
impl DeviceHandles {
186186
/// Create `DeviceHandles` for `name` and try to open a handle for both
187187
/// directions. Returns `Ok` if either direction is opened successfully.
188-
fn open(name: &str) -> Result<Self, alsa::Error> {
188+
fn open(pcm_id: &str) -> Result<Self, alsa::Error> {
189189
let mut handles = Self::default();
190-
let playback_err = handles.try_open(name, alsa::Direction::Playback).err();
191-
let capture_err = handles.try_open(name, alsa::Direction::Capture).err();
190+
let playback_err = handles.try_open(pcm_id, alsa::Direction::Playback).err();
191+
let capture_err = handles.try_open(pcm_id, alsa::Direction::Capture).err();
192192
if let Some(err) = capture_err.and(playback_err) {
193193
Err(err)
194194
} else {
@@ -202,7 +202,7 @@ impl DeviceHandles {
202202
/// `Option` is guaranteed to be `Some(..)`.
203203
fn try_open(
204204
&mut self,
205-
name: &str,
205+
pcm_id: &str,
206206
stream_type: alsa::Direction,
207207
) -> Result<&mut Option<alsa::PCM>, alsa::Error> {
208208
let handle = match stream_type {
@@ -211,7 +211,7 @@ impl DeviceHandles {
211211
};
212212

213213
if handle.is_none() {
214-
*handle = Some(alsa::pcm::PCM::new(name, stream_type, true)?);
214+
*handle = Some(alsa::pcm::PCM::new(pcm_id, stream_type, true)?);
215215
}
216216

217217
Ok(handle)
@@ -221,10 +221,10 @@ impl DeviceHandles {
221221
/// If the handle is not yet opened, it will be opened and stored in `self`.
222222
fn get_mut(
223223
&mut self,
224-
name: &str,
224+
pcm_id: &str,
225225
stream_type: alsa::Direction,
226226
) -> Result<&mut alsa::PCM, alsa::Error> {
227-
Ok(self.try_open(name, stream_type)?.as_mut().unwrap())
227+
Ok(self.try_open(pcm_id, stream_type)?.as_mut().unwrap())
228228
}
229229

230230
/// Take ownership of the `alsa::PCM` handle for a specific `stream_type`.
@@ -237,6 +237,7 @@ impl DeviceHandles {
237237
#[derive(Clone)]
238238
pub struct Device {
239239
name: String,
240+
pcm_id: String,
240241
handles: Arc<Mutex<DeviceHandles>>,
241242
}
242243

@@ -251,7 +252,7 @@ impl Device {
251252
.handles
252253
.lock()
253254
.unwrap()
254-
.take(&self.name, stream_type)
255+
.take(&self.pcm_id, stream_type)
255256
.map_err(|e| (e, e.errno()));
256257

257258
let handle = match handle_result {
@@ -308,7 +309,7 @@ impl Device {
308309
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
309310
let mut guard = self.handles.lock().unwrap();
310311
let handle_result = guard
311-
.get_mut(&self.name, stream_t)
312+
.get_mut(&self.pcm_id, stream_t)
312313
.map_err(|e| (e, e.errno()));
313314

314315
let handle = match handle_result {

0 commit comments

Comments
 (0)