diff --git a/Cargo.lock b/Cargo.lock index a3789f16..37205efe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1190,11 +1190,46 @@ dependencies = [ ] [[package]] -name = "libpulse-sys" -version = "0.0.0" +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" +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 = "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 +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-sys 0.0.0 (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", @@ -3310,7 +3346,10 @@ 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-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" "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 df380078..7c672996 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-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 } @@ -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-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 8a833d65..e0b9ad9b 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -1,130 +1,86 @@ use super::{Open, Sink}; -use libc; -use libpulse_sys::*; -use std::ffi::CStr; -use std::ffi::CString; +use libpulse_binding::{self as pulse, stream::Direction}; +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. + 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) => { + 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 e2a2ac83..f4606430 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -8,11 +8,13 @@ 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")] -extern crate libpulse_sys; +#[cfg(feature = "pulseaudio-backend")] +extern crate libpulse_binding; +#[cfg(feature = "pulseaudio-backend")] +extern crate libpulse_simple_binding; #[cfg(feature = "jackaudio-backend")] extern crate jack; @@ -29,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;