diff --git a/src/host/alsa/enumerate.rs b/src/host/alsa/enumerate.rs index 3032861c9..b031b4a21 100644 --- a/src/host/alsa/enumerate.rs +++ b/src/host/alsa/enumerate.rs @@ -5,13 +5,15 @@ 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 { Ok(Devices { - hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?, + builtin_pos: 0, + card_iter: alsa::card::Iter::new(), }) } } @@ -19,28 +21,63 @@ impl Devices { 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 { + 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)), + }); } } } @@ -50,6 +87,7 @@ impl Iterator for Devices { pub fn default_input_device() -> Option { Some(Device { name: "default".to_owned(), + pcm_id: "default".to_owned(), handles: Arc::new(Mutex::new(Default::default())), }) } @@ -58,6 +96,7 @@ pub fn default_input_device() -> Option { pub fn default_output_device() -> Option { Some(Device { name: "default".to_owned(), + pcm_id: "default".to_owned(), handles: Arc::new(Mutex::new(Default::default())), }) } diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 2085c20b7..515d5abea 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -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 { + fn open(pcm_id: &str) -> Result { 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 { @@ -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::Error> { let handle = match stream_type { @@ -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) @@ -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`. @@ -237,6 +237,7 @@ impl DeviceHandles { #[derive(Clone)] pub struct Device { name: String, + pcm_id: String, handles: Arc>, } @@ -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 { @@ -308,7 +309,7 @@ impl Device { ) -> Result, 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 {