diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 8bdcb9d1..ae76f057 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -1,12 +1,20 @@ use super::{Open, Sink}; use alsa::device_name::HintIter; -use alsa::pcm::{Access, Format, HwParams, PCM}; +use alsa::pcm::{Access, Format, Frames, HwParams, PCM}; use alsa::{Direction, Error, ValueOr}; +use std::cmp::min; use std::ffi::CString; use std::io; use std::process::exit; -pub struct AlsaSink(Option, String); +const PREFERED_PERIOD_SIZE: Frames = 5512; // Period of roughly 125ms +const BUFFERED_PERIODS: Frames = 4; + +pub struct AlsaSink { + pcm: Option, + device: String, + buffer: Vec, +} fn list_outputs() { for t in &["pcm", "ctl", "hwdep"] { @@ -25,8 +33,9 @@ fn list_outputs() { } } -fn open_device(dev_name: &str) -> Result<(PCM), Box> { +fn open_device(dev_name: &str) -> Result<(PCM, Frames), Box> { let pcm = PCM::new(dev_name, Direction::Playback, false)?; + let mut period_size = PREFERED_PERIOD_SIZE; // http://www.linuxjournal.com/article/6735?page=0,1#N0x19ab2890.0x19ba78d8 // latency = period_size * periods / (rate * bytes_per_frame) // For 16 Bit stereo data, one frame has a length of four bytes. @@ -41,7 +50,8 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box> { hwp.set_format(Format::s16())?; hwp.set_rate(44100, ValueOr::Nearest)?; hwp.set_channels(2)?; - hwp.set_buffer_size_near(22050)?; // ~ 0.5s latency + period_size = hwp.set_period_size_near(period_size, ValueOr::Greater)?; + hwp.set_buffer_size_near(period_size * BUFFERED_PERIODS)?; pcm.hw_params(&hwp)?; let swp = pcm.sw_params_current()?; @@ -49,7 +59,7 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box> { pcm.sw_params(&swp)?; } - Ok(pcm) + Ok((pcm, period_size)) } impl Open for AlsaSink { @@ -67,16 +77,24 @@ impl Open for AlsaSink { } .to_string(); - AlsaSink(None, name) + AlsaSink { + pcm: None, + device: name, + buffer: vec![], + } } } impl Sink for AlsaSink { fn start(&mut self) -> io::Result<()> { - if self.0.is_none() { - let pcm = open_device(&self.1); + if self.pcm.is_none() { + let pcm = open_device(&self.device); match pcm { - Ok(p) => self.0 = Some(p), + Ok((p, period_size)) => { + self.pcm = Some(p); + // Create a buffer for all samples for a full period + self.buffer = Vec::with_capacity((period_size * 2) as usize); + } Err(e) => { error!("Alsa error PCM open {}", e); return Err(io::Error::new( @@ -92,20 +110,39 @@ impl Sink for AlsaSink { fn stop(&mut self) -> io::Result<()> { { - let pcm = self.0.as_ref().unwrap(); + let pcm = self.pcm.as_mut().unwrap(); + // Write any leftover data in the period buffer + // before draining the actual buffer + let io = pcm.io_i16().unwrap(); + match io.writei(&self.buffer[..]) { + Ok(_) => (), + Err(err) => pcm.try_recover(err, false).unwrap(), + } pcm.drain().unwrap(); } - self.0 = None; + self.pcm = None; Ok(()) } fn write(&mut self, data: &[i16]) -> io::Result<()> { - let pcm = self.0.as_mut().unwrap(); - let io = pcm.io_i16().unwrap(); - - match io.writei(&data) { - Ok(_) => (), - Err(err) => pcm.try_recover(err, false).unwrap(), + let mut processed_data = 0; + while processed_data < data.len() { + let data_to_buffer = min( + self.buffer.capacity() - self.buffer.len(), + data.len() - processed_data, + ); + self.buffer + .extend_from_slice(&data[processed_data..processed_data + data_to_buffer]); + processed_data += data_to_buffer; + if self.buffer.len() == self.buffer.capacity() { + let pcm = self.pcm.as_mut().unwrap(); + let io = pcm.io_i16().unwrap(); + match io.writei(&self.buffer) { + Ok(_) => (), + Err(err) => pcm.try_recover(err, false).unwrap(), + } + self.buffer.clear(); + } } Ok(())