#![allow(unsafe_code)]
use std::{
convert::TryInto,
ffi::CStr,
mem::MaybeUninit,
os::raw::{c_char, c_void},
};
use fon::chan::{Ch32, Channel};
use super::{
free, pcm, Alsa, SndPcmAccess, SndPcmFormat, SndPcmMode, SndPcmStream,
};
pub(crate) const DEFAULT: &[u8] = b"default\0";
pub(crate) unsafe fn reset_hwp(
pcm: *mut c_void,
hwp: *mut c_void,
) -> Option<()> {
let format = if cfg!(target_endian = "little") {
SndPcmFormat::FloatLe
} else if cfg!(target_endian = "big") {
SndPcmFormat::FloatBe
} else {
unreachable!()
};
pcm::hw_params_any(pcm, hwp).ok()?;
pcm::hw_params_set_access(pcm, hwp, SndPcmAccess::RwInterleaved).ok()?;
pcm::hw_params_set_format(pcm, hwp, format).ok()?;
Some(())
}
pub(crate) fn open(
name: *const c_char,
stream: SndPcmStream,
) -> Option<(*mut c_void, *mut c_void, u8)> {
unsafe {
let pcm = pcm::open(name, stream, SndPcmMode::Nonblock).ok()?;
let hwp = pcm::hw_params_malloc().ok()?;
let mut channels = 0;
reset_hwp(pcm, hwp)?;
for i in 1..=8 {
if pcm::hw_test_channels(pcm, hwp, i).is_ok() {
channels |= 1 << (i - 1);
}
}
Some((pcm, hwp, channels))
}
}
pub(crate) trait SoundDevice:
std::fmt::Display + From<AudioDevice>
{
const INPUT: bool;
fn pcm(&self) -> *mut c_void;
fn hwp(&self) -> *mut c_void;
}
#[derive(Debug)]
pub(crate) struct AudioDevice {
pub(crate) name: String,
pub(crate) pcm: *mut c_void,
pub(crate) hwp: *mut c_void,
pub(crate) supported: u8,
pub(crate) fds: Vec<smelling_salts::Device>,
}
impl AudioDevice {
pub(crate) fn start(&mut self) -> Option<()> {
assert!(self.fds.is_empty());
let fd_list = unsafe { pcm::poll_descriptors(self.pcm).ok()? };
for fd in fd_list {
self.fds.push(smelling_salts::Device::new(fd.fd, unsafe {
smelling_salts::Watcher::from_raw(fd.events as u32)
}));
}
Some(())
}
}
impl Drop for AudioDevice {
fn drop(&mut self) {
for fd in &mut self.fds {
fd.old();
}
unsafe {
pcm::hw_params_free(self.hwp);
pcm::close(self.pcm).unwrap();
}
}
}
pub(crate) fn device_list<D: SoundDevice, F: Fn(D) -> T, T>(
abstrakt: F,
) -> Vec<T> {
super::ALSA.with(|alsa| {
if let Some(alsa) = alsa {
device_list_internal(&alsa, abstrakt)
} else {
Vec::new()
}
})
}
fn device_list_internal<D: SoundDevice, F: Fn(D) -> T, T>(
alsa: &Alsa,
abstrakt: F,
) -> Vec<T> {
let tpcm = CStr::from_bytes_with_nul(b"pcm\0").unwrap();
let tname = CStr::from_bytes_with_nul(b"NAME\0").unwrap();
let tdesc = CStr::from_bytes_with_nul(b"DESC\0").unwrap();
let tioid = CStr::from_bytes_with_nul(b"IOID\0").unwrap();
let mut hints = MaybeUninit::uninit();
let mut devices = Vec::new();
unsafe {
if (alsa.snd_device_name_hint)(-1, tpcm.as_ptr(), hints.as_mut_ptr())
< 0
{
return Vec::new();
}
let hints = hints.assume_init();
let mut n = hints;
while !(*n).is_null() {
let pcm_name = (alsa.snd_device_name_get_hint)(*n, tname.as_ptr());
let io = (alsa.snd_device_name_get_hint)(*n, tioid.as_ptr());
debug_assert_ne!(pcm_name, std::ptr::null_mut());
let name = match CStr::from_ptr(pcm_name).to_str() {
Ok(x) if x.starts_with("sysdefault") => {
n = n.offset(1);
continue;
}
Ok("null") => {
n = n.offset(1);
continue;
}
Ok("default") => "Default".to_string(),
_a => {
let name =
(alsa.snd_device_name_get_hint)(*n, tdesc.as_ptr());
assert_ne!(name, std::ptr::null_mut());
let rust =
CStr::from_ptr(name).to_string_lossy().to_string();
free(name.cast());
rust.replace("\n", ": ")
}
};
let is_input = io.is_null() || *(io.cast::<u8>()) == b'I';
let is_output = io.is_null() || *(io.cast::<u8>()) == b'O';
if !io.is_null() {
free(io.cast());
}
if (D::INPUT && is_input) || (!D::INPUT && is_output) {
let dev = open(
pcm_name,
if D::INPUT {
SndPcmStream::Capture
} else {
SndPcmStream::Playback
},
);
if let Some((pcm, hwp, supported)) = dev {
devices.push(abstrakt(D::from(AudioDevice {
name,
pcm,
hwp,
supported,
fds: Vec::new(),
})));
}
}
free(pcm_name.cast());
n = n.offset(1);
}
(alsa.snd_device_name_free_hint)(hints);
}
devices
}
#[allow(unsafe_code)]
pub(crate) fn pcm_hw_params(
device: &AudioDevice,
channels: u8,
buffer: &mut Vec<Ch32>,
sample_rate: &mut Option<f64>,
period: &mut u16,
) -> Option<()> {
unsafe {
reset_hwp(device.pcm, device.hwp)?;
pcm::hw_params_set_rate_near(
device.pcm,
device.hwp,
&mut crate::consts::SAMPLE_RATE.into(),
&mut 0,
)
.ok()?;
pcm::hw_set_channels(device.pcm, device.hwp, channels).ok()?;
let mut period_size = crate::consts::PERIOD.into();
pcm::hw_params_set_period_size_near(
device.pcm,
device.hwp,
&mut period_size,
&mut 0,
)
.ok()?;
pcm::hw_params_set_buffer_size_near(
device.pcm,
device.hwp,
&mut period_size,
)
.ok()?;
pcm::hw_params(device.pcm, device.hwp).ok()?;
*sample_rate = Some(pcm::hw_get_rate(device.hwp)?);
*period = period_size.try_into().ok()?;
buffer.resize(*period as usize * channels as usize, Ch32::MID);
let _ = pcm::drop(device.pcm);
pcm::prepare(device.pcm).ok()?;
}
Some(())
}