mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Add initial support for alsamixer
This commit is contained in:
parent
a41ab28540
commit
a67048c3d7
6 changed files with 108 additions and 30 deletions
|
@ -15,7 +15,7 @@ futures = "0.1.8"
|
||||||
log = "0.3.5"
|
log = "0.3.5"
|
||||||
byteorder = "1.2.1"
|
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 }
|
portaudio-rs = { version = "0.3.0", optional = true }
|
||||||
libpulse-sys = { version = "0.0.0", optional = true }
|
libpulse-sys = { version = "0.0.0", optional = true }
|
||||||
jack = { version = "0.5.3", optional = true }
|
jack = { version = "0.5.3", optional = true }
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
use alsa::{Access, Format, Mode, Stream, PCM};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
use std::ffi::CString;
|
||||||
|
use alsa::{Direction, ValueOr};
|
||||||
|
use alsa::pcm::{PCM, HwParams, Format, Access};
|
||||||
|
|
||||||
pub struct AlsaSink(Option<PCM>, String);
|
pub struct AlsaSink(Option<PCM>, String);
|
||||||
|
|
||||||
impl Open for AlsaSink {
|
impl Open for AlsaSink {
|
||||||
fn open(device: Option<String>) -> AlsaSink {
|
fn open(device: Option<String>) -> AlsaSink {
|
||||||
info!("Using alsa sink");
|
info!("Using alsa sink");
|
||||||
|
|
||||||
let name = device.unwrap_or("default".to_string());
|
let name = device.unwrap_or("default".to_string());
|
||||||
|
@ -16,26 +19,24 @@ impl Open for AlsaSink {
|
||||||
|
|
||||||
impl Sink for AlsaSink {
|
impl Sink for AlsaSink {
|
||||||
fn start(&mut self) -> io::Result<()> {
|
fn start(&mut self) -> io::Result<()> {
|
||||||
if self.0.is_none() {
|
if self.0.is_some() {
|
||||||
match PCM::open(
|
} else {
|
||||||
&*self.1,
|
let pcm = PCM::open(&*CString::new(self.1.to_owned().into_bytes()).unwrap(),
|
||||||
Stream::Playback,
|
Direction::Playback,
|
||||||
Mode::Blocking,
|
false).unwrap();
|
||||||
Format::Signed16,
|
{
|
||||||
Access::Interleaved,
|
// Set hardware parameters: 44100 Hz / Stereo / 16 bit
|
||||||
2,
|
let hwp = HwParams::any(&pcm).unwrap();
|
||||||
44100,
|
hwp.set_channels(2).unwrap();
|
||||||
) {
|
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
|
||||||
Ok(f) => self.0 = Some(f),
|
hwp.set_format(Format::s16()).unwrap();
|
||||||
Err(e) => {
|
hwp.set_access(Access::RWInterleaved).unwrap();
|
||||||
error!("Alsa error PCM open {}", e);
|
pcm.hw_params(&hwp).unwrap();
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Alsa error: PCM open failed",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.0 = Some(pcm);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +46,14 @@ impl Sink for AlsaSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
playback/src/mixer/alsamixer.rs
Normal file
62
playback/src/mixer/alsamixer.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
pub trait Mixer: Send {
|
pub trait Mixer: Send {
|
||||||
fn open() -> Self
|
fn open(Option<String>) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn start(&self);
|
fn start(&self);
|
||||||
|
@ -15,16 +15,23 @@ pub trait AudioFilter {
|
||||||
fn modify_stream(&self, data: &mut [i16]);
|
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;
|
pub mod softmixer;
|
||||||
use self::softmixer::SoftMixer;
|
use self::softmixer::SoftMixer;
|
||||||
|
|
||||||
fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
|
fn mk_sink<M: Mixer + 'static>(device: Option<String>) -> Box<Mixer> {
|
||||||
Box::new(M::open())
|
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) {
|
match name.as_ref().map(AsRef::as_ref) {
|
||||||
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
||||||
|
#[cfg(feature = "alsa-backend")]
|
||||||
|
Some("alsa") => Some(mk_sink::<AlsaMixer>),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub struct SoftMixer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixer for SoftMixer {
|
impl Mixer for SoftMixer {
|
||||||
fn open() -> SoftMixer {
|
fn open(_: Option<String>) -> SoftMixer {
|
||||||
SoftMixer {
|
SoftMixer {
|
||||||
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ struct Setup {
|
||||||
backend: fn(Option<String>) -> Box<Sink>,
|
backend: fn(Option<String>) -> Box<Sink>,
|
||||||
device: Option<String>,
|
device: Option<String>,
|
||||||
|
|
||||||
mixer: fn() -> Box<Mixer>,
|
mixer: fn(Option<String>) -> Box<Mixer>,
|
||||||
|
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
player_config: PlayerConfig,
|
player_config: PlayerConfig,
|
||||||
|
@ -335,7 +335,7 @@ struct Main {
|
||||||
connect_config: ConnectConfig,
|
connect_config: ConnectConfig,
|
||||||
backend: fn(Option<String>) -> Box<Sink>,
|
backend: fn(Option<String>) -> Box<Sink>,
|
||||||
device: Option<String>,
|
device: Option<String>,
|
||||||
mixer: fn() -> Box<Mixer>,
|
mixer: fn(Option<String>) -> Box<Mixer>,
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
|
|
||||||
discovery: Option<DiscoveryStream>,
|
discovery: Option<DiscoveryStream>,
|
||||||
|
@ -423,12 +423,13 @@ impl Future for Main {
|
||||||
if let Async::Ready(session) = self.connect.poll().unwrap() {
|
if let Async::Ready(session) = self.connect.poll().unwrap() {
|
||||||
self.connect = Box::new(futures::future::empty());
|
self.connect = Box::new(futures::future::empty());
|
||||||
let device = self.device.clone();
|
let device = self.device.clone();
|
||||||
let mixer = (self.mixer)();
|
let mixer = (self.mixer)(device);
|
||||||
let player_config = self.player_config.clone();
|
let player_config = self.player_config.clone();
|
||||||
let connect_config = self.connect_config.clone();
|
let connect_config = self.connect_config.clone();
|
||||||
|
|
||||||
let audio_filter = mixer.get_audio_filter();
|
let audio_filter = mixer.get_audio_filter();
|
||||||
let backend = self.backend;
|
let backend = self.backend;
|
||||||
|
let device = self.device.clone();
|
||||||
let (player, event_channel) =
|
let (player, event_channel) =
|
||||||
Player::new(player_config, session.clone(), audio_filter, move || {
|
Player::new(player_config, session.clone(), audio_filter, move || {
|
||||||
(backend)(device)
|
(backend)(device)
|
||||||
|
|
Loading…
Reference in a new issue