diff --git a/Cargo.toml b/Cargo.toml index dfe27524..6e3a798d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,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 } libc = { version = "0.2", optional = true } dns-sd = { version = "0.1.3", optional = true } @@ -65,6 +66,7 @@ protobuf_macros = { git = "https://github.com/plietar/rust-protobuf-macros", fea alsa-backend = ["alsa"] portaudio-backend = ["portaudio-rs"] pulseaudio-backend = ["libpulse-sys", "libc"] +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..9b389ea6 --- /dev/null +++ b/src/audio_backend/jackaudio.rs @@ -0,0 +1,75 @@ +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}; + +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 { + sample as f32 / 32768.0; +} + +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 a08f2f5c..09398162 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,9 @@ extern crate portaudio_rs; #[cfg(feature = "libpulse-sys")] extern crate libpulse_sys; +#[cfg(feature = "jackaudio-backend")] +extern crate jack; + #[cfg(feature = "libc")] extern crate libc;