Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALSA: better card enumeration #916

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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