use std::collections::HashSet;
use super::{alsa, Device, Host};
use crate::{BackendSpecificError, DeviceDirection, DevicesError};
const HW_PREFIX: &str = "hw";
const PLUGHW_PREFIX: &str = "plughw";
struct PhysicalDevice {
card_index: u32,
card_name: Option<String>,
device_index: u32,
device_name: Option<String>,
direction: DeviceDirection,
}
pub type Devices = std::vec::IntoIter<Device>;
impl Host {
pub(super) fn enumerate_devices(&self) -> Result<Devices, DevicesError> {
let mut devices = Vec::new();
let mut seen_pcm_ids = HashSet::new();
let physical_devices = physical_devices();
if let Ok(hints) = alsa::device_name::HintIter::new_str(None, "pcm") {
for hint in hints {
if let Some(pcm_id) = hint.name {
let direction = hint.direction.map_or(DeviceDirection::Duplex, Into::into);
let device = Device {
pcm_id,
desc: hint.desc,
direction,
_context: self.inner.clone(),
};
seen_pcm_ids.insert(device.pcm_id.clone());
devices.push(device);
}
}
}
for phys_dev in physical_devices {
for prefix in [HW_PREFIX, PLUGHW_PREFIX] {
let pcm_id = format!(
"{}:CARD={},DEV={}",
prefix, phys_dev.card_index, phys_dev.device_index
);
if seen_pcm_ids.insert(pcm_id.clone()) {
devices.push(Device {
pcm_id,
desc: Some(format_device_description(&phys_dev, prefix)),
direction: phys_dev.direction,
_context: self.inner.clone(),
});
}
}
}
Ok(devices.into_iter())
}
}
fn format_device_description(phys_dev: &PhysicalDevice, prefix: &str) -> String {
let first_line = match (&phys_dev.card_name, &phys_dev.device_name) {
(Some(card), Some(device)) => format!("{}, {}", card, device),
(Some(card), None) => card.clone(),
(None, Some(device)) => device.clone(),
(None, None) => format!("Card {}", phys_dev.card_index),
};
let second_line = match prefix {
HW_PREFIX => "Direct hardware device without any conversions",
PLUGHW_PREFIX => "Hardware device with all software conversions",
_ => "",
};
format!("{}\n{}", first_line, second_line)
}
fn physical_devices() -> Vec<PhysicalDevice> {
let mut devices = Vec::new();
for card in alsa::card::Iter::new().filter_map(Result::ok) {
let card_index = card.get_index() as u32;
let ctl = match alsa::Ctl::new(&format!("{}:{}", HW_PREFIX, card_index), false) {
Ok(ctl) => ctl,
Err(_) => continue,
};
let card_name = ctl
.card_info()
.ok()
.and_then(|info| info.get_name().ok().map(|s| s.to_string()));
for device_index in alsa::ctl::DeviceIter::new(&ctl) {
let device_index = device_index as u32;
let playback_info = ctl
.pcm_info(device_index, 0, alsa::Direction::Playback)
.ok();
let capture_info = ctl.pcm_info(device_index, 0, alsa::Direction::Capture).ok();
let (direction, device_name) = match (&playback_info, &capture_info) {
(Some(p_info), Some(_c_info)) => (
DeviceDirection::Duplex,
p_info.get_name().ok().map(|s| s.to_string()),
),
(Some(p_info), None) => (
DeviceDirection::Output,
p_info.get_name().ok().map(|s| s.to_string()),
),
(None, Some(c_info)) => (
DeviceDirection::Input,
c_info.get_name().ok().map(|s| s.to_string()),
),
(None, None) => {
continue;
}
};
let device_name = device_name.unwrap_or_else(|| format!("Device {}", device_index));
devices.push(PhysicalDevice {
card_index,
card_name: card_name.clone(),
device_index,
device_name: Some(device_name),
direction,
});
}
}
devices
}
impl From<alsa::Error> for DevicesError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
impl From<alsa::Direction> for DeviceDirection {
fn from(direction: alsa::Direction) -> Self {
match direction {
alsa::Direction::Playback => DeviceDirection::Output,
alsa::Direction::Capture => DeviceDirection::Input,
}
}
}