mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Implement mapped volume for alsa mixer
This commit is contained in:
parent
4886d4eed2
commit
3dfad7f788
3 changed files with 102 additions and 19 deletions
|
@ -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) {}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue