From a67048c3d711bf8dd5b9bd68034b8c9ce37d235b Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Tue, 20 Mar 2018 16:32:43 +0100 Subject: [PATCH 1/8] Add initial support for `alsamixer` --- playback/Cargo.toml | 2 +- playback/src/audio_backend/alsa.rs | 50 ++++++++++++++---------- playback/src/mixer/alsamixer.rs | 62 ++++++++++++++++++++++++++++++ playback/src/mixer/mod.rs | 15 ++++++-- playback/src/mixer/softmixer.rs | 2 +- src/main.rs | 7 ++-- 6 files changed, 108 insertions(+), 30 deletions(-) create mode 100644 playback/src/mixer/alsamixer.rs diff --git a/playback/Cargo.toml b/playback/Cargo.toml index 2efd6ca6..1d9390c3 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -15,7 +15,7 @@ futures = "0.1.8" log = "0.3.5" byteorder = "1.2.1" -alsa = { git = "https://github.com/plietar/rust-alsa", optional = true } +alsa = { version = "0.1.5", optional = true } portaudio-rs = { version = "0.3.0", optional = true } libpulse-sys = { version = "0.0.0", optional = true } jack = { version = "0.5.3", optional = true } diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 982a2625..7a067697 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -1,11 +1,14 @@ use super::{Open, Sink}; -use alsa::{Access, Format, Mode, Stream, PCM}; use std::io; +use std::ffi::CString; +use alsa::{Direction, ValueOr}; +use alsa::pcm::{PCM, HwParams, Format, Access}; + pub struct AlsaSink(Option<PCM>, String); impl Open for AlsaSink { - fn open(device: Option<String>) -> AlsaSink { + fn open(device: Option<String>) -> AlsaSink { info!("Using alsa sink"); let name = device.unwrap_or("default".to_string()); @@ -16,26 +19,24 @@ impl Open for AlsaSink { impl Sink for AlsaSink { fn start(&mut self) -> io::Result<()> { - if self.0.is_none() { - match PCM::open( - &*self.1, - Stream::Playback, - Mode::Blocking, - Format::Signed16, - Access::Interleaved, - 2, - 44100, - ) { - Ok(f) => self.0 = Some(f), - Err(e) => { - error!("Alsa error PCM open {}", e); - return Err(io::Error::new( - io::ErrorKind::Other, - "Alsa error: PCM open failed", - )); - } + if self.0.is_some() { + } else { + let pcm = PCM::open(&*CString::new(self.1.to_owned().into_bytes()).unwrap(), + 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(); } + + self.0 = Some(pcm); } + Ok(()) } @@ -45,7 +46,14 @@ impl Sink for AlsaSink { } fn write(&mut self, data: &[i16]) -> io::Result<()> { - self.0.as_mut().unwrap().write_interleaved(&data).unwrap(); + let pcm = self.0.as_mut().unwrap(); + let io = pcm.io_i16().unwrap(); + + match io.writei(&data) { + Ok(_) => (), + Err(err) => pcm.recover(err.code(), false).unwrap(), + } + Ok(()) } } diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs new file mode 100644 index 00000000..1f7e1092 --- /dev/null +++ b/playback/src/mixer/alsamixer.rs @@ -0,0 +1,62 @@ +use super::Mixer; +use super::AudioFilter; + +use alsa; + +#[derive(Clone)] +pub struct AlsaMixer { + name: String +} + +impl Mixer for AlsaMixer { + fn open(device: Option<String>) -> AlsaMixer { + let name = device.unwrap_or("default".to_string()); + AlsaMixer { + name: name + } + } + + fn start(&self) { + } + + fn stop(&self) { + } + + fn volume(&self) -> u16 { + let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap(); + let selem_id = alsa::mixer::SelemId::new("Master", 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 + } + + fn set_volume(&self, volume: u16) { + let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap(); + let selem_id = alsa::mixer::SelemId::new("Master", 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; + + selem.set_playback_volume_all(volume).unwrap(); + } + + fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> { + None + } +} diff --git a/playback/src/mixer/mod.rs b/playback/src/mixer/mod.rs index a6ba34aa..34e4dd3e 100644 --- a/playback/src/mixer/mod.rs +++ b/playback/src/mixer/mod.rs @@ -1,5 +1,5 @@ pub trait Mixer: Send { - fn open() -> Self + fn open(Option<String>) -> Self where Self: Sized; fn start(&self); @@ -15,16 +15,23 @@ pub trait AudioFilter { fn modify_stream(&self, data: &mut [i16]); } +#[cfg(feature = "alsa-backend")] +pub mod alsamixer; +#[cfg(feature = "alsa-backend")] +use self::alsamixer::AlsaMixer; + pub mod softmixer; use self::softmixer::SoftMixer; -fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> { - Box::new(M::open()) +fn mk_sink<M: Mixer + 'static>(device: Option<String>) -> Box<Mixer> { + Box::new(M::open(device)) } -pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> { +pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<String>) -> Box<Mixer>> { match name.as_ref().map(AsRef::as_ref) { None | Some("softvol") => Some(mk_sink::<SoftMixer>), + #[cfg(feature = "alsa-backend")] + Some("alsa") => Some(mk_sink::<AlsaMixer>), _ => None, } } diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs index b197f09c..36e4c6f8 100644 --- a/playback/src/mixer/softmixer.rs +++ b/playback/src/mixer/softmixer.rs @@ -10,7 +10,7 @@ pub struct SoftMixer { } impl Mixer for SoftMixer { - fn open() -> SoftMixer { + fn open(_: Option<String>) -> SoftMixer { SoftMixer { volume: Arc::new(AtomicUsize::new(0xFFFF)), } diff --git a/src/main.rs b/src/main.rs index 61290fb1..333095d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,7 +90,7 @@ struct Setup { backend: fn(Option<String>) -> Box<Sink>, device: Option<String>, - mixer: fn() -> Box<Mixer>, + mixer: fn(Option<String>) -> Box<Mixer>, cache: Option<Cache>, player_config: PlayerConfig, @@ -335,7 +335,7 @@ struct Main { connect_config: ConnectConfig, backend: fn(Option<String>) -> Box<Sink>, device: Option<String>, - mixer: fn() -> Box<Mixer>, + mixer: fn(Option<String>) -> Box<Mixer>, handle: Handle, discovery: Option<DiscoveryStream>, @@ -423,12 +423,13 @@ impl Future for Main { if let Async::Ready(session) = self.connect.poll().unwrap() { self.connect = Box::new(futures::future::empty()); let device = self.device.clone(); - let mixer = (self.mixer)(); + let mixer = (self.mixer)(device); let player_config = self.player_config.clone(); let connect_config = self.connect_config.clone(); let audio_filter = mixer.get_audio_filter(); let backend = self.backend; + let device = self.device.clone(); let (player, event_channel) = Player::new(player_config, session.clone(), audio_filter, move || { (backend)(device) From 08cfb1516ddb0ad45bdff6c5f5d1924b362a1f33 Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Wed, 21 Mar 2018 22:18:37 +0100 Subject: [PATCH 2/8] Switch to latest `alsa-rs` crate --- playback/Cargo.toml | 2 +- playback/src/audio_backend/alsa.rs | 43 ++++++++++++++++++++++-------- playback/src/mixer/alsamixer.rs | 31 +++++++++++++++------ 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/playback/Cargo.toml b/playback/Cargo.toml index 1d9390c3..ec5c03b3 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -15,7 +15,7 @@ futures = "0.1.8" log = "0.3.5" byteorder = "1.2.1" -alsa = { version = "0.1.5", optional = true } +alsa = { git = "https://github.com/diwic/alsa-rs.git", optional = true } portaudio-rs = { version = "0.3.0", optional = true } libpulse-sys = { version = "0.0.0", optional = true } jack = { version = "0.5.3", optional = true } diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 7a067697..f28ddea1 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -1,17 +1,34 @@ 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 std::io; +use std::process::exit; -use std::ffi::CString; -use alsa::{Direction, ValueOr}; -use alsa::pcm::{PCM, HwParams, Format, Access}; pub struct AlsaSink(Option<PCM>, 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) } + } +} + impl Open for AlsaSink { fn open(device: Option<String>) -> AlsaSink { info!("Using alsa sink"); - let name = device.unwrap_or("default".to_string()); + let name = match device.as_ref().map(AsRef::as_ref) { + Some("?") => { + list_outputs(); + exit(0) + } + Some(device) => device, + None => "default", + }.to_string(); AlsaSink(None, name) } @@ -19,11 +36,8 @@ impl Open for AlsaSink { impl Sink for AlsaSink { fn start(&mut self) -> io::Result<()> { - if self.0.is_some() { - } else { - let pcm = PCM::open(&*CString::new(self.1.to_owned().into_bytes()).unwrap(), - Direction::Playback, - false).unwrap(); + 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(); @@ -32,7 +46,9 @@ impl Sink for AlsaSink { 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()) + } + PCM::prepare(&pcm).unwrap(); self.0 = Some(pcm); } @@ -41,6 +57,10 @@ impl Sink for AlsaSink { } fn stop(&mut self) -> io::Result<()> { + { + let pcm = self.0.as_mut().unwrap(); + pcm.drain().unwrap(); + } self.0 = None; Ok(()) } @@ -51,7 +71,8 @@ impl Sink for AlsaSink { match io.writei(&data) { Ok(_) => (), - Err(err) => pcm.recover(err.code(), false).unwrap(), + Err(err) => pcm.try_recover(err, false).unwrap(), + // Err(err) => println!("{:?}",err), } Ok(()) diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index 1f7e1092..196a1e6b 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -5,14 +5,29 @@ use alsa; #[derive(Clone)] pub struct AlsaMixer { - name: String + card: String, + mixer: String, } +// Doesn't work - Selem is borrowed from Mixer +// impl AlsaMixer { +// fn get_selem(&self ) -> Result<(alsa::mixer::Selem), Box<Error>> { +// +// 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 Mixer for AlsaMixer { fn open(device: Option<String>) -> AlsaMixer { - let name = device.unwrap_or("default".to_string()); + let card = device.unwrap_or(String::from("default")); + let mixer = String::from("PCM"); AlsaMixer { - name: name + card: card, + mixer: mixer, } } @@ -23,8 +38,8 @@ impl Mixer for AlsaMixer { } fn volume(&self) -> u16 { - let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap(); - let selem_id = alsa::mixer::SelemId::new("Master", 0); + 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(); @@ -40,8 +55,8 @@ impl Mixer for AlsaMixer { } fn set_volume(&self, volume: u16) { - let mixer = alsa::mixer::Mixer::new(&self.name, false).unwrap(); - let selem_id = alsa::mixer::SelemId::new("Master", 0); + 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(); @@ -52,7 +67,7 @@ impl Mixer for AlsaMixer { 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(); } From 99106c5ae335377d6510ad649fa7cdae54aa364a Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Sat, 1 Sep 2018 02:42:50 +0200 Subject: [PATCH 3/8] 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<PCM>, 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<Error>> { + 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<Error>> { -// -// 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<u16>) -> Result<(u16),Box<Error>> { + 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<String>) -> 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<Box<AudioFilter + Send>> { From 0e1147077c590d26d04cb469c386f0a00f69c2f3 Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Tue, 11 Sep 2018 18:53:18 +0200 Subject: [PATCH 4/8] Add run time option flags for `AlsaMixer` Add `Cargo.lock` for Travis --- Cargo.lock | 29 ++++++++++++++++++++---- playback/src/mixer/alsamixer.rs | 29 +++++++++--------------- playback/src/mixer/mod.rs | 22 +++++++++++++++--- playback/src/mixer/softmixer.rs | 4 ++-- src/main.rs | 40 ++++++++++++++++++++++++++++----- 5 files changed, 90 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b07bc265..2eccd947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,10 +49,13 @@ dependencies = [ [[package]] name = "alsa" -version = "0.0.1" -source = "git+https://github.com/plietar/rust-alsa#8c63543fa0ccd971cf15f5675293d19febd6f79e" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -164,6 +167,11 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.0.4" @@ -824,7 +832,7 @@ dependencies = [ name = "librespot-playback" version = "0.1.0" dependencies = [ - "alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)", + "alsa 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "cpal 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1006,6 +1014,17 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nix" version = "0.11.0" @@ -2181,7 +2200,7 @@ dependencies = [ "checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" "checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" -"checksum alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)" = "<none>" +"checksum alsa 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fd5a75e70d45a943d2a0a818277e71d6ff777e97358529d6b460d3d4c4d0745" "checksum alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" @@ -2196,6 +2215,7 @@ dependencies = [ "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" "checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" @@ -2279,6 +2299,7 @@ dependencies = [ "checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" +"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index b4a0ac1a..c34d839c 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -1,22 +1,19 @@ use super::AudioFilter; -use super::Mixer; -use std::env; +use super::{Mixer, MixerConfig}; use std::error::Error; use alsa; #[derive(Clone)] pub struct AlsaMixer { - card: String, - mixer: String, - index: u32, + config: MixerConfig, } impl AlsaMixer { fn map_volume(&self, set_volume:Option<u16>) -> Result<(u16),Box<Error>> { - let mixer = alsa::mixer::Mixer::new(&self.card, false)?; - let sid = alsa::mixer::SelemId::new(&*self.mixer, self.index); + let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; + let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); let selem = mixer.find_selem(&sid).expect("Coundn't find SelemId"); let (min, max) = selem.get_playback_volume_range(); @@ -41,19 +38,13 @@ impl AlsaMixer { } impl Mixer for AlsaMixer { - fn open(device: Option<String>) -> AlsaMixer { - 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; + fn open(config: Option<MixerConfig>) -> AlsaMixer { + let config = config.unwrap_or_default(); info!( "Setting up new mixer: card:{} mixer:{} index:{}", - card, mixer, index + config.card, config.mixer, config.index ); - AlsaMixer { - card: card, - mixer: mixer, - index: index, - } + AlsaMixer { config: config } } fn start(&self) { @@ -67,7 +58,7 @@ impl Mixer for AlsaMixer { match self.map_volume(None){ Ok(vol) => vol, Err(e) => { - error!("Error getting volume for <{}>, {:?}",self.card, e); + error!("Error getting volume for <{}>, {:?}",self.config.card, e); 0 } } } @@ -75,7 +66,7 @@ impl Mixer for AlsaMixer { fn set_volume(&self, volume: u16) { match self.map_volume(Some(volume)){ Ok(_) => (), - Err(e) => error!("Error setting volume for <{}>, {:?}",self.card, e), + Err(e) => error!("Error setting volume for <{}>, {:?}",self.config.card, e), } } diff --git a/playback/src/mixer/mod.rs b/playback/src/mixer/mod.rs index 34e4dd3e..f19a8661 100644 --- a/playback/src/mixer/mod.rs +++ b/playback/src/mixer/mod.rs @@ -1,5 +1,5 @@ pub trait Mixer: Send { - fn open(Option<String>) -> Self + fn open(Option<MixerConfig>) -> Self where Self: Sized; fn start(&self); @@ -20,14 +20,30 @@ pub mod alsamixer; #[cfg(feature = "alsa-backend")] use self::alsamixer::AlsaMixer; +#[derive(Debug, Clone)] +pub struct MixerConfig { + pub card: String, + pub mixer: String, + pub index: u32, +} + +impl Default for MixerConfig { + fn default() -> MixerConfig { MixerConfig { + card: String::from("default"), + mixer: String::from("PCM"), + index: 0, + } + } +} + pub mod softmixer; use self::softmixer::SoftMixer; -fn mk_sink<M: Mixer + 'static>(device: Option<String>) -> Box<Mixer> { +fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> { Box::new(M::open(device)) } -pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<String>) -> Box<Mixer>> { +pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> { match name.as_ref().map(AsRef::as_ref) { None | Some("softvol") => Some(mk_sink::<SoftMixer>), #[cfg(feature = "alsa-backend")] diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs index 36e4c6f8..4b969785 100644 --- a/playback/src/mixer/softmixer.rs +++ b/playback/src/mixer/softmixer.rs @@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use super::AudioFilter; -use super::Mixer; +use super::{Mixer, MixerConfig}; #[derive(Clone)] pub struct SoftMixer { @@ -10,7 +10,7 @@ pub struct SoftMixer { } impl Mixer for SoftMixer { - fn open(_: Option<String>) -> SoftMixer { + fn open(_: Option<MixerConfig>) -> SoftMixer { SoftMixer { volume: Arc::new(AtomicUsize::new(0xFFFF)), } diff --git a/src/main.rs b/src/main.rs index 333095d0..cfc752ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ use librespot::connect::discovery::{discovery, DiscoveryStream}; use librespot::connect::spirc::{Spirc, SpircTask}; use librespot::playback::audio_backend::{self, Sink, BACKENDS}; use librespot::playback::config::{Bitrate, PlayerConfig}; -use librespot::playback::mixer::{self, Mixer}; +use librespot::playback::mixer::{self, Mixer, MixerConfig}; use librespot::playback::player::{Player, PlayerEvent}; mod player_event_handler; @@ -90,12 +90,13 @@ struct Setup { backend: fn(Option<String>) -> Box<Sink>, device: Option<String>, - mixer: fn(Option<String>) -> Box<Mixer>, + mixer: fn(Option<MixerConfig>) -> Box<Mixer>, cache: Option<Cache>, player_config: PlayerConfig, session_config: SessionConfig, connect_config: ConnectConfig, + mixer_config: MixerConfig, credentials: Option<Credentials>, enable_discovery: bool, zeroconf_port: u16, @@ -142,7 +143,25 @@ fn setup(args: &[String]) -> Setup { "Audio device to use. Use '?' to list options if using portaudio", "DEVICE", ) - .optopt("", "mixer", "Mixer to use", "MIXER") + .optopt("", "mixer", "Mixer to use (Alsa or softmixer)", "MIXER") + .optopt( + "m", + "mixer-name", + "Alsa mixer name, e.g \"PCM\" or \"Master\". Defaults to 'PCM'", + "MIXER_NAME", + ) + .optopt( + "", + "mixer-card", + "Alsa mixer card, e.g \"hw:0\" or similar from `aplay -l`. Defaults to 'default' ", + "MIXER_CARD", + ) + .optopt( + "", + "mixer-index", + "Alsa mixer index, Index of the cards mixer. Defaults to 0", + "MIXER_INDEX", + ) .optopt( "", "initial-volume", @@ -208,6 +227,12 @@ fn setup(args: &[String]) -> Setup { let mixer_name = matches.opt_str("mixer"); let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); + let mixer_config = MixerConfig { + card: matches.opt_str("mixer-card").unwrap_or(String::from("default")), + mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), + index: matches.opt_str("mixer-index").map(|index| index.parse::<u32>().unwrap()).unwrap_or(0), + }; + let use_audio_cache = !matches.opt_present("disable-audio-cache"); let cache = matches @@ -324,6 +349,7 @@ fn setup(args: &[String]) -> Setup { enable_discovery: enable_discovery, zeroconf_port: zeroconf_port, mixer: mixer, + mixer_config: mixer_config, player_event_program: matches.opt_str("onevent"), } } @@ -335,7 +361,8 @@ struct Main { connect_config: ConnectConfig, backend: fn(Option<String>) -> Box<Sink>, device: Option<String>, - mixer: fn(Option<String>) -> Box<Mixer>, + mixer: fn(Option<MixerConfig>) -> Box<Mixer>, + mixer_config: MixerConfig, handle: Handle, discovery: Option<DiscoveryStream>, @@ -362,6 +389,7 @@ impl Main { backend: setup.backend, device: setup.device, mixer: setup.mixer, + mixer_config: setup.mixer_config, connect: Box::new(futures::future::empty()), discovery: None, @@ -422,8 +450,8 @@ impl Future for Main { if let Async::Ready(session) = self.connect.poll().unwrap() { self.connect = Box::new(futures::future::empty()); - let device = self.device.clone(); - let mixer = (self.mixer)(device); + let mixer_config = self.mixer_config.clone(); + let mixer = (self.mixer)(Some(mixer_config)); let player_config = self.player_config.clone(); let connect_config = self.connect_config.clone(); From a80bf86a2b71ab0721fbb68ae9cedbd2b30c3e7e Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Tue, 16 Oct 2018 16:46:26 +0200 Subject: [PATCH 5/8] Clean up alsa stragglers and typos --- playback/src/audio_backend/alsa.rs | 35 +++++-------------- playback/src/mixer/alsamixer.rs | 54 +++++++++++++++--------------- 2 files changed, 35 insertions(+), 54 deletions(-) diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 6db35310..927c0921 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -10,11 +10,13 @@ use std::process::exit; pub struct AlsaSink(Option<PCM>, String); fn list_outputs() { - for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] { + for t in &["pcm", "ctl", "hwdep"] { println!("{} devices:", t); let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); for a in i { - println!(" {:?}", a) + if let Some(Direction::Playback) = a.direction { + println!("{:#?}", a) + } } } } @@ -25,7 +27,8 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> { // 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 + // buffer_size_bytes = 0.5 * 44100 / 4 + // buffer_size_frames = 0.5 * 44100 = 22050 { // Set hardware parameters: 44100 Hz / Stereo / 16 bit let hwp = HwParams::any(&pcm)?; @@ -34,37 +37,16 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> { 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 + hwp.set_buffer_size_near(22050)?; // ~ 0.5s 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 { - fn open(device: Option<String>) -> AlsaSink { + fn open(device: Option<String>) -> AlsaSink { info!("Using alsa sink"); let name = match device.as_ref().map(AsRef::as_ref) { @@ -116,7 +98,6 @@ impl Sink for AlsaSink { match io.writei(&data) { Ok(_) => (), Err(err) => pcm.try_recover(err, false).unwrap(), - // Err(err) => println!("{:?}",err), } Ok(()) diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index c34d839c..36256268 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -10,29 +10,31 @@ pub struct AlsaMixer { } impl AlsaMixer { + fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> { + let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; + let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); - fn map_volume(&self, set_volume:Option<u16>) -> Result<(u16),Box<Error>> { - let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; - let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); - - let selem = mixer.find_selem(&sid).expect("Coundn't find SelemId"); + let selem = mixer.find_selem(&sid).expect("Couldn'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 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; + 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 + let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min; + debug!("Mapping volume {:?} [u16] ->> alsa {:?} [i64]", vol, alsa_volume); + selem + .set_playback_volume_all(alsa_volume) + .expect("Couldn't set alsa volume"); + new_vol = vol; } else { - new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16; - debug!("Maping volume {:?} [u16] <<- Alsa {:?} [i64]",new_vol, cur_vol); + new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16; + debug!("Mapping volume {:?} [u16] <<- alsa {:?} [i64]", new_vol, cur_vol); } - Ok(new_vol) } } @@ -47,26 +49,24 @@ impl Mixer for AlsaMixer { AlsaMixer { config: config } } - fn start(&self) { - } + fn start(&self) {} - fn stop(&self) { - } + fn stop(&self) {} fn volume(&self) -> u16 { - - match self.map_volume(None){ - Ok(vol) => vol, - Err(e) => { - error!("Error getting volume for <{}>, {:?}",self.config.card, e); - 0 } + match self.map_volume(None) { + Ok(vol) => vol, + Err(e) => { + error!("Error getting volume for <{}>, {:?}", self.config.card, e); + 0 + } } } fn set_volume(&self, volume: u16) { - match self.map_volume(Some(volume)){ - Ok(_) => (), - Err(e) => error!("Error setting volume for <{}>, {:?}",self.config.card, e), + match self.map_volume(Some(volume)) { + Ok(_) => (), + Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e), } } From cc6c9b2dc4fae61ad79a96384357fd9d4e3b3dc1 Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Thu, 1 Nov 2018 17:40:42 +0100 Subject: [PATCH 6/8] More `alsa` stragglers --- playback/src/audio_backend/alsa.rs | 3 +-- playback/src/mixer/alsamixer.rs | 14 ++++++++------ src/main.rs | 16 +++++++++------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 927c0921..0e18708b 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -2,7 +2,6 @@ use super::{Open, Sink}; use alsa::device_name::HintIter; 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; @@ -15,7 +14,7 @@ fn list_outputs() { let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); for a in i { if let Some(Direction::Playback) = a.direction { - println!("{:#?}", a) + println!("{}\n\t{}", a.name.unwrap(), a.desc.unwrap()); } } } diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index 36256268..5c77d470 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -14,25 +14,27 @@ impl AlsaMixer { let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); - let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId"); + let selem = mixer + .find_selem(&sid) + .expect(format!("Couldn't find simple mixer control for {}", self.config.mixer).as_str()); 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!("Mapping volume {:?} [u16] ->> alsa {:?} [i64]", vol, alsa_volume); + debug!("Mapping volume {:?} ->> alsa {:?}", vol, alsa_volume); selem .set_playback_volume_all(alsa_volume) .expect("Couldn't set alsa volume"); new_vol = vol; } else { + let cur_vol = selem + .get_playback_volume(alsa::mixer::SelemChannelId::mono()) + .expect("Couldn't get current volume"); new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16; - debug!("Mapping volume {:?} [u16] <<- alsa {:?} [i64]", new_vol, cur_vol); + debug!("Mapping volume {:?} <<- alsa {:?}", new_vol, cur_vol); } Ok(new_vol) diff --git a/src/main.rs b/src/main.rs index cfc752ff..4fec379a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,10 +140,10 @@ fn setup(args: &[String]) -> Setup { .optopt( "", "device", - "Audio device to use. Use '?' to list options if using portaudio", + "Audio device to use. Use '?' to list options if using portaudio or alsa", "DEVICE", ) - .optopt("", "mixer", "Mixer to use (Alsa or softmixer)", "MIXER") + .optopt("", "mixer", "Mixer to use (alsa or softmixer)", "MIXER") .optopt( "m", "mixer-name", @@ -228,9 +228,12 @@ fn setup(args: &[String]) -> Setup { let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); let mixer_config = MixerConfig { - card: matches.opt_str("mixer-card").unwrap_or(String::from("default")), - mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), - index: matches.opt_str("mixer-index").map(|index| index.parse::<u32>().unwrap()).unwrap_or(0), + card: matches.opt_str("mixer-card").unwrap_or(String::from("default")), + mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), + index: matches + .opt_str("mixer-index") + .map(|index| index.parse::<u32>().unwrap()) + .unwrap_or(0), }; let use_audio_cache = !matches.opt_present("disable-audio-cache"); @@ -247,8 +250,7 @@ fn setup(args: &[String]) -> Setup { panic!("Initial volume must be in the range 0-100"); } (volume as i32 * 0xFFFF / 100) as u16 - }) - .or_else(|| cache.as_ref().and_then(Cache::volume)) + }).or_else(|| cache.as_ref().and_then(Cache::volume)) .unwrap_or(0x8000); let zeroconf_port = matches From 8fd0caf583e7def555cfbb595d88d709980ae0a8 Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Fri, 2 Nov 2018 15:24:43 +0100 Subject: [PATCH 7/8] Explicitly set `start_threshold` and pretty print devices --- playback/src/audio_backend/alsa.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 0e18708b..98e7c8f7 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -14,7 +14,12 @@ fn list_outputs() { let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); for a in i { if let Some(Direction::Playback) = a.direction { - println!("{}\n\t{}", a.name.unwrap(), a.desc.unwrap()); + // mimic aplay -L + println!( + "{}\n\t{}\n", + a.name.unwrap(), + a.desc.unwrap().replace("\n", "\n\t") + ); } } } @@ -37,8 +42,11 @@ fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> { hwp.set_rate(44100, ValueOr::Nearest)?; hwp.set_channels(2)?; hwp.set_buffer_size_near(22050)?; // ~ 0.5s latency - pcm.hw_params(&hwp)?; + + let swp = pcm.sw_params_current()?; + swp.set_start_threshold(hwp.get_buffer_size()? - hwp.get_period_size()?)?; + pcm.sw_params(&swp)?; } Ok(pcm) From 9cb2f49d529052f9f0d1f1a4999e7d1396afed51 Mon Sep 17 00:00:00 2001 From: ashthespy <ashthespy@gmail.com> Date: Thu, 21 Mar 2019 23:32:18 +0100 Subject: [PATCH 8/8] Switch `alsa` to crates.io --- playback/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback/Cargo.toml b/playback/Cargo.toml index ec5c03b3..02b750d9 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -15,7 +15,7 @@ futures = "0.1.8" log = "0.3.5" byteorder = "1.2.1" -alsa = { git = "https://github.com/diwic/alsa-rs.git", optional = true } +alsa = { version = "0.2.1", optional = true } portaudio-rs = { version = "0.3.0", optional = true } libpulse-sys = { version = "0.0.0", optional = true } jack = { version = "0.5.3", optional = true }