use std::borrow::Cow;
use std::sync::mpsc::{SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use fixed_resample::{PushStatus, ResamplingChannelConfig, ResamplingCons, ResamplingProd, resampling_channel};
use super::driver::Shared;
use super::mixer::{self, BUS_CHANNELS, Gain};
use crate::resample::remix;
use crate::{Error, Format};
const LATENCY: f64 = 0.05;
const CAPACITY: f64 = 3.0;
#[derive(Clone, Debug)]
pub struct Input {
pub format: Format,
pub sample_rate: u32,
pub channels: u32,
}
impl Default for Input {
fn default() -> Self {
Self {
format: Format::F32,
sample_rate: 48_000,
channels: 2,
}
}
}
impl Input {
fn validate(&self) -> Result<(), Error> {
if self.sample_rate == 0 {
return Err(Error::Unsupported("sample rate must be > 0".into()));
}
if self.channels == 0 || self.channels > BUS_CHANNELS as u32 {
return Err(Error::Unsupported(format!(
"playback accepts mono or stereo input (got {} channels)",
self.channels
)));
}
Ok(())
}
}
pub struct Sink {
id: u64,
input: Input,
prod: Arc<Mutex<ResamplingProd<f32>>>,
control: Control,
overflowing: bool,
shared: Arc<Shared>,
engine: Arc<super::Handle>,
}
impl Sink {
pub fn write(&mut self, samples: &[u8]) -> Result<(), Error> {
let pcm = self.input.format.as_interleaved_f32(samples, self.input.channels)?;
let pcm = match self.input.channels as usize {
BUS_CHANNELS => pcm,
channels => Cow::Owned(remix(&pcm, channels as u32, BUS_CHANNELS as u32)?),
};
match self.prod.lock().unwrap().push_interleaved(&pcm) {
PushStatus::Ok | PushStatus::OutputNotReady => self.overflowing = false,
PushStatus::OverflowOccurred { num_frames_pushed } => {
if !self.overflowing {
tracing::warn!(num_frames_pushed, "audio playback overflow, dropping samples");
self.overflowing = true;
}
}
PushStatus::UnderflowCorrected { num_zero_frames_pushed } => {
self.overflowing = false;
tracing::debug!(num_zero_frames_pushed, "audio playback underflow, padded with silence");
}
}
Ok(())
}
pub fn buffered(&self) -> Duration {
Duration::from_secs_f64(self.prod.lock().unwrap().occupied_seconds().max(0.0))
}
pub fn input(&self) -> &Input {
&self.input
}
pub fn control(&self) -> Control {
self.control.clone()
}
pub fn set_volume(&self, volume: f32) {
self.control.set_volume(volume);
}
pub fn volume(&self) -> f32 {
self.control.volume()
}
pub fn peak(&self) -> f32 {
self.control.peak()
}
}
impl Drop for Sink {
fn drop(&mut self) {
self.shared.remove(self.id);
self.engine.wake();
}
}
#[derive(Clone, Debug)]
pub struct Control {
gain: Arc<Gain>,
}
impl Control {
pub fn set_volume(&self, volume: f32) {
self.gain.set_volume(volume);
}
pub fn volume(&self) -> f32 {
self.gain.volume()
}
pub fn peak(&self) -> f32 {
self.gain.peak()
}
}
pub(super) struct Registration {
pub(super) id: u64,
rate: u32,
prod: Arc<Mutex<ResamplingProd<f32>>>,
gain: Arc<Gain>,
pending: Option<ResamplingCons<f32>>,
}
impl Registration {
pub(super) fn attached(&self) -> bool {
self.pending.is_none()
}
pub(super) fn attach(&mut self, mixer: &SyncSender<mixer::Command>) {
let Some(cons) = self.pending.take() else { return };
let command = mixer::Command::Add {
id: self.id,
cons,
gain: self.gain.clone(),
};
if let Err(err) = mixer.try_send(command) {
let (TrySendError::Full(rejected) | TrySendError::Disconnected(rejected)) = err;
if let mixer::Command::Add { cons, .. } = rejected {
self.pending = Some(cons);
}
}
}
pub(super) fn rebuild(&mut self, rate: u32) {
let (prod, cons) = channel(self.rate, rate);
*self.prod.lock().unwrap() = prod;
self.pending = Some(cons);
}
}
pub(super) fn new(
id: u64,
rate: u32,
input: Input,
shared: Arc<Shared>,
engine: Arc<super::Handle>,
) -> Result<(Sink, Registration), Error> {
input.validate()?;
let (prod, cons) = channel(input.sample_rate, rate);
let prod = Arc::new(Mutex::new(prod));
let gain = Arc::new(Gain::new());
let sink = Sink {
id,
input,
prod: prod.clone(),
control: Control { gain: gain.clone() },
overflowing: false,
shared,
engine,
};
let registration = Registration {
id,
rate: sink.input.sample_rate,
prod,
gain,
pending: Some(cons),
};
Ok((sink, registration))
}
fn channel(from: u32, to: u32) -> (ResamplingProd<f32>, ResamplingCons<f32>) {
resampling_channel::<f32>(
BUS_CHANNELS,
from,
to,
true,
ResamplingChannelConfig {
latency_seconds: LATENCY,
capacity_seconds: CAPACITY,
underflow_autocorrect_percent_threshold: Some(25.0),
overflow_autocorrect_percent_threshold: Some(75.0),
..Default::default()
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_layouts_it_cannot_mix() {
for channels in [0, 6] {
let input = Input {
channels,
..Default::default()
};
assert!(
matches!(input.validate(), Err(Error::Unsupported(_))),
"{channels} channels"
);
}
let input = Input {
sample_rate: 0,
..Default::default()
};
assert!(matches!(input.validate(), Err(Error::Unsupported(_))));
}
#[test]
fn accepts_mono_and_stereo() {
for channels in [1, 2] {
let input = Input {
channels,
..Default::default()
};
input.validate().unwrap();
}
}
}