Implement mapped volume for alsa mixer

This commit is contained in:
ashthespy 2018-09-17 17:28:54 +02:00 committed by Sasha Hilton
parent 4886d4eed2
commit 3dfad7f788
3 changed files with 102 additions and 19 deletions

View file

@ -1,44 +1,119 @@
use super::AudioFilter; use super::AudioFilter;
use super::{Mixer, MixerConfig}; use super::{Mixer, MixerConfig};
use std;
use std::error::Error; use std::error::Error;
use alsa; use alsa;
#[derive(Clone)]
struct AlsaMixerVolumeParams {
min: i64,
max: i64,
range: f64,
min_db: alsa::mixer::MilliBel,
max_db: alsa::mixer::MilliBel,
}
#[derive(Clone)] #[derive(Clone)]
pub struct AlsaMixer { pub struct AlsaMixer {
config: MixerConfig, config: MixerConfig,
params: AlsaMixerVolumeParams,
} }
impl AlsaMixer { impl AlsaMixer {
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<dyn Error>> { fn pvol<T>(&self, vol: T, min: T, max: T) -> f64
where
T: std::ops::Sub + Copy,
f64: std::convert::From<<T as std::ops::Sub>::Output>,
{
f64::from(vol - min) / f64::from(max - min)
}
fn init_mixer(mut config: MixerConfig) -> Result<AlsaMixer, Box<Error>> {
let mixer = alsa::mixer::Mixer::new(&config.card, false)?;
let sid = alsa::mixer::SelemId::new(&config.mixer, config.index);
let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
let (min, max) = selem.get_playback_volume_range();
let (min_db, max_db) = selem.get_playback_db_range();
info!(
"Alsa min: {} ({:?}[dB]) -- max: {} ({:?}[dB])",
min, min_db, max, max_db
);
if config.mapped_volume && (max_db - min_db <= alsa::mixer::MilliBel(24)) {
warn!(
"Switching to linear volume mapping, control range: {:?}",
max_db - min_db
);
config.mapped_volume = false;
} else {
info!("Using Alsa mapped volume: dB range: {:?}", max_db - min_db);
}
Ok(AlsaMixer {
config: config,
params: AlsaMixerVolumeParams {
min: min,
max: max,
range: (max - min) as f64,
min_db: min_db,
max_db: max_db,
},
})
}
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
let selem = mixer.find_selem(&sid).expect( let selem = mixer.find_selem(&sid).expect("Couldn't find SelemId");
format!( let cur_vol = selem
"Couldn't find simple mixer control for {}", .get_playback_volume(alsa::mixer::SelemChannelId::mono())
self.config.mixer .expect("Couldn't get current volume");
) let cur_vol_db = selem
.as_str(), .get_playback_vol_db(alsa::mixer::SelemChannelId::mono())
); .expect("Couldn't get current volume");
let (min, max) = selem.get_playback_volume_range();
let range = (max - min) as f64;
let new_vol: u16; let new_vol: u16;
debug!("Current alsa volume: {}[i64] {:?}", cur_vol, cur_vol_db);
if let Some(vol) = set_volume { if let Some(vol) = set_volume {
let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min; let alsa_volume = if self.config.mapped_volume {
debug!("Mapping volume {:?} ->> alsa {:?}", vol, alsa_volume); ((self.pvol(vol, 0x0000, 0xFFFF)).log10() * 6000.0).floor() as i64 + self.params.max
} else {
((vol as f64 / 0xFFFF as f64) * self.params.range) as i64 + self.params.min
};
debug!(
"Maping volume [{:.3}%] {:?} [u16] ->> Alsa [{:.3}%] {:?} [i64]",
self.pvol(vol, 0x0000, 0xFFFF) * 100.0,
vol,
self.pvol(
alsa_volume as f64,
self.params.min as f64,
self.params.max as f64
) * 100.0,
alsa_volume
);
selem selem
.set_playback_volume_all(alsa_volume) .set_playback_volume_all(alsa_volume)
.expect("Couldn't set alsa volume"); .expect("Couldn't set alsa volume");
new_vol = vol; new_vol = vol; // Meh
} else { } else {
let cur_vol = selem new_vol =
.get_playback_volume(alsa::mixer::SelemChannelId::mono()) (((cur_vol - self.params.min) as f64 / self.params.range) * 0xFFFF as f64) as u16;
.expect("Couldn't get current volume"); debug!(
new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16; "Maping volume [{:.3}%] {:?} [u16] <<- Alsa [{:.3}%] {:?} [i64]",
debug!("Mapping volume {:?} <<- alsa {:?}", new_vol, cur_vol); self.pvol(new_vol, 0x0000, 0xFFFF),
new_vol,
self.pvol(
cur_vol as f64,
self.params.min as f64,
self.params.max as f64
),
cur_vol
);
} }
Ok(new_vol) Ok(new_vol)
@ -52,7 +127,7 @@ impl Mixer for AlsaMixer {
"Setting up new mixer: card:{} mixer:{} index:{}", "Setting up new mixer: card:{} mixer:{} index:{}",
config.card, config.mixer, config.index config.card, config.mixer, config.index
); );
AlsaMixer { config: config } AlsaMixer::init_mixer(config).expect("Error setting up mixer!")
} }
fn start(&self) {} fn start(&self) {}

View file

@ -25,6 +25,7 @@ pub struct MixerConfig {
pub card: String, pub card: String,
pub mixer: String, pub mixer: String,
pub index: u32, pub index: u32,
pub mapped_volume: bool,
} }
impl Default for MixerConfig { impl Default for MixerConfig {
@ -33,6 +34,7 @@ impl Default for MixerConfig {
card: String::from("default"), card: String::from("default"),
mixer: String::from("PCM"), mixer: String::from("PCM"),
index: 0, index: 0,
mapped_volume: true,
} }
} }
} }

View file

@ -150,6 +150,11 @@ fn setup(args: &[String]) -> Setup {
"Alsa mixer index, Index of the cards mixer. Defaults to 0", "Alsa mixer index, Index of the cards mixer. Defaults to 0",
"MIXER_INDEX", "MIXER_INDEX",
) )
.optflag(
"",
"mixer-linear-volume",
"Disable alsa's mapped volume scale (cubic). Default false",
)
.optopt( .optopt(
"", "",
"initial-volume", "initial-volume",
@ -241,6 +246,7 @@ fn setup(args: &[String]) -> Setup {
.opt_str("mixer-index") .opt_str("mixer-index")
.map(|index| index.parse::<u32>().unwrap()) .map(|index| index.parse::<u32>().unwrap())
.unwrap_or(0), .unwrap_or(0),
mapped_volume: !matches.opt_present("mixer-linear-volume"),
}; };
let use_audio_cache = !matches.opt_present("disable-audio-cache"); let use_audio_cache = !matches.opt_present("disable-audio-cache");