From adeb22b2f31e904568b92c1d103b7681ed08dda7 Mon Sep 17 00:00:00 2001 From: loblik Date: Thu, 5 Oct 2017 20:41:02 +0200 Subject: [PATCH] add support for jack audio connection kit This is initial support for JACK. It creates ports at startup and keeps it connected while librespot is running. So when librespot playback is stoped it writes silence (zeroes). It uses jack crate (rust-jack) which needs libjack. To compile in jack support use --features jackaudio-backend. And run librespot with --backend jackaudio. --- Cargo.toml | 2 + src/audio_backend/jackaudio.rs | 79 ++++++++++++++++++++++++++++++++++ src/audio_backend/mod.rs | 7 +++ src/lib.rs | 3 ++ 4 files changed, 91 insertions(+) create mode 100644 src/audio_backend/jackaudio.rs diff --git a/Cargo.toml b/Cargo.toml index f4e63498..c2a8d87f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ url = "1.3" alsa = { git = "https://github.com/plietar/rust-alsa", optional = true } portaudio-rs = { version = "0.3.0", optional = true } libpulse-sys = { version = "0.0.0", optional = true } +jack = { version = "0.5.3", optional = true } [build-dependencies] rand = "0.3.13" @@ -62,6 +63,7 @@ protobuf_macros = { git = "https://github.com/plietar/rust-protobuf-macros", fea alsa-backend = ["alsa"] portaudio-backend = ["portaudio-rs"] pulseaudio-backend = ["libpulse-sys"] +jackaudio-backend = ["jack"] with-tremor = ["librespot-audio/with-tremor"] with-lewton = ["librespot-audio/with-lewton"] diff --git a/src/audio_backend/jackaudio.rs b/src/audio_backend/jackaudio.rs new file mode 100644 index 00000000..e1a67aec --- /dev/null +++ b/src/audio_backend/jackaudio.rs @@ -0,0 +1,79 @@ +use std::io; +use super::{Open, Sink}; +use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackControl, ProcessScope, AsyncClient, client_options, ProcessHandler, Port }; +use std::sync::mpsc::{sync_channel, SyncSender, Receiver}; + +#[allow(dead_code)] +pub struct JackSink { + send: SyncSender, + active_client: AsyncClient<(),JackData>, +} + +pub struct JackData { + rec: Receiver, + port_l: Port, + port_r: Port, +} + +fn pcm_to_f32(sample: i16) -> f32 { + let mut f: f32 = sample as f32 / 32768.0; + if f > 1.0 { f = 1.0; } + if f < -1.0 { f = -1.0; } + f +} + +impl ProcessHandler for JackData { + fn process(&mut self, _: &Client, ps: &ProcessScope) -> JackControl { + // get output port buffers + let mut out_r = AudioOutPort::new(&mut self.port_r, ps); + let mut out_l = AudioOutPort::new(&mut self.port_l, ps); + let buf_r: &mut [f32] = &mut out_r; + let buf_l: &mut [f32] = &mut out_l; + // get queue iterator + let mut queue_iter = self.rec.try_iter(); + + let buf_size = buf_r.len(); + for i in 0..buf_size { + buf_r[i] = pcm_to_f32(queue_iter.next().unwrap_or(0)); + buf_l[i] = pcm_to_f32(queue_iter.next().unwrap_or(0)); + } + JackControl::Continue + } +} + +impl Open for JackSink { + fn open(client_name: Option) -> JackSink { + info!("Using jack sink!"); + + let client_name = client_name.unwrap_or("librespot".to_string()); + let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap(); + let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap(); + let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap(); + // buffer for samples from librespot (~10ms) + let (tx, rx) = sync_channel(2*1024*4); + let jack_data = JackData { rec: rx, port_l: ch_l, port_r: ch_r }; + let active_client = AsyncClient::new(client, (), jack_data).unwrap(); + + JackSink { send: tx, active_client: active_client } + } +} + +impl Sink for JackSink { + fn start(&mut self) -> io::Result<()> { + Ok(()) + } + + fn stop(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write(&mut self, data: &[i16]) -> io::Result<()> { + for s in data.iter() { + let res = self.send.send(*s); + if res.is_err() { + error!("jackaudio: cannot write to channel"); + } + } + Ok(()) + } +} diff --git a/src/audio_backend/mod.rs b/src/audio_backend/mod.rs index 1effc05a..be73bf6c 100644 --- a/src/audio_backend/mod.rs +++ b/src/audio_backend/mod.rs @@ -29,6 +29,11 @@ mod pulseaudio; #[cfg(feature = "pulseaudio-backend")] use self::pulseaudio::PulseAudioSink; +#[cfg(feature = "jackaudio-backend")] +mod jackaudio; +#[cfg(feature = "jackaudio-backend")] +use self::jackaudio::JackSink; + mod pipe; use self::pipe::StdoutSink; @@ -41,6 +46,8 @@ pub const BACKENDS : &'static [ ("portaudio", mk_sink::), #[cfg(feature = "pulseaudio-backend")] ("pulseaudio", mk_sink::), + #[cfg(feature = "jackaudio-backend")] + ("jackaudio", mk_sink::), ("pipe", mk_sink::), ]; diff --git a/src/lib.rs b/src/lib.rs index b9c920ec..5a9274b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ extern crate portaudio_rs; #[cfg(feature = "libpulse-sys")] extern crate libpulse_sys; +#[cfg(feature = "jackaudio-backend")] +extern crate jack; + pub mod audio_backend; pub mod discovery; pub mod keymaster;