From 99106c5ae335377d6510ad649fa7cdae54aa364a Mon Sep 17 00:00:00 2001 From: ashthespy Date: Sat, 1 Sep 2018 02:42:50 +0200 Subject: [PATCH] Rework `alsa` hw and mixer parameters --- playback/src/audio_backend/alsa.rs | 86 +++++++++++++++++++++------- playback/src/mixer/alsamixer.rs | 90 ++++++++++++++++-------------- 2 files changed, 114 insertions(+), 62 deletions(-) diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index f28ddea1..6db35310 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -1,20 +1,66 @@ use super::{Open, Sink}; -use alsa::{Direction, Error, ValueOr}; use alsa::device_name::HintIter; -use std::ffi::{CStr, CString}; use alsa::pcm::{Access, Format, HwParams, PCM}; +use alsa::{Direction, Error, ValueOr}; +use std::env; +use std::ffi::CString; use std::io; use std::process::exit; - pub struct AlsaSink(Option, String); fn list_outputs() { for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] { - println!("{} devices:", t); - let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); - for a in i { println!(" {:?}", a) } - } + println!("{} devices:", t); + let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); + for a in i { + println!(" {:?}", a) + } + } +} + +fn open_device(dev_name: &str) -> Result<(PCM), Box> { + let pcm = PCM::new(dev_name, Direction::Playback, false)?; + // 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. + // 500ms = buffer_size / (44100 * 4) + // buffer_size = 0.5 * 44100 = 22050 frames + { + // Set hardware parameters: 44100 Hz / Stereo / 16 bit + let hwp = HwParams::any(&pcm)?; + + hwp.set_access(Access::RWInterleaved)?; + hwp.set_format(Format::s16())?; + hwp.set_rate(44100, ValueOr::Nearest)?; + hwp.set_channels(2)?; + // hwp.set_period_size_near(256, ValueOr::Nearest)?; + hwp.set_buffer_size_near(11025 * 2)?; // ~ 0.25 x 2 s latency + + pcm.hw_params(&hwp)?; + } + + // Additional software paramters + check + if env::var("LIBRESPOT_DEBUG").is_ok() { + let hwp = pcm.hw_params_current()?; + let swp = pcm.sw_params_current()?; + let (bufsize, periodsize) = (hwp.get_buffer_size()?, hwp.get_period_size()?); + let periods = hwp.get_periods()?; + info!( + "periods: {:?} buffer_size: {:?} period_size {:?}", + periods, bufsize, periodsize + ); + // Not required now that buffer size is set properly + // swp.set_start_threshold(bufsize - periodsize)?; + // swp.set_avail_min(periodsize)?; + // pcm.sw_params(&swp).unwrap(); + info!( + "Opened audio output {:?} with parameters: {:?}, {:?}", + dev_name, hwp, swp + ); + } + + Ok(pcm) } impl Open for AlsaSink { @@ -23,6 +69,7 @@ impl Open for AlsaSink { let name = match device.as_ref().map(AsRef::as_ref) { Some("?") => { + println!("Listing available alsa outputs"); list_outputs(); exit(0) } @@ -37,20 +84,17 @@ impl Open for AlsaSink { impl Sink for AlsaSink { fn start(&mut self) -> io::Result<()> { if self.0.is_none() { - let pcm = PCM::new(&*self.1, Direction::Playback, false).unwrap(); - { - // Set hardware parameters: 44100 Hz / Stereo / 16 bit - let hwp = HwParams::any(&pcm).unwrap(); - hwp.set_channels(2).unwrap(); - hwp.set_rate(44100, ValueOr::Nearest).unwrap(); - hwp.set_format(Format::s16()).unwrap(); - hwp.set_access(Access::RWInterleaved).unwrap(); - pcm.hw_params(&hwp).unwrap(); - println!("PCM status: {:?}, {:?}", pcm.state(), pcm.hw_params_current().unwrap()) + let pcm = open_device(&self.1); + match pcm { + Ok(p) => self.0 = Some(p), + Err(e) => { + error!("Alsa error PCM open {}", e); + return Err(io::Error::new( + io::ErrorKind::Other, + "Alsa error: PCM open failed", + )); } - PCM::prepare(&pcm).unwrap(); - - self.0 = Some(pcm); + } } Ok(()) @@ -58,7 +102,7 @@ impl Sink for AlsaSink { fn stop(&mut self) -> io::Result<()> { { - let pcm = self.0.as_mut().unwrap(); + let pcm = self.0.as_ref().unwrap(); pcm.drain().unwrap(); } self.0 = None; diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index 196a1e6b..b4a0ac1a 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -1,5 +1,7 @@ -use super::Mixer; use super::AudioFilter; +use super::Mixer; +use std::env; +use std::error::Error; use alsa; @@ -7,27 +9,50 @@ use alsa; pub struct AlsaMixer { card: String, mixer: String, + index: u32, } -// Doesn't work - Selem is borrowed from Mixer -// impl AlsaMixer { -// fn get_selem(&self ) -> Result<(alsa::mixer::Selem), Box> { -// -// let selem_id = alsa::mixer::SelemId::new(self.mixer, 0); -// let mixer = alsa::mixer::Mixer::new(self.card, false)?; -// let selem = mixer.find_selem(&selem_id).unwrap(); -// -// Ok((selem)) -// } -// } +impl AlsaMixer { + + fn map_volume(&self, set_volume:Option) -> Result<(u16),Box> { + let mixer = alsa::mixer::Mixer::new(&self.card, false)?; + let sid = alsa::mixer::SelemId::new(&*self.mixer, self.index); + + let selem = mixer.find_selem(&sid).expect("Coundn't find SelemId"); + let (min, max) = selem.get_playback_volume_range(); + let cur_vol = selem.get_playback_volume(alsa::mixer::SelemChannelId::mono()).expect("Couldn't get current volume"); + let range = (max - min) as f64; + + let new_vol:u16; + + if let Some(vol) = set_volume { + let alsa_volume:i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min; + debug!("Maping volume {:?} [u16] ->> Alsa {:?} [i64]",vol,alsa_volume); + selem.set_playback_volume_all(alsa_volume).expect("Couldn't set alsa volume"); + new_vol = vol; // Meh + } else { + new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16; + debug!("Maping volume {:?} [u16] <<- Alsa {:?} [i64]",new_vol, cur_vol); + } + + + Ok(new_vol) + } +} impl Mixer for AlsaMixer { fn open(device: Option) -> AlsaMixer { - let card = device.unwrap_or(String::from("default")); - let mixer = String::from("PCM"); + let card = env::var("LIBRESPOT_CARD").unwrap_or(device.unwrap_or(String::from("default"))); + let mixer = env::var("LIBRESPOT_MIXER").unwrap_or(String::from("PCM")); + let index: u32 = 0; + info!( + "Setting up new mixer: card:{} mixer:{} index:{}", + card, mixer, index + ); AlsaMixer { card: card, mixer: mixer, + index: index, } } @@ -38,37 +63,20 @@ impl Mixer for AlsaMixer { } fn volume(&self) -> u16 { - let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap(); - let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0); - let selem = mixer.find_selem(&selem_id).unwrap(); - let (min, max) = selem.get_playback_volume_range(); - let volume: i64 = selem.get_playback_volume(alsa::mixer::SelemChannelId::FrontLeft).unwrap(); - // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might - // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate - // the multiplier to use, to get the corresponding Spotify volume value from the ALSA - // mixers volume. - let resolution = max - min + 1; - let multiplier: u16 = (((0xFFFF + 1) / resolution) - 1) as u16; - - volume as u16 * multiplier + match self.map_volume(None){ + Ok(vol) => vol, + Err(e) => { + error!("Error getting volume for <{}>, {:?}",self.card, e); + 0 } + } } fn set_volume(&self, volume: u16) { - let mixer = alsa::mixer::Mixer::new(&self.card, false).unwrap(); - let selem_id = alsa::mixer::SelemId::new(&self.mixer, 0); - let selem = mixer.find_selem(&selem_id).unwrap(); - let (min, max) = selem.get_playback_volume_range(); - - // Spotify uses a volume range from 0 to 65535, but the ALSA mixers resolution might - // differ, e.g. most ALSA mixers uses a resolution of 256. Therefore, we have to calculate - // the factor to use, to get the corresponding ALSA mixers volume value from the Spotify - // volume. - let resolution = max - min + 1; - let factor: u16 = (((0xFFFF + 1) / resolution) - 1) as u16; - let volume: i64 = (volume / factor) as i64; - info!("Setting volume: {:?}", volume); - selem.set_playback_volume_all(volume).unwrap(); + match self.map_volume(Some(volume)){ + Ok(_) => (), + Err(e) => error!("Error setting volume for <{}>, {:?}",self.card, e), + } } fn get_audio_filter(&self) -> Option> {