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.
This commit is contained in:
loblik 2017-10-05 20:41:02 +02:00
parent 8971d3aa68
commit adeb22b2f3
4 changed files with 91 additions and 0 deletions

View file

@ -52,6 +52,7 @@ url = "1.3"
alsa = { git = "https://github.com/plietar/rust-alsa", optional = true } alsa = { git = "https://github.com/plietar/rust-alsa", optional = true }
portaudio-rs = { version = "0.3.0", optional = true } portaudio-rs = { version = "0.3.0", optional = true }
libpulse-sys = { version = "0.0.0", optional = true } libpulse-sys = { version = "0.0.0", optional = true }
jack = { version = "0.5.3", optional = true }
[build-dependencies] [build-dependencies]
rand = "0.3.13" rand = "0.3.13"
@ -62,6 +63,7 @@ protobuf_macros = { git = "https://github.com/plietar/rust-protobuf-macros", fea
alsa-backend = ["alsa"] alsa-backend = ["alsa"]
portaudio-backend = ["portaudio-rs"] portaudio-backend = ["portaudio-rs"]
pulseaudio-backend = ["libpulse-sys"] pulseaudio-backend = ["libpulse-sys"]
jackaudio-backend = ["jack"]
with-tremor = ["librespot-audio/with-tremor"] with-tremor = ["librespot-audio/with-tremor"]
with-lewton = ["librespot-audio/with-lewton"] with-lewton = ["librespot-audio/with-lewton"]

View file

@ -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(())
}
}

View file

@ -29,6 +29,11 @@ mod pulseaudio;
#[cfg(feature = "pulseaudio-backend")] #[cfg(feature = "pulseaudio-backend")]
use self::pulseaudio::PulseAudioSink; use self::pulseaudio::PulseAudioSink;
#[cfg(feature = "jackaudio-backend")]
mod jackaudio;
#[cfg(feature = "jackaudio-backend")]
use self::jackaudio::JackSink;
mod pipe; mod pipe;
use self::pipe::StdoutSink; use self::pipe::StdoutSink;
@ -41,6 +46,8 @@ pub const BACKENDS : &'static [
("portaudio", mk_sink::<PortAudioSink>), ("portaudio", mk_sink::<PortAudioSink>),
#[cfg(feature = "pulseaudio-backend")] #[cfg(feature = "pulseaudio-backend")]
("pulseaudio", mk_sink::<PulseAudioSink>), ("pulseaudio", mk_sink::<PulseAudioSink>),
#[cfg(feature = "jackaudio-backend")]
("jackaudio", mk_sink::<JackSink>),
("pipe", mk_sink::<StdoutSink>), ("pipe", mk_sink::<StdoutSink>),
]; ];

View file

@ -34,6 +34,9 @@ extern crate portaudio_rs;
#[cfg(feature = "libpulse-sys")] #[cfg(feature = "libpulse-sys")]
extern crate libpulse_sys; extern crate libpulse_sys;
#[cfg(feature = "jackaudio-backend")]
extern crate jack;
pub mod audio_backend; pub mod audio_backend;
pub mod discovery; pub mod discovery;
pub mod keymaster; pub mod keymaster;