use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use std::sync::Arc;
use std::thread::JoinHandle;
use log::debug;
use crate::sink::AudioSink;
pub fn max_queued_samples(rate: u32, channels: u8) -> usize {
rate as usize * channels as usize * 2
}
const FLUSH_NONE: u64 = 0;
const FLUSH_ALL: u64 = u64::MAX;
enum Command {
Pcm(u64, Vec<i16>),
Wake,
Stop,
}
#[derive(Clone)]
pub struct PlayerSender {
tx: Sender<Command>,
pending: Arc<AtomicUsize>,
flush_request: Arc<AtomicU64>,
paused: Arc<AtomicBool>,
}
impl PlayerSender {
pub fn play(&self, seq: u64, pcm: Vec<i16>) {
self.pending.fetch_add(pcm.len(), Ordering::Relaxed);
let _ = self.tx.send(Command::Pcm(seq, pcm));
}
pub fn set_paused(&self, paused: bool) {
self.paused.store(paused, Ordering::Relaxed);
let _ = self.tx.send(Command::Wake);
}
pub fn flush(&self, below_seq: Option<u64>) {
let encoded = below_seq.map_or(FLUSH_ALL, |seq| seq.saturating_add(1));
self.flush_request.store(encoded, Ordering::Relaxed);
let _ = self.tx.send(Command::Wake);
}
pub fn pending_samples(&self) -> usize {
self.pending.load(Ordering::Relaxed)
}
#[cfg(test)]
pub fn is_paused(&self) -> bool {
self.paused.load(Ordering::Relaxed)
}
}
pub struct Player {
tx: Option<Sender<Command>>,
handle: Option<JoinHandle<()>>,
stop: Arc<AtomicBool>,
pending: Arc<AtomicUsize>,
flush_request: Arc<AtomicU64>,
paused: Arc<AtomicBool>,
}
impl Player {
pub fn spawn(sink: Box<dyn AudioSink>) -> Player {
let (tx, rx) = std::sync::mpsc::channel();
let stop = Arc::new(AtomicBool::new(false));
let pending = Arc::new(AtomicUsize::new(0));
let flush_request = Arc::new(AtomicU64::new(FLUSH_NONE));
let paused = Arc::new(AtomicBool::new(false));
let ctx = RunCtx {
stop: stop.clone(),
pending: pending.clone(),
flush_request: flush_request.clone(),
paused: paused.clone(),
};
let handle = std::thread::Builder::new()
.name("audio-player".into())
.spawn(move || run(sink, rx, ctx))
.expect("spawn player thread");
Player {
tx: Some(tx),
handle: Some(handle),
stop,
pending,
flush_request,
paused,
}
}
pub fn sender(&self) -> PlayerSender {
PlayerSender {
tx: self.tx.clone().expect("sender available before drop"),
pending: self.pending.clone(),
flush_request: self.flush_request.clone(),
paused: self.paused.clone(),
}
}
}
impl Drop for Player {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(tx) = &self.tx {
let _ = tx.send(Command::Stop);
}
self.tx = None;
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
struct RunCtx {
stop: Arc<AtomicBool>,
pending: Arc<AtomicUsize>,
flush_request: Arc<AtomicU64>,
paused: Arc<AtomicBool>,
}
fn run(mut sink: Box<dyn AudioSink>, rx: Receiver<Command>, ctx: RunCtx) {
let mut held: VecDeque<(u64, Vec<i16>)> = VecDeque::new();
let mut was_paused = false;
let mut packets: u64 = 0;
'outer: while let Ok(command) = rx.recv() {
if ctx.stop.load(Ordering::Relaxed) {
break;
}
let paused = ctx.paused.load(Ordering::Relaxed);
let flush = ctx.flush_request.swap(FLUSH_NONE, Ordering::Relaxed);
let just_paused = paused && !was_paused;
if flush != FLUSH_NONE || just_paused {
sink.flush();
}
if just_paused {
debug!("player: paused");
} else if !paused && was_paused {
debug!("player: resumed");
}
was_paused = paused;
match command {
Command::Stop => break,
Command::Wake => {}
Command::Pcm(seq, pcm) => held.push_back((seq, pcm)),
}
if flush != FLUSH_NONE {
loop {
match rx.try_recv() {
Ok(Command::Pcm(seq, pcm)) => held.push_back((seq, pcm)),
Ok(Command::Wake) => {}
Ok(Command::Stop) => break 'outer,
Err(TryRecvError::Empty) | Err(TryRecvError::Disconnected) => break,
}
}
let before = held.len();
held.retain(|(seq, pcm)| {
let keep = flush != FLUSH_ALL && seq.saturating_add(1) >= flush;
if !keep {
ctx.pending.fetch_sub(pcm.len(), Ordering::Relaxed);
}
keep
});
debug!(
"player: flushed {} packets, retained {}",
before - held.len(),
held.len()
);
}
if paused {
continue; }
while let Some((_, pcm)) = held.pop_front() {
ctx.pending.fetch_sub(pcm.len(), Ordering::Relaxed);
packets += 1;
if packets <= 3 || packets.is_multiple_of(250) {
debug!("player: {packets} packets");
}
sink.write(&pcm);
if ctx.stop.load(Ordering::Relaxed) {
break 'outer;
}
if ctx.paused.load(Ordering::Relaxed)
|| ctx.flush_request.load(Ordering::Relaxed) != FLUSH_NONE
{
break;
}
}
}
debug!("player: stopped, {packets} packets");
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
use std::sync::Mutex;
use std::time::Duration;
#[derive(Clone, Default)]
struct Recorder {
writes: Arc<Mutex<Vec<Vec<i16>>>>,
flushes: Arc<AtomicUsize>,
}
impl AudioSink for Recorder {
fn write(&mut self, pcm: &[i16]) {
self.writes.lock().unwrap().push(pcm.to_vec());
}
fn flush(&mut self) {
self.flushes.fetch_add(1, Ordering::Relaxed);
}
}
struct GatedRecorder {
recorder: Recorder,
entered: std::sync::mpsc::Sender<()>,
release: std::sync::mpsc::Receiver<()>,
}
impl AudioSink for GatedRecorder {
fn write(&mut self, pcm: &[i16]) {
let _ = self.entered.send(());
let _ = self.release.recv();
self.recorder.write(pcm);
}
fn flush(&mut self) {
self.recorder.flush();
}
}
fn settle(sender: &PlayerSender) {
for _ in 0..400 {
if sender.pending_samples() == 0 {
break;
}
std::thread::sleep(Duration::from_millis(5));
}
assert_eq!(sender.pending_samples(), 0);
}
fn assert_pending_holds_at(sender: &PlayerSender, expected: usize) {
for _ in 0..400 {
if sender.pending_samples() == expected {
break;
}
std::thread::sleep(Duration::from_millis(5));
}
assert_eq!(sender.pending_samples(), expected);
std::thread::sleep(Duration::from_millis(50));
assert_eq!(
sender.pending_samples(),
expected,
"held audio must stay counted"
);
}
#[test]
fn max_queued_for_44100_stereo() {
assert_eq!(max_queued_samples(44100, 2), 176_400); }
#[test]
fn delivers_packets_in_order() {
let recorder = Recorder::default();
let writes = recorder.writes.clone();
let player = Player::spawn(Box::new(recorder));
let sender = player.sender();
sender.play(1, vec![1i16; 4]);
sender.play(2, vec![2i16; 4]);
sender.play(3, vec![3i16; 4]);
settle(&sender);
drop(player);
assert_eq!(
*writes.lock().unwrap(),
vec![vec![1i16; 4], vec![2i16; 4], vec![3i16; 4]]
);
}
#[test]
fn pause_holds_delivery_then_resume_plays_held_audio() {
let recorder = Recorder::default();
let writes = recorder.writes.clone();
let flushes = recorder.flushes.clone();
let player = Player::spawn(Box::new(recorder));
let sender = player.sender();
sender.set_paused(true);
assert!(sender.is_paused());
sender.play(1, vec![1i16; 4]);
sender.play(2, vec![2i16; 4]);
assert_pending_holds_at(&sender, 8);
assert!(
writes.lock().unwrap().is_empty(),
"paused audio must not play"
);
assert!(
flushes.load(Ordering::Relaxed) >= 1,
"pause must flush the sink for immediate silence"
);
sender.set_paused(false);
sender.play(3, vec![3i16; 4]);
settle(&sender);
drop(player);
assert_eq!(
*writes.lock().unwrap(),
vec![vec![1i16; 4], vec![2i16; 4], vec![3i16; 4]]
);
}
#[test]
fn flush_discards_below_boundary_and_retains_the_rest() {
let recorder = Recorder::default();
let writes = recorder.writes.clone();
let flushes = recorder.flushes.clone();
let (entered_tx, entered_rx) = std::sync::mpsc::channel();
let (release_tx, release_rx) = std::sync::mpsc::channel();
let player = Player::spawn(Box::new(GatedRecorder {
recorder,
entered: entered_tx,
release: release_rx,
}));
let sender = player.sender();
sender.play(10, vec![1i16; 4]);
entered_rx
.recv_timeout(Duration::from_secs(5))
.expect("thread entered write");
sender.play(11, vec![2i16; 4]);
sender.play(12, vec![3i16; 4]);
sender.play(13, vec![4i16; 4]);
sender.flush(Some(13));
release_tx.send(()).unwrap();
release_tx.send(()).unwrap();
settle(&sender);
drop(player);
assert_eq!(
*writes.lock().unwrap(),
vec![vec![1i16; 4], vec![4i16; 4]],
"pre-flush write plays; below-boundary audio is discarded; \
at/after-boundary audio is retained"
);
assert_eq!(flushes.load(Ordering::Relaxed), 1);
}
#[test]
fn flush_without_boundary_discards_everything_queued() {
let recorder = Recorder::default();
let writes = recorder.writes.clone();
let (entered_tx, entered_rx) = std::sync::mpsc::channel();
let (release_tx, release_rx) = std::sync::mpsc::channel();
let player = Player::spawn(Box::new(GatedRecorder {
recorder,
entered: entered_tx,
release: release_rx,
}));
let sender = player.sender();
sender.play(1, vec![1i16; 4]);
entered_rx
.recv_timeout(Duration::from_secs(5))
.expect("thread entered write");
sender.play(2, vec![2i16; 4]);
sender.play(3, vec![3i16; 4]);
sender.flush(None);
release_tx.send(()).unwrap();
settle(&sender);
drop(player);
assert_eq!(*writes.lock().unwrap(), vec![vec![1i16; 4]]);
}
#[test]
fn flush_during_pause_discards_held_below_boundary_only() {
let recorder = Recorder::default();
let writes = recorder.writes.clone();
let player = Player::spawn(Box::new(recorder));
let sender = player.sender();
sender.set_paused(true);
sender.play(5, vec![5i16; 4]);
sender.play(6, vec![6i16; 4]);
sender.play(7, vec![7i16; 4]);
assert_pending_holds_at(&sender, 12);
sender.flush(Some(7));
assert_pending_holds_at(&sender, 4);
sender.set_paused(false);
settle(&sender);
drop(player);
assert_eq!(*writes.lock().unwrap(), vec![vec![7i16; 4]]);
}
#[test]
fn backpressure_counter_rises_and_falls() {
let recorder = Recorder::default();
let (entered_tx, entered_rx) = std::sync::mpsc::channel();
let (release_tx, release_rx) = std::sync::mpsc::channel();
let player = Player::spawn(Box::new(GatedRecorder {
recorder,
entered: entered_tx,
release: release_rx,
}));
let sender = player.sender();
sender.play(1, vec![0i16; 100]);
entered_rx
.recv_timeout(Duration::from_secs(5))
.expect("thread entered write");
sender.play(2, vec![0i16; 100]);
sender.play(3, vec![0i16; 100]);
assert_eq!(sender.pending_samples(), 200);
for _ in 0..3 {
let _ = release_tx.send(());
}
settle(&sender);
drop(player);
}
}