From cea63e57a4f4bd4393ad0da77eb7881bcb193d9b Mon Sep 17 00:00:00 2001 From: Lyndon Brown Date: Fri, 27 Nov 2020 01:57:49 +0000 Subject: [PATCH 1/4] use actual feature names rather than crate names for conditional compilation --- playback/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playback/src/lib.rs b/playback/src/lib.rs index e2a2ac8..79b9856 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -8,10 +8,10 @@ extern crate shell_words; #[cfg(feature = "alsa-backend")] extern crate alsa; -#[cfg(feature = "portaudio-rs")] +#[cfg(feature = "portaudio-backend")] extern crate portaudio_rs; -#[cfg(feature = "libpulse-sys")] +#[cfg(feature = "pulseaudio-backend")] extern crate libpulse_sys; #[cfg(feature = "jackaudio-backend")] From 28061dffe29963c0aa48619d54f1b9287133575d Mon Sep 17 00:00:00 2001 From: Lyndon Brown Date: Fri, 27 Nov 2020 00:06:16 +0000 Subject: [PATCH 2/4] upgrade to newer PulseAudio crate dependency requires adding dependency on libpulse-simple-sys since the PulseAudio simple components were moved to their own crate (the original version did not stick to the one crate per one system library rule). this fixes the licensing compatibility issue discussed in #539 ([1]) (the original v0.0.0 was LGPL-3.0 licensed, while v1.11 onwards are 'MIT OR Apache-2.0'). [1]: https://github.com/librespot-org/librespot/issues/539 --- Cargo.lock | 21 ++++++++++++++++++--- playback/Cargo.toml | 5 +++-- playback/src/audio_backend/pulseaudio.rs | 1 + playback/src/lib.rs | 2 ++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3789f1..23a90a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,12 +1189,25 @@ dependencies = [ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libpulse-simple-sys" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libpulse-sys" -version = "0.0.0" +version = "1.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1334,7 +1347,8 @@ dependencies = [ "gstreamer-app 0.15.6 (registry+https://github.com/rust-lang/crates.io-index)", "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", - "libpulse-sys 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-simple-sys 1.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)", "librespot-audio 0.1.3", "librespot-core 0.1.3", "librespot-metadata 0.1.3", @@ -3310,7 +3324,8 @@ dependencies = [ "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum libmdns 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5d8582c174736c53633bc482ac709b24527c018356c3dc6d8e25a788b06b394e" -"checksum libpulse-sys 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9bb11b06faf883500c1b625cf4453e6c7737e9df9c7ba01df3f84b22b083e4ac" +"checksum libpulse-simple-sys 1.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b72cb239bc4de6858fa0bbad27419e72cd4466f079ca56f21d94b0a712ab02e" +"checksum libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "706e95c4b87ebb81c1e7763c74bf7d5ba897208f1a8aa5fc7bea8298dee8f2ca" "checksum librespot-tremor 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b155a7dc4e4d272e01c37a1b85c1ee1bee7f04980ad4a7784c1a6e0f2de5929b" "checksum linear-map 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" "checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" diff --git a/playback/Cargo.toml b/playback/Cargo.toml index df38007..0392ac8 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -25,7 +25,8 @@ shell-words = "0.1.0" alsa = { version = "0.2", optional = true } portaudio-rs = { version = "0.3", optional = true } -libpulse-sys = { version = "0.0.0", optional = true } +libpulse-sys = { version = "1.11", optional = true, default-features = false } +libpulse-simple-sys = { version = "1.11", optional = true, default-features = false } jack = { version = "0.5", optional = true } libc = { version = "0.2", optional = true } rodio = { version = "0.13", optional = true, default-features = false } @@ -39,7 +40,7 @@ zerocopy = { version = "0.2", optional = true } [features] alsa-backend = ["alsa"] portaudio-backend = ["portaudio-rs"] -pulseaudio-backend = ["libpulse-sys", "libc"] +pulseaudio-backend = ["libpulse-sys", "libpulse-simple-sys", "libc"] jackaudio-backend = ["jack"] rodio-backend = ["rodio", "cpal"] sdl-backend = ["sdl2"] diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs index 8a833d6..80faa43 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -1,6 +1,7 @@ use super::{Open, Sink}; use libc; use libpulse_sys::*; +use libpulse_simple_sys::*; use std::ffi::CStr; use std::ffi::CString; use std::io; diff --git a/playback/src/lib.rs b/playback/src/lib.rs index 79b9856..08cbd40 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -13,6 +13,8 @@ extern crate portaudio_rs; #[cfg(feature = "pulseaudio-backend")] extern crate libpulse_sys; +#[cfg(feature = "pulseaudio-backend")] +extern crate libpulse_simple_sys; #[cfg(feature = "jackaudio-backend")] extern crate jack; From 0411e6954822cf97afb7d58b6088f8125aca78df Mon Sep 17 00:00:00 2001 From: Lyndon Brown Date: Fri, 27 Nov 2020 01:06:25 +0000 Subject: [PATCH 3/4] convert PulseAudio backend to use the available binding crates rather than the raw 'sys' layer. --- Cargo.lock | 28 ++++- playback/Cargo.toml | 6 +- playback/src/audio_backend/pulseaudio.rs | 143 ++++++++--------------- playback/src/lib.rs | 7 +- 4 files changed, 80 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23a90a7..37205ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,6 +1189,28 @@ dependencies = [ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libpulse-binding" +version = "2.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libpulse-simple-binding" +version = "2.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libpulse-binding 2.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-simple-sys 1.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libpulse-simple-sys" version = "1.15.1" @@ -1347,8 +1369,8 @@ dependencies = [ "gstreamer-app 0.15.6 (registry+https://github.com/rust-lang/crates.io-index)", "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", - "libpulse-simple-sys 1.15.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-binding 2.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libpulse-simple-binding 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)", "librespot-audio 0.1.3", "librespot-core 0.1.3", "librespot-metadata 0.1.3", @@ -3324,6 +3346,8 @@ dependencies = [ "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum libmdns 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5d8582c174736c53633bc482ac709b24527c018356c3dc6d8e25a788b06b394e" +"checksum libpulse-binding 2.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e8f85a42300c868de4849bb72eda5a65cea08c3ca61396b72c2d7c28a87f055" +"checksum libpulse-simple-binding 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a047f4502997eed57b3e9d8e71f2b860da91a20bb7e15c65d1f183a7b4fb1226" "checksum libpulse-simple-sys 1.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b72cb239bc4de6858fa0bbad27419e72cd4466f079ca56f21d94b0a712ab02e" "checksum libpulse-sys 1.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "706e95c4b87ebb81c1e7763c74bf7d5ba897208f1a8aa5fc7bea8298dee8f2ca" "checksum librespot-tremor 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b155a7dc4e4d272e01c37a1b85c1ee1bee7f04980ad4a7784c1a6e0f2de5929b" diff --git a/playback/Cargo.toml b/playback/Cargo.toml index 0392ac8..7c67299 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -25,8 +25,8 @@ shell-words = "0.1.0" alsa = { version = "0.2", optional = true } portaudio-rs = { version = "0.3", optional = true } -libpulse-sys = { version = "1.11", optional = true, default-features = false } -libpulse-simple-sys = { version = "1.11", optional = true, default-features = false } +libpulse-binding = { version = "2.13", optional = true, default-features = false } +libpulse-simple-binding = { version = "2.13", optional = true, default-features = false } jack = { version = "0.5", optional = true } libc = { version = "0.2", optional = true } rodio = { version = "0.13", optional = true, default-features = false } @@ -40,7 +40,7 @@ zerocopy = { version = "0.2", optional = true } [features] alsa-backend = ["alsa"] portaudio-backend = ["portaudio-rs"] -pulseaudio-backend = ["libpulse-sys", "libpulse-simple-sys", "libc"] +pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"] jackaudio-backend = ["jack"] rodio-backend = ["rodio", "cpal"] sdl-backend = ["sdl2"] diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs index 80faa43..2f0e85d 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -1,131 +1,86 @@ use super::{Open, Sink}; -use libc; -use libpulse_sys::*; -use libpulse_simple_sys::*; -use std::ffi::CStr; -use std::ffi::CString; +use libpulse_binding as pulse; +use libpulse_simple_binding::Simple; use std::io; -use std::mem; -use std::ptr::{null, null_mut}; + +const APP_NAME: &str = "librespot"; +const STREAM_NAME: &str = "Spotify endpoint"; pub struct PulseAudioSink { - s: *mut pa_simple, - ss: pa_sample_spec, - name: CString, - desc: CString, - device: Option, -} - -fn call_pulseaudio( - f: F, - fail_check: FailCheck, - kind: io::ErrorKind, -) -> io::Result -where - T: Copy, - F: Fn(*mut libc::c_int) -> T, - FailCheck: Fn(T) -> bool, -{ - let mut error: libc::c_int = 0; - let ret = f(&mut error); - if fail_check(ret) { - let err_cstr = unsafe { CStr::from_ptr(pa_strerror(error)) }; - let errstr = err_cstr.to_string_lossy().into_owned(); - Err(io::Error::new(kind, errstr)) - } else { - Ok(ret) - } -} - -impl PulseAudioSink { - fn free_connection(&mut self) { - if self.s != null_mut() { - unsafe { - pa_simple_free(self.s); - } - self.s = null_mut(); - } - } -} - -impl Drop for PulseAudioSink { - fn drop(&mut self) { - self.free_connection(); - } + s: Option, + ss: pulse::sample::Spec, + device: Option, } impl Open for PulseAudioSink { fn open(device: Option) -> PulseAudioSink { debug!("Using PulseAudio sink"); - let ss = pa_sample_spec { - format: PA_SAMPLE_S16LE, + let ss = pulse::sample::Spec { + format: pulse::sample::Format::S16le, channels: 2, // stereo rate: 44100, }; - - let name = CString::new("librespot").unwrap(); - let description = CString::new("Spotify endpoint").unwrap(); + debug_assert!(ss.is_valid()); PulseAudioSink { - s: null_mut(), + s: None, ss: ss, - name: name, - desc: description, - device: device.and_then(|s| CString::new(s).ok()), + device: device, } } } impl Sink for PulseAudioSink { fn start(&mut self) -> io::Result<()> { - if self.s == null_mut() { - let device = match &self.device { - None => null(), - Some(device) => device.as_ptr(), - }; - self.s = call_pulseaudio( - |err| unsafe { - pa_simple_new( - null(), // Use the default server. - self.name.as_ptr(), // Our application's name. - PA_STREAM_PLAYBACK, - device, - self.desc.as_ptr(), // desc of our stream. - &self.ss, // Our sample format. - null(), // Use default channel map - null(), // Use default buffering attributes. - err, - ) - }, - |ptr| ptr == null_mut(), - io::ErrorKind::ConnectionRefused, - )?; + if self.s.is_some() { + return Ok(()); + } + + let device = self.device.as_ref().map(|s| (*s).as_str()); + let result = Simple::new( + None, // Use the default server. + APP_NAME, // Our application's name. + pulse::stream::Direction::Playback, + device, + STREAM_NAME, // desc of our stream. + &self.ss, // Our sample format. + None, // Use default channel map + None, // Use default buffering attributes. + ); + match result { + Ok(s) => { + self.s = Some(s); + Ok(()) + } + Err(e) => Err(io::Error::new( + io::ErrorKind::ConnectionRefused, + e.to_string().unwrap(), + )), } - Ok(()) } fn stop(&mut self) -> io::Result<()> { - self.free_connection(); + self.s = None; Ok(()) } fn write(&mut self, data: &[i16]) -> io::Result<()> { - if self.s == null_mut() { + if let Some(s) = &self.s { + let d: &[u8] = unsafe { std::mem::transmute(data) }; + + match s.write(d) { + Ok(_) => Ok(()), + Err(e) => Err(io::Error::new( + io::ErrorKind::BrokenPipe, + e.to_string().unwrap(), + )), + } + } else { Err(io::Error::new( io::ErrorKind::NotConnected, "Not connected to pulseaudio", )) - } else { - let ptr = data.as_ptr() as *const libc::c_void; - let len = data.len() as usize * mem::size_of::(); - assert!(len > 0); - call_pulseaudio( - |err| unsafe { pa_simple_write(self.s, ptr, len, err) }, - |ret| ret < 0, - io::ErrorKind::BrokenPipe, - )?; - Ok(()) } } } diff --git a/playback/src/lib.rs b/playback/src/lib.rs index 08cbd40..f460643 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -12,9 +12,9 @@ extern crate alsa; extern crate portaudio_rs; #[cfg(feature = "pulseaudio-backend")] -extern crate libpulse_sys; +extern crate libpulse_binding; #[cfg(feature = "pulseaudio-backend")] -extern crate libpulse_simple_sys; +extern crate libpulse_simple_binding; #[cfg(feature = "jackaudio-backend")] extern crate jack; @@ -31,9 +31,6 @@ extern crate zerocopy; #[cfg(feature = "sdl-backend")] extern crate sdl2; -#[cfg(feature = "libc")] -extern crate libc; - extern crate librespot_audio as audio; extern crate librespot_core; extern crate librespot_metadata as metadata; From 120bd88326c1f79b519fe3d07c212e34b2c4bc0a Mon Sep 17 00:00:00 2001 From: Lyndon Brown Date: Fri, 27 Nov 2020 05:08:16 +0000 Subject: [PATCH 4/4] fix alignment causing format check failure --- playback/src/audio_backend/pulseaudio.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs index 2f0e85d..e0b9ad9 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -1,5 +1,5 @@ use super::{Open, Sink}; -use libpulse_binding as pulse; +use libpulse_binding::{self as pulse, stream::Direction}; use libpulse_simple_binding::Simple; use std::io; @@ -39,14 +39,14 @@ impl Sink for PulseAudioSink { let device = self.device.as_ref().map(|s| (*s).as_str()); let result = Simple::new( - None, // Use the default server. - APP_NAME, // Our application's name. - pulse::stream::Direction::Playback, - device, - STREAM_NAME, // desc of our stream. - &self.ss, // Our sample format. - None, // Use default channel map - None, // Use default buffering attributes. + None, // Use the default server. + APP_NAME, // Our application's name. + Direction::Playback, // Direction. + device, // Our device (sink) name. + STREAM_NAME, // Description of our stream. + &self.ss, // Our sample format. + None, // Use default channel map. + None, // Use default buffering attributes. ); match result { Ok(s) => {