Browse Source

Merge pull request #540 from jnqnfe/pa

Improve PulseAudio backend
Sasha Hilton 3 years ago
parent
commit
7e8feed6dd
4 changed files with 98 additions and 103 deletions
  1. 42 3
      Cargo.lock
  2. 3 2
      playback/Cargo.toml
  3. 48 92
      playback/src/audio_backend/pulseaudio.rs
  4. 5 6
      playback/src/lib.rs

+ 42 - 3
Cargo.lock

@@ -1189,12 +1189,47 @@ 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"
+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 +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"

+ 3 - 2
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"]

+ 48 - 92
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};
 
-pub struct PulseAudioSink {
-    s: *mut pa_simple,
-    ss: pa_sample_spec,
-    name: CString,
-    desc: CString,
-    device: Option<CString>,
-}
-
-fn call_pulseaudio<T, F, FailCheck>(
-    f: F,
-    fail_check: FailCheck,
-    kind: io::ErrorKind,
-) -> io::Result<T>
-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)
-    }
-}
+const APP_NAME: &str = "librespot";
+const STREAM_NAME: &str = "Spotify endpoint";
 
-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();
-    }
+pub struct PulseAudioSink {
+    s: Option<Simple>,
+    ss: pulse::sample::Spec,
+    device: Option<String>,
 }
 
 impl Open for PulseAudioSink {
     fn open(device: Option<String>) -> 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(),
+        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::<i16>();
-            assert!(len > 0);
-            call_pulseaudio(
-                |err| unsafe { pa_simple_write(self.s, ptr, len, err) },
-                |ret| ret < 0,
-                io::ErrorKind::BrokenPipe,
-            )?;
-            Ok(())
         }
     }
 }

+ 5 - 6
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;