From c0942f14e8868c3c0836438ed1d92cc698c8d38c Mon Sep 17 00:00:00 2001 From: johannesd3 Date: Tue, 23 Feb 2021 15:05:02 +0100 Subject: [PATCH] Restore rodiojack support Probably more simple than the previous approach which doubles the code: Instead of implementing the `Open` trait, we simply use custom SinkBuilder, one for the default host, and one for the "jack" host. --- .github/workflows/test.yml | 1 + COMPILING.md | 1 + Cargo.lock | 1 + Cargo.toml | 1 + README.md | 1 + playback/Cargo.toml | 1 + playback/src/audio_backend/mod.rs | 9 +-- playback/src/audio_backend/rodio.rs | 94 +++++++++++++++++------------ 8 files changed, 65 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ad4b406..c20fe1c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,7 @@ jobs: - run: cargo build --locked --no-default-features --features "portaudio-backend" - run: cargo build --locked --no-default-features --features "pulseaudio-backend" - run: cargo build --locked --no-default-features --features "jackaudio-backend" + - run: cargo build --locked --no-default-features --features "rodiojack-backend" - run: cargo build --locked --no-default-features --features "rodio-backend" - run: cargo build --locked --no-default-features --features "sdl-backend" - run: cargo build --locked --no-default-features --features "gstreamer-backend" diff --git a/COMPILING.md b/COMPILING.md index 7b3467ee..40eefb39 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -46,6 +46,7 @@ Depending on the chosen backend, specific development libraries are required. |PortAudio | `portaudio19-dev` | `portaudio-devel` | `portaudio` | |PulseAudio | `libpulse-dev` | `pulseaudio-libs-devel` | | |JACK | `libjack-dev` | `jack-audio-connection-kit-devel` | | +|JACK over Rodio | `libjack-dev` | `jack-audio-connection-kit-devel` | - | |SDL | `libsdl2-dev` | `SDL2-devel` | | |Pipe | - | - | - | diff --git a/Cargo.lock b/Cargo.lock index 33dbb922..1e8176f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,7 @@ dependencies = [ "alsa", "core-foundation-sys", "coreaudio-rs", + "jack", "jni 0.17.0", "js-sys", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 21c010c9..d34189ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ portaudio-backend = ["librespot-playback/portaudio-backend"] pulseaudio-backend = ["librespot-playback/pulseaudio-backend"] jackaudio-backend = ["librespot-playback/jackaudio-backend"] rodio-backend = ["librespot-playback/rodio-backend"] +rodiojack-backend = ["librespot-playback/rodiojack-backend"] sdl-backend = ["librespot-playback/sdl-backend"] gstreamer-backend = ["librespot-playback/gstreamer-backend"] diff --git a/README.md b/README.md index e7611aa8..7102c28a 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ ALSA PortAudio PulseAudio JACK +JACK over Rodio SDL Pipe ``` diff --git a/playback/Cargo.toml b/playback/Cargo.toml index acb20c46..2759ae0e 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -46,5 +46,6 @@ portaudio-backend = ["portaudio-rs"] pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"] jackaudio-backend = ["jack"] rodio-backend = ["rodio", "cpal", "thiserror"] +rodiojack-backend = ["rodio", "cpal/jack", "thiserror"] sdl-backend = ["sdl2"] gstreamer-backend = ["gstreamer", "gstreamer-app", "glib", "zerocopy"] diff --git a/playback/src/audio_backend/mod.rs b/playback/src/audio_backend/mod.rs index 50031a40..214ede8c 100644 --- a/playback/src/audio_backend/mod.rs +++ b/playback/src/audio_backend/mod.rs @@ -42,10 +42,9 @@ mod gstreamer; #[cfg(feature = "gstreamer-backend")] use self::gstreamer::GstreamerSink; -#[cfg(feature = "rodio-backend")] +#[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))] mod rodio; -#[cfg(feature = "rodio-backend")] -use self::rodio::RodioSink; + #[cfg(feature = "sdl-backend")] mod sdl; #[cfg(feature = "sdl-backend")] @@ -69,7 +68,9 @@ pub const BACKENDS: &'static [(&'static str, SinkBuilder)] = &[ #[cfg(feature = "gstreamer-backend")] ("gstreamer", mk_sink::), #[cfg(feature = "rodio-backend")] - ("rodio", mk_sink::), + ("rodio", rodio::mk_rodio), + #[cfg(feature = "rodiojack-backend")] + ("rodiojack", rodio::mk_rodiojack), #[cfg(feature = "sdl-backend")] ("sdl", mk_sink::), ("pipe", mk_sink::), diff --git a/playback/src/audio_backend/rodio.rs b/playback/src/audio_backend/rodio.rs index 1b7a8b8a..56e19b61 100644 --- a/playback/src/audio_backend/rodio.rs +++ b/playback/src/audio_backend/rodio.rs @@ -5,9 +5,28 @@ use std::{io, thread, time}; use cpal::traits::{DeviceTrait, HostTrait}; use thiserror::Error; -use super::{Open, Sink}; +use super::Sink; use crate::audio::AudioPacket; +#[cfg(all( + feature = "rodiojack-backend", + not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")) +))] +compile_error!("Rodio JACK backend is currently only supported on linux."); + +#[cfg(feature = "rodio-backend")] +pub fn mk_rodio(device: Option) -> Box { + Box::new(open(cpal::default_host(), device)) +} + +#[cfg(feature = "rodiojack-backend")] +pub fn mk_rodiojack(device: Option) -> Box { + Box::new(open( + cpal::host_from_id(cpal::HostId::Jack).unwrap(), + device, + )) +} + #[derive(Debug, Error)] pub enum RodioError { #[error("Rodio: no device available")] @@ -60,10 +79,10 @@ fn list_formats(device: &rodio::Device) { } } -fn list_outputs() -> Result<(), cpal::DevicesError> { +fn list_outputs(host: &cpal::Host) -> Result<(), cpal::DevicesError> { let mut default_device_name = None; - if let Some(default_device) = get_default_device() { + if let Some(default_device) = host.default_output_device() { default_device_name = default_device.name().ok(); println!( "Default Audio Device:\n {}", @@ -77,7 +96,7 @@ fn list_outputs() -> Result<(), cpal::DevicesError> { warn!("No default device was found"); } - for device in cpal::default_host().output_devices()? { + for device in host.output_devices()? { match device.name() { Ok(name) if Some(&name) == default_device_name.as_ref() => (), Ok(name) => { @@ -95,14 +114,13 @@ fn list_outputs() -> Result<(), cpal::DevicesError> { Ok(()) } -fn get_default_device() -> Option { - cpal::default_host().default_output_device() -} - -fn create_sink(device: Option) -> Result<(rodio::Sink, rodio::OutputStream), RodioError> { +fn create_sink( + host: &cpal::Host, + device: Option, +) -> Result<(rodio::Sink, rodio::OutputStream), RodioError> { let rodio_device = match device { Some(ask) if &ask == "?" => { - let exit_code = match list_outputs() { + let exit_code = match list_outputs(host) { Ok(()) => 0, Err(e) => { error!("{}", e); @@ -112,12 +130,13 @@ fn create_sink(device: Option) -> Result<(rodio::Sink, rodio::OutputStre exit(exit_code) } Some(device_name) => { - cpal::default_host() - .output_devices()? + host.output_devices()? .find(|d| d.name().ok().map_or(false, |name| name == device_name)) // Ignore devices for which getting name fails .ok_or(RodioError::DeviceNotAvailable(device_name))? } - None => get_default_device().ok_or(RodioError::NoDeviceAvailable)?, + None => host + .default_output_device() + .ok_or(RodioError::NoDeviceAvailable)?, }; let name = rodio_device.name().ok(); @@ -131,37 +150,32 @@ fn create_sink(device: Option) -> Result<(rodio::Sink, rodio::OutputStre Ok((sink, stream)) } -impl Open for RodioSink { - fn open(device: Option) -> RodioSink { - debug!( - "Using rodio sink with cpal host: {:?}", - cpal::default_host().id().name() - ); +pub fn open(host: cpal::Host, device: Option) -> RodioSink { + debug!("Using rodio sink with cpal host: {}", host.id().name()); - let (sink_tx, sink_rx) = mpsc::sync_channel(1); - let (close_tx, close_rx) = mpsc::sync_channel(1); + let (sink_tx, sink_rx) = mpsc::sync_channel(1); + let (close_tx, close_rx) = mpsc::sync_channel(1); - std::thread::spawn(move || match create_sink(device) { - Ok((sink, stream)) => { - sink_tx.send(Ok(sink)).unwrap(); + std::thread::spawn(move || match create_sink(&host, device) { + Ok((sink, stream)) => { + sink_tx.send(Ok(sink)).unwrap(); - close_rx.recv().unwrap_err(); // This will fail as soon as the sender is dropped - debug!("drop rodio::OutputStream"); - drop(stream); - } - Err(e) => { - sink_tx.send(Err(e)).unwrap(); - } - }); - - // Instead of the second `unwrap`, better error handling could be introduced - let sink = sink_rx.recv().unwrap().unwrap(); - - debug!("Rodio sink was created"); - RodioSink { - rodio_sink: sink, - _close_tx: close_tx, + close_rx.recv().unwrap_err(); // This will fail as soon as the sender is dropped + debug!("drop rodio::OutputStream"); + drop(stream); } + Err(e) => { + sink_tx.send(Err(e)).unwrap(); + } + }); + + // Instead of the second `unwrap`, better error handling could be introduced + let sink = sink_rx.recv().unwrap().unwrap(); + + debug!("Rodio sink was created"); + RodioSink { + rodio_sink: sink, + _close_tx: close_tx, } }