Skip to content

Commit

Permalink
ALSA: better card enumeration (#916)
Browse files Browse the repository at this point in the history
- better device name
- list physical devices
- inject most common virtual ones (default, pipewire, pulse, ...)
  • Loading branch information
abique authored Oct 11, 2024
1 parent 02b454c commit f43d36e
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 27 deletions.
75 changes: 57 additions & 18 deletions src/host/alsa/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,79 @@ use std::sync::{Arc, Mutex};

/// ALSA's implementation for `Devices`.
pub struct Devices {
hint_iter: alsa::device_name::HintIter,
builtin_pos: usize,
card_iter: alsa::card::Iter,
}

impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices {
hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
builtin_pos: 0,
card_iter: alsa::card::Iter::new(),
})
}
}

unsafe impl Send for Devices {}
unsafe impl Sync for Devices {}

const BUILTINS: [&'static str; 5] = ["default", "pipewire", "pulse", "jack", "oss"];

impl Iterator for Devices {
type Item = Device;

fn next(&mut self) -> Option<Device> {
while self.builtin_pos < BUILTINS.len() {
let pos = self.builtin_pos;
self.builtin_pos += 1;
let name = BUILTINS[pos];

if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name: name.to_string(),
pcm_id: name.to_string(),
handles: Arc::new(Mutex::new(handles)),
});
}
}

loop {
match self.hint_iter.next() {
None => return None,
Some(hint) => {
let name = match hint.name {
None => continue,
// Ignoring the `null` device.
Some(name) if name == "null" => continue,
Some(name) => name,
};
let Some(res) = self.card_iter.next() else {
return None;
};
let Ok(card) = res else { continue };

let ctl_id = format!("hw:{}", card.get_index());
let Ok(ctl) = alsa::Ctl::new(&ctl_id, false) else {
continue;
};
let Ok(cardinfo) = ctl.card_info() else {
continue;
};
let Ok(card_name) = cardinfo.get_name() else {
continue;
};

if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name,
handles: Arc::new(Mutex::new(handles)),
});
}
}
// Using plughw adds the ALSA plug layer, which can do sample type conversion,
// sample rate convertion, ...
// It is convenient, but at the same time not suitable for pro-audio as it hides
// the actual device capabilities and perform audio manipulation under your feet,
// for example sample rate conversion, sample format conversion, adds dummy channels,
// ...
// For now, many hardware only support 24bit / 3 bytes, which isn't yet supported by
// cpal. So we have to enable plughw (unfortunately) for maximum compatibility.
const USE_PLUGHW: bool = true;
let pcm_id = if USE_PLUGHW {
format!("plughw:{}", card.get_index())
} else {
ctl_id
};
if let Ok(handles) = DeviceHandles::open(&pcm_id) {
return Some(Device {
name: card_name.to_string(),
pcm_id: pcm_id.to_string(),
handles: Arc::new(Mutex::new(handles)),
});
}
}
}
Expand All @@ -50,6 +87,7 @@ impl Iterator for Devices {
pub fn default_input_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
pcm_id: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
Expand All @@ -58,6 +96,7 @@ pub fn default_input_device() -> Option<Device> {
pub fn default_output_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
pcm_id: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
Expand Down
19 changes: 10 additions & 9 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ struct DeviceHandles {
impl DeviceHandles {
/// Create `DeviceHandles` for `name` and try to open a handle for both
/// directions. Returns `Ok` if either direction is opened successfully.
fn open(name: &str) -> Result<Self, alsa::Error> {
fn open(pcm_id: &str) -> Result<Self, alsa::Error> {
let mut handles = Self::default();
let playback_err = handles.try_open(name, alsa::Direction::Playback).err();
let capture_err = handles.try_open(name, alsa::Direction::Capture).err();
let playback_err = handles.try_open(pcm_id, alsa::Direction::Playback).err();
let capture_err = handles.try_open(pcm_id, alsa::Direction::Capture).err();
if let Some(err) = capture_err.and(playback_err) {
Err(err)
} else {
Expand All @@ -202,7 +202,7 @@ impl DeviceHandles {
/// `Option` is guaranteed to be `Some(..)`.
fn try_open(
&mut self,
name: &str,
pcm_id: &str,
stream_type: alsa::Direction,
) -> Result<&mut Option<alsa::PCM>, alsa::Error> {
let handle = match stream_type {
Expand All @@ -211,7 +211,7 @@ impl DeviceHandles {
};

if handle.is_none() {
*handle = Some(alsa::pcm::PCM::new(name, stream_type, true)?);
*handle = Some(alsa::pcm::PCM::new(pcm_id, stream_type, true)?);
}

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

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

Expand All @@ -251,7 +252,7 @@ impl Device {
.handles
.lock()
.unwrap()
.take(&self.name, stream_type)
.take(&self.pcm_id, stream_type)
.map_err(|e| (e, e.errno()));

let handle = match handle_result {
Expand Down Expand Up @@ -308,7 +309,7 @@ impl Device {
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
let mut guard = self.handles.lock().unwrap();
let handle_result = guard
.get_mut(&self.name, stream_t)
.get_mut(&self.pcm_id, stream_t)
.map_err(|e| (e, e.errno()));

let handle = match handle_result {
Expand Down

0 comments on commit f43d36e

Please sign in to comment.