Browse Source

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.
loblik 7 years ago
parent
commit
adeb22b2f3
4 changed files with 91 additions and 0 deletions
  1. 2 0
      Cargo.toml
  2. 79 0
      src/audio_backend/jackaudio.rs
  3. 7 0
      src/audio_backend/mod.rs
  4. 3 0
      src/lib.rs

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

+ 79 - 0
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<i16>,
+    active_client: AsyncClient<(),JackData>,
+}
+
+pub struct JackData {
+    rec: Receiver<i16>,
+    port_l: Port<AudioOutSpec>,
+    port_r: Port<AudioOutSpec>,
+}
+
+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<String>) -> 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(())
+    }
+}

+ 7 - 0
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::<PortAudioSink>),
     #[cfg(feature = "pulseaudio-backend")]
     ("pulseaudio", mk_sink::<PulseAudioSink>),
+    #[cfg(feature = "jackaudio-backend")]
+    ("jackaudio", mk_sink::<JackSink>),
     ("pipe", mk_sink::<StdoutSink>),
 ];
 

+ 3 - 0
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;