mirror of
https://github.com/librespot-org/librespot.git
synced 2025-03-09 00:17:28 +00:00
commit
5990a0dcff
7 changed files with 247 additions and 33 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -49,10 +49,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa"
|
name = "alsa"
|
||||||
version = "0.0.1"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/plietar/rust-alsa#8c63543fa0ccd971cf15f5675293d19febd6f79e"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
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)",
|
"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]]
|
[[package]]
|
||||||
|
@ -164,6 +167,11 @@ name = "bitflags"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -824,7 +832,7 @@ dependencies = [
|
||||||
name = "librespot-playback"
|
name = "librespot-playback"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.11.0"
|
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 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 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 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 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 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"
|
"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 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.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.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 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-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"
|
"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 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 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.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 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 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"
|
"checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
|
||||||
|
|
|
@ -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.2.1", 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,14 +1,70 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
use alsa::{Access, Format, Mode, Stream, PCM};
|
use alsa::device_name::HintIter;
|
||||||
|
use alsa::pcm::{Access, Format, HwParams, PCM};
|
||||||
|
use alsa::{Direction, Error, ValueOr};
|
||||||
|
use std::ffi::CString;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
pub struct AlsaSink(Option<PCM>, String);
|
pub struct AlsaSink(Option<PCM>, String);
|
||||||
|
|
||||||
|
fn list_outputs() {
|
||||||
|
for t in &["pcm", "ctl", "hwdep"] {
|
||||||
|
println!("{} devices:", t);
|
||||||
|
let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
|
||||||
|
for a in i {
|
||||||
|
if let Some(Direction::Playback) = a.direction {
|
||||||
|
// mimic aplay -L
|
||||||
|
println!(
|
||||||
|
"{}\n\t{}\n",
|
||||||
|
a.name.unwrap(),
|
||||||
|
a.desc.unwrap().replace("\n", "\n\t")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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)?;
|
||||||
|
|
||||||
|
hwp.set_access(Access::RWInterleaved)?;
|
||||||
|
hwp.set_format(Format::s16())?;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
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 = match device.as_ref().map(AsRef::as_ref) {
|
||||||
|
Some("?") => {
|
||||||
|
println!("Listing available alsa outputs");
|
||||||
|
list_outputs();
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
Some(device) => device,
|
||||||
|
None => "default",
|
||||||
|
}.to_string();
|
||||||
|
|
||||||
AlsaSink(None, name)
|
AlsaSink(None, name)
|
||||||
}
|
}
|
||||||
|
@ -17,16 +73,9 @@ 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_none() {
|
||||||
match PCM::open(
|
let pcm = open_device(&self.1);
|
||||||
&*self.1,
|
match pcm {
|
||||||
Stream::Playback,
|
Ok(p) => self.0 = Some(p),
|
||||||
Mode::Blocking,
|
|
||||||
Format::Signed16,
|
|
||||||
Access::Interleaved,
|
|
||||||
2,
|
|
||||||
44100,
|
|
||||||
) {
|
|
||||||
Ok(f) => self.0 = Some(f),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Alsa error PCM open {}", e);
|
error!("Alsa error PCM open {}", e);
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
|
@ -36,16 +85,28 @@ impl Sink for AlsaSink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> io::Result<()> {
|
fn stop(&mut self) -> io::Result<()> {
|
||||||
|
{
|
||||||
|
let pcm = self.0.as_ref().unwrap();
|
||||||
|
pcm.drain().unwrap();
|
||||||
|
}
|
||||||
self.0 = None;
|
self.0 = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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.try_recover(err, false).unwrap(),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
78
playback/src/mixer/alsamixer.rs
Normal file
78
playback/src/mixer/alsamixer.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use super::AudioFilter;
|
||||||
|
use super::{Mixer, MixerConfig};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use alsa;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AlsaMixer {
|
||||||
|
config: MixerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 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 {:?} ->> 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 {:?} <<- alsa {:?}", new_vol, cur_vol);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_vol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mixer for AlsaMixer {
|
||||||
|
fn open(config: Option<MixerConfig>) -> AlsaMixer {
|
||||||
|
let config = config.unwrap_or_default();
|
||||||
|
info!(
|
||||||
|
"Setting up new mixer: card:{} mixer:{} index:{}",
|
||||||
|
config.card, config.mixer, config.index
|
||||||
|
);
|
||||||
|
AlsaMixer { config: config }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_volume(&self, volume: u16) {
|
||||||
|
match self.map_volume(Some(volume)) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<MixerConfig>) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn start(&self);
|
fn start(&self);
|
||||||
|
@ -15,16 +15,39 @@ 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;
|
||||||
|
|
||||||
|
#[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;
|
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<MixerConfig>) -> 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<MixerConfig>) -> 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::AudioFilter;
|
use super::AudioFilter;
|
||||||
use super::Mixer;
|
use super::{Mixer, MixerConfig};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SoftMixer {
|
pub struct SoftMixer {
|
||||||
|
@ -10,7 +10,7 @@ pub struct SoftMixer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixer for SoftMixer {
|
impl Mixer for SoftMixer {
|
||||||
fn open() -> SoftMixer {
|
fn open(_: Option<MixerConfig>) -> SoftMixer {
|
||||||
SoftMixer {
|
SoftMixer {
|
||||||
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
||||||
}
|
}
|
||||||
|
|
49
src/main.rs
49
src/main.rs
|
@ -37,7 +37,7 @@ use librespot::connect::discovery::{discovery, DiscoveryStream};
|
||||||
use librespot::connect::spirc::{Spirc, SpircTask};
|
use librespot::connect::spirc::{Spirc, SpircTask};
|
||||||
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
||||||
use librespot::playback::config::{Bitrate, PlayerConfig};
|
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};
|
use librespot::playback::player::{Player, PlayerEvent};
|
||||||
|
|
||||||
mod player_event_handler;
|
mod player_event_handler;
|
||||||
|
@ -90,12 +90,13 @@ 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<MixerConfig>) -> Box<Mixer>,
|
||||||
|
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
player_config: PlayerConfig,
|
player_config: PlayerConfig,
|
||||||
session_config: SessionConfig,
|
session_config: SessionConfig,
|
||||||
connect_config: ConnectConfig,
|
connect_config: ConnectConfig,
|
||||||
|
mixer_config: MixerConfig,
|
||||||
credentials: Option<Credentials>,
|
credentials: Option<Credentials>,
|
||||||
enable_discovery: bool,
|
enable_discovery: bool,
|
||||||
zeroconf_port: u16,
|
zeroconf_port: u16,
|
||||||
|
@ -139,10 +140,28 @@ fn setup(args: &[String]) -> Setup {
|
||||||
.optopt(
|
.optopt(
|
||||||
"",
|
"",
|
||||||
"device",
|
"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",
|
"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(
|
.optopt(
|
||||||
"",
|
"",
|
||||||
"initial-volume",
|
"initial-volume",
|
||||||
|
@ -208,6 +227,15 @@ fn setup(args: &[String]) -> Setup {
|
||||||
let mixer_name = matches.opt_str("mixer");
|
let mixer_name = matches.opt_str("mixer");
|
||||||
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid 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 use_audio_cache = !matches.opt_present("disable-audio-cache");
|
||||||
|
|
||||||
let cache = matches
|
let cache = matches
|
||||||
|
@ -222,8 +250,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
panic!("Initial volume must be in the range 0-100");
|
panic!("Initial volume must be in the range 0-100");
|
||||||
}
|
}
|
||||||
(volume as i32 * 0xFFFF / 100) as u16
|
(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);
|
.unwrap_or(0x8000);
|
||||||
|
|
||||||
let zeroconf_port = matches
|
let zeroconf_port = matches
|
||||||
|
@ -324,6 +351,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
enable_discovery: enable_discovery,
|
enable_discovery: enable_discovery,
|
||||||
zeroconf_port: zeroconf_port,
|
zeroconf_port: zeroconf_port,
|
||||||
mixer: mixer,
|
mixer: mixer,
|
||||||
|
mixer_config: mixer_config,
|
||||||
player_event_program: matches.opt_str("onevent"),
|
player_event_program: matches.opt_str("onevent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,7 +363,8 @@ 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<MixerConfig>) -> Box<Mixer>,
|
||||||
|
mixer_config: MixerConfig,
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
|
|
||||||
discovery: Option<DiscoveryStream>,
|
discovery: Option<DiscoveryStream>,
|
||||||
|
@ -362,6 +391,7 @@ impl Main {
|
||||||
backend: setup.backend,
|
backend: setup.backend,
|
||||||
device: setup.device,
|
device: setup.device,
|
||||||
mixer: setup.mixer,
|
mixer: setup.mixer,
|
||||||
|
mixer_config: setup.mixer_config,
|
||||||
|
|
||||||
connect: Box::new(futures::future::empty()),
|
connect: Box::new(futures::future::empty()),
|
||||||
discovery: None,
|
discovery: None,
|
||||||
|
@ -422,13 +452,14 @@ 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 mixer_config = self.mixer_config.clone();
|
||||||
let mixer = (self.mixer)();
|
let mixer = (self.mixer)(Some(mixer_config));
|
||||||
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