extern crate alsa;
extern crate libc;
use std::{
cmp,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
},
thread::{self, JoinHandle},
time::Duration,
vec::IntoIter as VecIntoIter,
};
use self::alsa::poll::Descriptors;
pub use self::enumerate::Devices;
use crate::{
iter::{SupportedInputConfigs, SupportedOutputConfigs},
traits::{DeviceTrait, HostTrait, StreamTrait},
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceDescription, DeviceDescriptionBuilder, DeviceDirection,
DeviceId, DeviceIdError, DeviceNameError, DevicesError, FrameCount, InputCallbackInfo,
OutputCallbackInfo, PauseStreamError, PlayStreamError, Sample, SampleFormat, SampleRate,
StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError, I24, U24,
};
mod enumerate;
const DEFAULT_DEVICE: &str = "default";
const LIBC_ENOTSUPP: libc::c_int = 524;
#[derive(Debug, Clone)]
pub struct Host {
inner: Arc<AlsaContext>,
}
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
let inner = AlsaContext::new().map_err(|_| crate::HostUnavailable)?;
Ok(Host {
inner: Arc::new(inner),
})
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
self.enumerate_devices()
}
fn default_input_device(&self) -> Option<Self::Device> {
Some(Device::default())
}
fn default_output_device(&self) -> Option<Self::Device> {
Some(Device::default())
}
}
static ALSA_CONTEXT_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
pub(super) struct AlsaContext;
impl AlsaContext {
fn new() -> Result<Self, alsa::Error> {
if ALSA_CONTEXT_COUNT.fetch_add(1, Ordering::SeqCst) == 0 {
alsa::config::update()?;
}
Ok(Self)
}
}
impl Drop for AlsaContext {
fn drop(&mut self) {
if ALSA_CONTEXT_COUNT.fetch_sub(1, Ordering::SeqCst) == 1 {
let _ = alsa::config::update_free_global();
}
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
fn description(&self) -> Result<DeviceDescription, DeviceNameError> {
Device::description(self)
}
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}
fn supports_input(&self) -> bool {
matches!(
self.direction,
DeviceDirection::Input | DeviceDirection::Duplex
)
}
fn supports_output(&self) -> bool {
matches!(
self.direction,
DeviceDirection::Output | DeviceDirection::Duplex
)
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let stream_inner =
self.build_stream_inner(conf, sample_format, alsa::Direction::Capture)?;
let stream = Self::Stream::new_input(
Arc::new(stream_inner),
data_callback,
error_callback,
timeout,
);
Ok(stream)
}
fn build_output_stream_raw<D, E>(
&self,
conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let stream_inner =
self.build_stream_inner(conf, sample_format, alsa::Direction::Playback)?;
let stream = Self::Stream::new_output(
Arc::new(stream_inner),
data_callback,
error_callback,
timeout,
);
Ok(stream)
}
}
#[derive(Debug)]
struct TriggerSender(libc::c_int);
#[derive(Debug)]
struct TriggerReceiver(libc::c_int);
impl TriggerSender {
fn wakeup(&self) {
let buf = 1u64;
let ret = unsafe { libc::write(self.0, &buf as *const u64 as *const _, 8) };
assert_eq!(ret, 8);
}
}
impl TriggerReceiver {
fn clear_pipe(&self) {
let mut out = 0u64;
let ret = unsafe { libc::read(self.0, &mut out as *mut u64 as *mut _, 8) };
assert_eq!(ret, 8);
}
}
fn trigger() -> (TriggerSender, TriggerReceiver) {
let mut fds = [0, 0];
match unsafe { libc::pipe(fds.as_mut_ptr()) } {
0 => (TriggerSender(fds[1]), TriggerReceiver(fds[0])),
_ => panic!("Could not create pipe"),
}
}
impl Drop for TriggerSender {
fn drop(&mut self) {
unsafe {
libc::close(self.0);
}
}
}
impl Drop for TriggerReceiver {
fn drop(&mut self) {
unsafe {
libc::close(self.0);
}
}
}
#[derive(Clone, Debug)]
pub struct Device {
pcm_id: String,
desc: Option<String>,
direction: DeviceDirection,
_context: Arc<AlsaContext>,
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.pcm_id == other.pcm_id
}
}
impl Eq for Device {}
impl std::hash::Hash for Device {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.pcm_id.hash(state);
}
}
impl Device {
fn build_stream_inner(
&self,
conf: &StreamConfig,
sample_format: SampleFormat,
stream_type: alsa::Direction,
) -> Result<StreamInner, BuildStreamError> {
if let BufferSize::Fixed(requested_size) = conf.buffer_size {
let supported_config = match stream_type {
alsa::Direction::Capture => self.default_input_config(),
alsa::Direction::Playback => self.default_output_config(),
};
if let Ok(config) = supported_config {
if let SupportedBufferSize::Range { min, max } = config.buffer_size {
if !(min..=max).contains(&requested_size) {
return Err(BuildStreamError::StreamConfigNotSupported);
}
}
}
}
let handle = match alsa::pcm::PCM::new(&self.pcm_id, stream_type, true)
.map_err(|e| (e, e.errno()))
{
Err((_, libc::ENOENT))
| Err((_, libc::EPERM))
| Err((_, libc::ENODEV))
| Err((_, LIBC_ENOTSUPP))
| Err((_, libc::EBUSY))
| Err((_, libc::EAGAIN)) => return Err(BuildStreamError::DeviceNotAvailable),
Err((_, libc::EINVAL)) => return Err(BuildStreamError::InvalidArgument),
Err((e, _)) => return Err(e.into()),
Ok(handle) => handle,
};
let can_pause = set_hw_params_from_format(&handle, conf, sample_format)?;
let period_samples = set_sw_params_from_format(&handle, conf, stream_type)?;
handle.prepare()?;
let num_descriptors = handle.count();
if num_descriptors == 0 {
let description = "poll descriptor count for stream was 0".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
let ts = handle.status()?.get_htstamp();
let creation_instant = match (ts.tv_sec, ts.tv_nsec) {
(0, 0) => Some(std::time::Instant::now()),
_ => None,
};
if let alsa::Direction::Capture = stream_type {
handle.start()?;
}
let period_frames = period_samples / conf.channels as usize;
let period_bytes = period_samples * sample_format.sample_size();
let mut silence_template = vec![0u8; period_bytes].into_boxed_slice();
if sample_format.is_uint() {
fill_with_equilibrium(&mut silence_template, sample_format);
}
let stream_inner = StreamInner {
dropping: AtomicBool::new(false),
channel: handle,
sample_format,
num_descriptors,
conf: conf.clone(),
period_samples,
period_frames,
silence_template,
can_pause,
creation_instant,
_context: self._context.clone(),
};
Ok(stream_inner)
}
fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.pcm_id.clone())
}
fn description(&self) -> Result<DeviceDescription, DeviceNameError> {
let name = self
.desc
.as_ref()
.and_then(|desc| desc.lines().next())
.unwrap_or(&self.pcm_id)
.to_string();
let mut builder = DeviceDescriptionBuilder::new(name)
.driver(self.pcm_id.clone())
.direction(self.direction);
if let Some(ref desc) = self.desc {
let lines = desc
.lines()
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.collect();
builder = builder.extended(lines);
}
Ok(builder.build())
}
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId(crate::platform::HostId::Alsa, self.pcm_id.clone()))
}
fn supported_configs(
&self,
stream_t: alsa::Direction,
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
let pcm =
match alsa::pcm::PCM::new(&self.pcm_id, stream_t, true).map_err(|e| (e, e.errno())) {
Err((_, libc::ENOENT))
| Err((_, libc::EPERM))
| Err((_, libc::ENODEV))
| Err((_, LIBC_ENOTSUPP))
| Err((_, libc::EBUSY))
| Err((_, libc::EAGAIN)) => {
return Err(SupportedStreamConfigsError::DeviceNotAvailable)
}
Err((_, libc::EINVAL)) => return Err(SupportedStreamConfigsError::InvalidArgument),
Err((e, _)) => return Err(e.into()),
Ok(pcm) => pcm,
};
let hw_params = alsa::pcm::HwParams::any(&pcm)?;
const FORMATS: [(SampleFormat, alsa::pcm::Format); 23] = [
(SampleFormat::I8, alsa::pcm::Format::S8),
(SampleFormat::U8, alsa::pcm::Format::U8),
(SampleFormat::I16, alsa::pcm::Format::S16LE),
(SampleFormat::I16, alsa::pcm::Format::S16BE),
(SampleFormat::U16, alsa::pcm::Format::U16LE),
(SampleFormat::U16, alsa::pcm::Format::U16BE),
(SampleFormat::I24, alsa::pcm::Format::S24LE),
(SampleFormat::I24, alsa::pcm::Format::S24BE),
(SampleFormat::U24, alsa::pcm::Format::U24LE),
(SampleFormat::U24, alsa::pcm::Format::U24BE),
(SampleFormat::I32, alsa::pcm::Format::S32LE),
(SampleFormat::I32, alsa::pcm::Format::S32BE),
(SampleFormat::U32, alsa::pcm::Format::U32LE),
(SampleFormat::U32, alsa::pcm::Format::U32BE),
(SampleFormat::F32, alsa::pcm::Format::FloatLE),
(SampleFormat::F32, alsa::pcm::Format::FloatBE),
(SampleFormat::F64, alsa::pcm::Format::Float64LE),
(SampleFormat::F64, alsa::pcm::Format::Float64BE),
(SampleFormat::DsdU8, alsa::pcm::Format::DSDU8),
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16LE),
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16BE),
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32LE),
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32BE),
];
let mut supported_formats = Vec::new();
for &(sample_format, alsa_format) in FORMATS.iter() {
if hw_params.test_format(alsa_format).is_ok()
&& !supported_formats.contains(&sample_format)
{
supported_formats.push(sample_format);
}
}
let min_rate = hw_params.get_rate_min()?;
let max_rate = hw_params.get_rate_max()?;
let sample_rates = if min_rate == max_rate || hw_params.test_rate(min_rate + 1).is_ok() {
vec![(min_rate, max_rate)]
} else {
let mut rates = Vec::new();
for &sample_rate in crate::COMMON_SAMPLE_RATES.iter() {
if hw_params.test_rate(sample_rate).is_ok() {
rates.push((sample_rate, sample_rate));
}
}
if rates.is_empty() {
vec![(min_rate, max_rate)]
} else {
rates
}
};
let min_channels = hw_params.get_channels_min()?;
let max_channels = hw_params.get_channels_max()?;
let max_channels = cmp::min(max_channels, 32); let supported_channels = (min_channels..max_channels + 1)
.filter_map(|num| {
if hw_params.test_channels(num).is_ok() {
Some(num as ChannelCount)
} else {
None
}
})
.collect::<Vec<_>>();
let (min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max(&hw_params);
let buffer_size_range = SupportedBufferSize::Range {
min: min_buffer_size,
max: max_buffer_size,
};
let mut output = Vec::with_capacity(
supported_formats.len() * supported_channels.len() * sample_rates.len(),
);
for &sample_format in supported_formats.iter() {
for &channels in supported_channels.iter() {
for &(min_rate, max_rate) in sample_rates.iter() {
output.push(SupportedStreamConfigRange {
channels,
min_sample_rate: min_rate,
max_sample_rate: max_rate,
buffer_size: buffer_size_range,
sample_format,
});
}
}
}
Ok(output.into_iter())
}
fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
self.supported_configs(alsa::Direction::Capture)
}
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
self.supported_configs(alsa::Direction::Playback)
}
fn default_config(
&self,
stream_t: alsa::Direction,
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let mut formats: Vec<_> = {
match self.supported_configs(stream_t) {
Err(SupportedStreamConfigsError::DeviceNotAvailable) => {
return Err(DefaultStreamConfigError::DeviceNotAvailable);
}
Err(SupportedStreamConfigsError::InvalidArgument) => {
return Err(DefaultStreamConfigError::StreamTypeNotSupported);
}
Err(SupportedStreamConfigsError::BackendSpecific { err }) => {
return Err(err.into());
}
Ok(fmts) => fmts.collect(),
}
};
formats.sort_by(|a, b| a.cmp_default_heuristics(b));
match formats.into_iter().next_back() {
Some(f) => {
let min_r = f.min_sample_rate;
let max_r = f.max_sample_rate;
let mut format = f.with_max_sample_rate();
const HZ_44100: SampleRate = 44_100;
if min_r <= HZ_44100 && HZ_44100 <= max_r {
format.sample_rate = HZ_44100;
}
Ok(format)
}
None => Err(DefaultStreamConfigError::StreamTypeNotSupported),
}
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(alsa::Direction::Capture)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(alsa::Direction::Playback)
}
}
impl Default for Device {
fn default() -> Self {
Self {
pcm_id: DEFAULT_DEVICE.to_owned(),
desc: Some("Default Audio Device".to_string()),
direction: DeviceDirection::Unknown,
_context: Arc::new(
AlsaContext::new().expect("Failed to initialize ALSA configuration"),
),
}
}
}
#[derive(Debug)]
struct StreamInner {
dropping: AtomicBool,
channel: alsa::pcm::PCM,
num_descriptors: usize,
sample_format: SampleFormat,
conf: StreamConfig,
period_samples: usize,
period_frames: usize,
silence_template: Box<[u8]>,
#[allow(dead_code)]
can_pause: bool,
creation_instant: Option<std::time::Instant>,
_context: Arc<AlsaContext>,
}
unsafe impl Sync for StreamInner {}
#[derive(Debug)]
pub struct Stream {
thread: Option<JoinHandle<()>>,
inner: Arc<StreamInner>,
trigger: TriggerSender,
}
crate::assert_stream_send!(Stream);
crate::assert_stream_sync!(Stream);
struct StreamWorkerContext {
descriptors: Box<[libc::pollfd]>,
transfer_buffer: Box<[u8]>,
poll_timeout: i32,
}
impl StreamWorkerContext {
fn new(poll_timeout: &Option<Duration>, stream: &StreamInner, rx: &TriggerReceiver) -> Self {
let poll_timeout: i32 = if let Some(d) = poll_timeout {
d.as_millis().try_into().unwrap()
} else {
-1 };
let transfer_buffer = stream.silence_template.clone();
let total_descriptors = 1 + stream.num_descriptors;
let mut descriptors = vec![
libc::pollfd {
fd: 0,
events: 0,
revents: 0
};
total_descriptors
]
.into_boxed_slice();
descriptors[0] = libc::pollfd {
fd: rx.0,
events: libc::POLLIN,
revents: 0,
};
let filled = stream
.channel
.fill(&mut descriptors[1..])
.expect("Failed to fill ALSA descriptors");
debug_assert_eq!(filled, stream.num_descriptors);
Self {
descriptors,
transfer_buffer,
poll_timeout,
}
}
}
fn input_stream_worker(
rx: TriggerReceiver,
stream: &StreamInner,
data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
timeout: Option<Duration>,
) {
boost_current_thread_priority(stream.conf.buffer_size, stream.conf.sample_rate);
let mut ctxt = StreamWorkerContext::new(&timeout, stream, &rx);
loop {
let flow =
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt).unwrap_or_else(|err| {
error_callback(err.into());
PollDescriptorsFlow::Continue
});
match flow {
PollDescriptorsFlow::Continue => {
continue;
}
PollDescriptorsFlow::XRun => {
error_callback(StreamError::BufferUnderrun);
if let Err(err) = stream.channel.prepare() {
error_callback(err.into());
}
continue;
}
PollDescriptorsFlow::Return => return,
PollDescriptorsFlow::Ready {
status,
delay_frames,
} => {
if let Err(err) = process_input(
stream,
&mut ctxt.transfer_buffer,
status,
delay_frames,
data_callback,
) {
error_callback(err.into());
}
}
}
}
}
fn output_stream_worker(
rx: TriggerReceiver,
stream: &StreamInner,
data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
timeout: Option<Duration>,
) {
boost_current_thread_priority(stream.conf.buffer_size, stream.conf.sample_rate);
let mut ctxt = StreamWorkerContext::new(&timeout, stream, &rx);
loop {
let flow =
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt).unwrap_or_else(|err| {
error_callback(err.into());
PollDescriptorsFlow::Continue
});
match flow {
PollDescriptorsFlow::Continue => continue,
PollDescriptorsFlow::XRun => {
error_callback(StreamError::BufferUnderrun);
if let Err(err) = stream.channel.prepare() {
error_callback(err.into());
}
continue;
}
PollDescriptorsFlow::Return => return,
PollDescriptorsFlow::Ready {
status,
delay_frames,
} => {
if let Err(err) = process_output(
stream,
&mut ctxt.transfer_buffer,
status,
delay_frames,
data_callback,
error_callback,
) {
error_callback(err.into());
}
}
}
}
}
#[cfg(feature = "audio_thread_priority")]
fn boost_current_thread_priority(buffer_size: BufferSize, sample_rate: SampleRate) {
use audio_thread_priority::promote_current_thread_to_real_time;
let buffer_size = if let BufferSize::Fixed(buffer_size) = buffer_size {
buffer_size
} else {
0
};
if let Err(err) = promote_current_thread_to_real_time(buffer_size, sample_rate) {
eprintln!("Failed to promote audio thread to real-time priority: {err}");
}
}
#[cfg(not(feature = "audio_thread_priority"))]
fn boost_current_thread_priority(_: BufferSize, _: SampleRate) {}
enum PollDescriptorsFlow {
Continue,
Return,
Ready {
status: alsa::pcm::Status,
delay_frames: usize,
},
XRun,
}
fn poll_descriptors_and_prepare_buffer(
rx: &TriggerReceiver,
stream: &StreamInner,
ctxt: &mut StreamWorkerContext,
) -> Result<PollDescriptorsFlow, BackendSpecificError> {
if stream.dropping.load(Ordering::Acquire) {
rx.clear_pipe();
return Ok(PollDescriptorsFlow::Return);
}
let StreamWorkerContext {
ref mut descriptors,
ref poll_timeout,
..
} = *ctxt;
let res = alsa::poll::poll(descriptors, *poll_timeout)?;
if res == 0 {
let description = String::from("`alsa::poll()` spuriously returned");
return Err(BackendSpecificError { description });
}
if descriptors[0].revents != 0 {
rx.clear_pipe();
return Ok(PollDescriptorsFlow::Return);
}
let revents = stream.channel.revents(&descriptors[1..])?;
if revents.contains(alsa::poll::Flags::ERR) {
let description = String::from("`alsa::poll()` returned POLLERR");
return Err(BackendSpecificError { description });
}
if !revents.contains(alsa::poll::Flags::IN) && !revents.contains(alsa::poll::Flags::OUT) {
return Ok(PollDescriptorsFlow::Continue);
}
let status = stream.channel.status()?;
let avail_frames = match stream.channel.avail() {
Err(err) if err.errno() == libc::EPIPE => return Ok(PollDescriptorsFlow::XRun),
res => res,
}? as usize;
let delay_frames = match status.get_delay() {
d if d < 0 => 0,
d => d as usize,
};
let available_samples = avail_frames * stream.conf.channels as usize;
if available_samples < stream.period_samples {
return Ok(PollDescriptorsFlow::Continue);
}
Ok(PollDescriptorsFlow::Ready {
status,
delay_frames,
})
}
fn process_input(
stream: &StreamInner,
buffer: &mut [u8],
status: alsa::pcm::Status,
delay_frames: usize,
data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
) -> Result<(), BackendSpecificError> {
stream.channel.io_bytes().readi(buffer)?;
let data = buffer.as_mut_ptr() as *mut ();
let data = unsafe { Data::from_parts(data, stream.period_samples, stream.sample_format) };
let callback = match stream.creation_instant {
None => stream_timestamp_hardware(&status)?,
Some(creation) => stream_timestamp_fallback(creation)?,
};
let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
let capture = callback
.sub(delay_duration)
.ok_or_else(|| BackendSpecificError {
description: "`capture` is earlier than representation supported by `StreamInstant`"
.to_string(),
})?;
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = crate::InputCallbackInfo { timestamp };
data_callback(&data, &info);
Ok(())
}
fn process_output(
stream: &StreamInner,
buffer: &mut [u8],
status: alsa::pcm::Status,
delay_frames: usize,
data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
error_callback: &mut dyn FnMut(StreamError),
) -> Result<(), BackendSpecificError> {
buffer.copy_from_slice(&stream.silence_template);
{
let data = buffer.as_mut_ptr() as *mut ();
let mut data =
unsafe { Data::from_parts(data, stream.period_samples, stream.sample_format) };
let callback = match stream.creation_instant {
None => stream_timestamp_hardware(&status)?,
Some(creation) => stream_timestamp_fallback(creation)?,
};
let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
let playback = callback
.add(delay_duration)
.ok_or_else(|| BackendSpecificError {
description: "`playback` occurs beyond representation supported by `StreamInstant`"
.to_string(),
})?;
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = crate::OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
}
loop {
match stream.channel.io_bytes().writei(buffer) {
Err(err) if err.errno() == libc::EPIPE => {
error_callback(StreamError::BufferUnderrun);
if let Err(recover_err) = stream.channel.try_recover(err, true) {
error_callback(recover_err.into());
}
}
Err(err) => {
error_callback(err.into());
continue;
}
Ok(result) if result != stream.period_frames => {
let description = format!(
"unexpected number of frames written: expected {}, \
result {result} (this should never happen)",
stream.period_frames
);
error_callback(BackendSpecificError { description }.into());
continue;
}
_ => {
break;
}
}
}
Ok(())
}
#[inline]
fn stream_timestamp_hardware(
status: &alsa::pcm::Status,
) -> Result<crate::StreamInstant, BackendSpecificError> {
let trigger_ts = status.get_trigger_htstamp();
let ts = status.get_htstamp();
let nanos = timespec_diff_nanos(ts, trigger_ts);
if nanos < 0 {
let description = format!(
"get_htstamp `{}.{}` was earlier than get_trigger_htstamp `{}.{}`",
ts.tv_sec, ts.tv_nsec, trigger_ts.tv_sec, trigger_ts.tv_nsec
);
return Err(BackendSpecificError { description });
}
Ok(crate::StreamInstant::from_nanos(nanos))
}
#[inline]
fn stream_timestamp_fallback(
creation: std::time::Instant,
) -> Result<crate::StreamInstant, BackendSpecificError> {
let now = std::time::Instant::now();
let duration = now.duration_since(creation);
crate::StreamInstant::from_nanos_i128(duration.as_nanos() as i128).ok_or(BackendSpecificError {
description: "stream duration has exceeded `StreamInstant` representation".to_string(),
})
}
#[inline]
fn timespec_to_nanos(ts: libc::timespec) -> i64 {
let nanos = ts.tv_sec * 1_000_000_000 + ts.tv_nsec;
#[cfg(target_pointer_width = "64")]
return nanos;
#[cfg(not(target_pointer_width = "64"))]
return nanos.into();
}
#[inline]
fn timespec_diff_nanos(a: libc::timespec, b: libc::timespec) -> i64 {
timespec_to_nanos(a) - timespec_to_nanos(b)
}
#[inline]
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
impl Stream {
fn new_input<D, E>(
inner: Arc<StreamInner>,
mut data_callback: D,
mut error_callback: E,
timeout: Option<Duration>,
) -> Stream
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let (tx, rx) = trigger();
let stream = inner.clone();
let thread = thread::Builder::new()
.name("cpal_alsa_in".to_owned())
.spawn(move || {
input_stream_worker(
rx,
&stream,
&mut data_callback,
&mut error_callback,
timeout,
);
})
.unwrap();
Self {
thread: Some(thread),
inner,
trigger: tx,
}
}
fn new_output<D, E>(
inner: Arc<StreamInner>,
mut data_callback: D,
mut error_callback: E,
timeout: Option<Duration>,
) -> Stream
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let (tx, rx) = trigger();
let stream = inner.clone();
let thread = thread::Builder::new()
.name("cpal_alsa_out".to_owned())
.spawn(move || {
output_stream_worker(
rx,
&stream,
&mut data_callback,
&mut error_callback,
timeout,
);
})
.unwrap();
Self {
thread: Some(thread),
inner,
trigger: tx,
}
}
}
impl Drop for Stream {
fn drop(&mut self) {
self.inner.dropping.store(true, Ordering::Release);
self.trigger.wakeup();
if let Some(handle) = self.thread.take() {
let _ = handle.join();
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
self.inner.channel.pause(false).ok();
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
self.inner.channel.pause(true).ok();
Ok(())
}
}
fn clamp_frame_count(buffer_size: alsa::pcm::Frames) -> FrameCount {
buffer_size.max(1).try_into().unwrap_or(FrameCount::MAX)
}
fn hw_params_buffer_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount, FrameCount) {
let min_buf = hw_params
.get_buffer_size_min()
.map(clamp_frame_count)
.unwrap_or(1);
let max_buf = hw_params
.get_buffer_size_max()
.map(clamp_frame_count)
.unwrap_or(FrameCount::MAX);
(min_buf, max_buf)
}
fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) {
macro_rules! fill_typed {
($sample_type:ty) => {{
let sample_size = std::mem::size_of::<$sample_type>();
assert_eq!(
buffer.len() % sample_size,
0,
"Buffer size must be aligned to sample size for format {:?}",
sample_format
);
let num_samples = buffer.len() / sample_size;
let equilibrium = <$sample_type as Sample>::EQUILIBRIUM;
let samples = unsafe {
std::slice::from_raw_parts_mut(
buffer.as_mut_ptr() as *mut $sample_type,
num_samples,
)
};
for sample in samples {
*sample = equilibrium;
}
}};
}
const DSD_SILENCE_BYTE: u8 = 0x69;
match sample_format {
SampleFormat::I8 => fill_typed!(i8),
SampleFormat::I16 => fill_typed!(i16),
SampleFormat::I24 => fill_typed!(I24),
SampleFormat::I32 => fill_typed!(i32),
SampleFormat::I64 => fill_typed!(i64),
SampleFormat::U8 => fill_typed!(u8),
SampleFormat::U16 => fill_typed!(u16),
SampleFormat::U24 => fill_typed!(U24),
SampleFormat::U32 => fill_typed!(u32),
SampleFormat::U64 => fill_typed!(u64),
SampleFormat::F32 => fill_typed!(f32),
SampleFormat::F64 => fill_typed!(f64),
SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => {
buffer.fill(DSD_SILENCE_BYTE)
}
}
}
fn init_hw_params<'a>(
pcm_handle: &'a alsa::pcm::PCM,
config: &StreamConfig,
sample_format: SampleFormat,
) -> Result<alsa::pcm::HwParams<'a>, BackendSpecificError> {
let hw_params = alsa::pcm::HwParams::any(pcm_handle)?;
hw_params.set_access(alsa::pcm::Access::RWInterleaved)?;
let alsa_format = sample_format_to_alsa_format(&hw_params, sample_format)?;
hw_params.set_format(alsa_format)?;
hw_params.set_rate(config.sample_rate, alsa::ValueOr::Nearest)?;
hw_params.set_channels(config.channels as u32)?;
Ok(hw_params)
}
fn sample_format_to_alsa_format(
hw_params: &alsa::pcm::HwParams,
sample_format: SampleFormat,
) -> Result<alsa::pcm::Format, BackendSpecificError> {
use alsa::pcm::Format;
let (native, opposite) = match sample_format {
SampleFormat::I8 => return Ok(Format::S8), SampleFormat::U8 => return Ok(Format::U8), #[cfg(target_endian = "little")]
SampleFormat::I16 => (Format::S16LE, Format::S16BE),
#[cfg(target_endian = "big")]
SampleFormat::I16 => (Format::S16BE, Format::S16LE),
#[cfg(target_endian = "little")]
SampleFormat::U16 => (Format::U16LE, Format::U16BE),
#[cfg(target_endian = "big")]
SampleFormat::U16 => (Format::U16BE, Format::U16LE),
#[cfg(target_endian = "little")]
SampleFormat::I24 => (Format::S24LE, Format::S24BE),
#[cfg(target_endian = "big")]
SampleFormat::I24 => (Format::S24BE, Format::S24LE),
#[cfg(target_endian = "little")]
SampleFormat::U24 => (Format::U24LE, Format::U24BE),
#[cfg(target_endian = "big")]
SampleFormat::U24 => (Format::U24BE, Format::U24LE),
#[cfg(target_endian = "little")]
SampleFormat::I32 => (Format::S32LE, Format::S32BE),
#[cfg(target_endian = "big")]
SampleFormat::I32 => (Format::S32BE, Format::S32LE),
#[cfg(target_endian = "little")]
SampleFormat::U32 => (Format::U32LE, Format::U32BE),
#[cfg(target_endian = "big")]
SampleFormat::U32 => (Format::U32BE, Format::U32LE),
#[cfg(target_endian = "little")]
SampleFormat::F32 => (Format::FloatLE, Format::FloatBE),
#[cfg(target_endian = "big")]
SampleFormat::F32 => (Format::FloatBE, Format::FloatLE),
#[cfg(target_endian = "little")]
SampleFormat::F64 => (Format::Float64LE, Format::Float64BE),
#[cfg(target_endian = "big")]
SampleFormat::F64 => (Format::Float64BE, Format::Float64LE),
SampleFormat::DsdU8 => return Ok(Format::DSDU8),
#[cfg(target_endian = "little")]
SampleFormat::DsdU16 => (Format::DSDU16LE, Format::DSDU16BE),
#[cfg(target_endian = "big")]
SampleFormat::DsdU16 => (Format::DSDU16BE, Format::DSDU16LE),
#[cfg(target_endian = "little")]
SampleFormat::DsdU32 => (Format::DSDU32LE, Format::DSDU32BE),
#[cfg(target_endian = "big")]
SampleFormat::DsdU32 => (Format::DSDU32BE, Format::DSDU32LE),
_ => {
return Err(BackendSpecificError {
description: format!("Sample format '{sample_format}' is not supported"),
})
}
};
if hw_params.test_format(native).is_ok() {
return Ok(native);
}
if hw_params.test_format(opposite).is_ok() {
return Ok(opposite);
}
Err(BackendSpecificError {
description: format!(
"Sample format '{sample_format}' is not supported by hardware in any endianness"
),
})
}
fn set_hw_params_from_format(
pcm_handle: &alsa::pcm::PCM,
config: &StreamConfig,
sample_format: SampleFormat,
) -> Result<bool, BackendSpecificError> {
let hw_params = init_hw_params(pcm_handle, config, sample_format)?;
if let BufferSize::Fixed(buffer_frames) = config.buffer_size {
hw_params.set_buffer_size_near((2 * buffer_frames) as alsa::pcm::Frames)?;
hw_params
.set_period_size_near(buffer_frames as alsa::pcm::Frames, alsa::ValueOr::Nearest)?;
}
pcm_handle.hw_params(&hw_params)?;
if config.buffer_size == BufferSize::Default {
if let Ok(period) = hw_params.get_period_size() {
let hw_params = init_hw_params(pcm_handle, config, sample_format)?;
hw_params.set_period_size_near(period, alsa::ValueOr::Nearest)?;
hw_params.set_buffer_size_near(2 * period)?;
pcm_handle.hw_params(&hw_params)?;
}
}
Ok(hw_params.can_pause())
}
fn set_sw_params_from_format(
pcm_handle: &alsa::pcm::PCM,
config: &StreamConfig,
stream_type: alsa::Direction,
) -> Result<usize, BackendSpecificError> {
let sw_params = pcm_handle.sw_params_current()?;
let period_samples = {
let (buffer, period) = pcm_handle.get_params()?;
if buffer == 0 {
return Err(BackendSpecificError {
description: "initialization resulted in a null buffer".to_string(),
});
}
let start_threshold = match stream_type {
alsa::Direction::Playback => {
2 * period
}
alsa::Direction::Capture => 1,
};
sw_params.set_start_threshold(start_threshold as alsa::pcm::Frames)?;
sw_params.set_avail_min(period as alsa::pcm::Frames)?;
period as usize * config.channels as usize
};
sw_params.set_tstamp_mode(true)?;
sw_params.set_tstamp_type(alsa::pcm::TstampType::MonotonicRaw)?;
if pcm_handle.sw_params(&sw_params).is_err() {
sw_params.set_tstamp_type(alsa::pcm::TstampType::Monotonic)?;
pcm_handle.sw_params(&sw_params)?;
}
Ok(period_samples)
}
impl From<alsa::Error> for BackendSpecificError {
fn from(err: alsa::Error) -> Self {
Self {
description: err.to_string(),
}
}
}
impl From<alsa::Error> for BuildStreamError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
impl From<alsa::Error> for SupportedStreamConfigsError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
impl From<alsa::Error> for PlayStreamError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
impl From<alsa::Error> for PauseStreamError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
impl From<alsa::Error> for StreamError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}