Merge pull request #540 from jnqnfe/pa

Improve PulseAudio backend
This commit is contained in:
Sasha Hilton 2020-12-14 01:20:22 +00:00 committed by GitHub
commit 7e8feed6dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 105 deletions

47
Cargo.lock generated
View file

@ -1190,11 +1190,46 @@ dependencies = [
] ]
[[package]] [[package]]
name = "libpulse-sys" name = "libpulse-binding"
version = "0.0.0" version = "2.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
@ -1334,7 +1369,8 @@ dependencies = [
"gstreamer-app 0.15.6 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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-audio 0.1.3",
"librespot-core 0.1.3", "librespot-core 0.1.3",
"librespot-metadata 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.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 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 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 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 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" "checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"

View file

@ -25,7 +25,8 @@ shell-words = "0.1.0"
alsa = { version = "0.2", optional = true } alsa = { version = "0.2", optional = true }
portaudio-rs = { version = "0.3", 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 } jack = { version = "0.5", optional = true }
libc = { version = "0.2", optional = true } libc = { version = "0.2", optional = true }
rodio = { version = "0.13", optional = true, default-features = false } rodio = { version = "0.13", optional = true, default-features = false }
@ -39,7 +40,7 @@ zerocopy = { version = "0.2", optional = true }
[features] [features]
alsa-backend = ["alsa"] alsa-backend = ["alsa"]
portaudio-backend = ["portaudio-rs"] portaudio-backend = ["portaudio-rs"]
pulseaudio-backend = ["libpulse-sys", "libc"] pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"]
jackaudio-backend = ["jack"] jackaudio-backend = ["jack"]
rodio-backend = ["rodio", "cpal"] rodio-backend = ["rodio", "cpal"]
sdl-backend = ["sdl2"] sdl-backend = ["sdl2"]

View file

@ -1,130 +1,86 @@
use super::{Open, Sink}; use super::{Open, Sink};
use libc; use libpulse_binding::{self as pulse, stream::Direction};
use libpulse_sys::*; use libpulse_simple_binding::Simple;
use std::ffi::CStr;
use std::ffi::CString;
use std::io; 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 { pub struct PulseAudioSink {
s: *mut pa_simple, s: Option<Simple>,
ss: pa_sample_spec, ss: pulse::sample::Spec,
name: CString, device: Option<String>,
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)
}
}
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();
}
} }
impl Open for PulseAudioSink { impl Open for PulseAudioSink {
fn open(device: Option<String>) -> PulseAudioSink { fn open(device: Option<String>) -> PulseAudioSink {
debug!("Using PulseAudio sink"); debug!("Using PulseAudio sink");
let ss = pa_sample_spec { let ss = pulse::sample::Spec {
format: PA_SAMPLE_S16LE, format: pulse::sample::Format::S16le,
channels: 2, // stereo channels: 2, // stereo
rate: 44100, rate: 44100,
}; };
debug_assert!(ss.is_valid());
let name = CString::new("librespot").unwrap();
let description = CString::new("Spotify endpoint").unwrap();
PulseAudioSink { PulseAudioSink {
s: null_mut(), s: None,
ss: ss, ss: ss,
name: name, device: device,
desc: description,
device: device.and_then(|s| CString::new(s).ok()),
} }
} }
} }
impl Sink for PulseAudioSink { impl Sink for PulseAudioSink {
fn start(&mut self) -> io::Result<()> { fn start(&mut self) -> io::Result<()> {
if self.s == null_mut() { if self.s.is_some() {
let device = match &self.device { return Ok(());
None => null(), }
Some(device) => device.as_ptr(),
}; let device = self.device.as_ref().map(|s| (*s).as_str());
self.s = call_pulseaudio( let result = Simple::new(
|err| unsafe { None, // Use the default server.
pa_simple_new( APP_NAME, // Our application's name.
null(), // Use the default server. Direction::Playback, // Direction.
self.name.as_ptr(), // Our application's name. device, // Our device (sink) name.
PA_STREAM_PLAYBACK, STREAM_NAME, // Description of our stream.
device, &self.ss, // Our sample format.
self.desc.as_ptr(), // desc of our stream. None, // Use default channel map.
&self.ss, // Our sample format. None, // Use default buffering attributes.
null(), // Use default channel map );
null(), // Use default buffering attributes. match result {
err, Ok(s) => {
) self.s = Some(s);
}, Ok(())
|ptr| ptr == null_mut(), }
io::ErrorKind::ConnectionRefused, Err(e) => Err(io::Error::new(
)?; io::ErrorKind::ConnectionRefused,
e.to_string().unwrap(),
)),
} }
Ok(())
} }
fn stop(&mut self) -> io::Result<()> { fn stop(&mut self) -> io::Result<()> {
self.free_connection(); self.s = None;
Ok(()) Ok(())
} }
fn write(&mut self, data: &[i16]) -> io::Result<()> { 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( Err(io::Error::new(
io::ErrorKind::NotConnected, io::ErrorKind::NotConnected,
"Not connected to pulseaudio", "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(())
} }
} }
} }

View file

@ -8,11 +8,13 @@ extern crate shell_words;
#[cfg(feature = "alsa-backend")] #[cfg(feature = "alsa-backend")]
extern crate alsa; extern crate alsa;
#[cfg(feature = "portaudio-rs")] #[cfg(feature = "portaudio-backend")]
extern crate portaudio_rs; extern crate portaudio_rs;
#[cfg(feature = "libpulse-sys")] #[cfg(feature = "pulseaudio-backend")]
extern crate libpulse_sys; extern crate libpulse_binding;
#[cfg(feature = "pulseaudio-backend")]
extern crate libpulse_simple_binding;
#[cfg(feature = "jackaudio-backend")] #[cfg(feature = "jackaudio-backend")]
extern crate jack; extern crate jack;
@ -29,9 +31,6 @@ extern crate zerocopy;
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
extern crate sdl2; extern crate sdl2;
#[cfg(feature = "libc")]
extern crate libc;
extern crate librespot_audio as audio; extern crate librespot_audio as audio;
extern crate librespot_core; extern crate librespot_core;
extern crate librespot_metadata as metadata; extern crate librespot_metadata as metadata;