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.
This commit is contained in:
johannesd3 2021-02-23 15:05:02 +01:00 committed by Johannesd3
parent 678d1777fd
commit c0942f14e8
8 changed files with 65 additions and 44 deletions

View file

@ -78,6 +78,7 @@ jobs:
- run: cargo build --locked --no-default-features --features "portaudio-backend" - 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 "pulseaudio-backend"
- run: cargo build --locked --no-default-features --features "jackaudio-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 "rodio-backend"
- run: cargo build --locked --no-default-features --features "sdl-backend" - run: cargo build --locked --no-default-features --features "sdl-backend"
- run: cargo build --locked --no-default-features --features "gstreamer-backend" - run: cargo build --locked --no-default-features --features "gstreamer-backend"

View file

@ -46,6 +46,7 @@ Depending on the chosen backend, specific development libraries are required.
|PortAudio | `portaudio19-dev` | `portaudio-devel` | `portaudio` | |PortAudio | `portaudio19-dev` | `portaudio-devel` | `portaudio` |
|PulseAudio | `libpulse-dev` | `pulseaudio-libs-devel` | | |PulseAudio | `libpulse-dev` | `pulseaudio-libs-devel` | |
|JACK | `libjack-dev` | `jack-audio-connection-kit-devel` | | |JACK | `libjack-dev` | `jack-audio-connection-kit-devel` | |
|JACK over Rodio | `libjack-dev` | `jack-audio-connection-kit-devel` | - |
|SDL | `libsdl2-dev` | `SDL2-devel` | | |SDL | `libsdl2-dev` | `SDL2-devel` | |
|Pipe | - | - | - | |Pipe | - | - | - |

1
Cargo.lock generated
View file

@ -433,6 +433,7 @@ dependencies = [
"alsa", "alsa",
"core-foundation-sys", "core-foundation-sys",
"coreaudio-rs", "coreaudio-rs",
"jack",
"jni 0.17.0", "jni 0.17.0",
"js-sys", "js-sys",
"lazy_static", "lazy_static",

View file

@ -66,6 +66,7 @@ portaudio-backend = ["librespot-playback/portaudio-backend"]
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"] pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
jackaudio-backend = ["librespot-playback/jackaudio-backend"] jackaudio-backend = ["librespot-playback/jackaudio-backend"]
rodio-backend = ["librespot-playback/rodio-backend"] rodio-backend = ["librespot-playback/rodio-backend"]
rodiojack-backend = ["librespot-playback/rodiojack-backend"]
sdl-backend = ["librespot-playback/sdl-backend"] sdl-backend = ["librespot-playback/sdl-backend"]
gstreamer-backend = ["librespot-playback/gstreamer-backend"] gstreamer-backend = ["librespot-playback/gstreamer-backend"]

View file

@ -56,6 +56,7 @@ ALSA
PortAudio PortAudio
PulseAudio PulseAudio
JACK JACK
JACK over Rodio
SDL SDL
Pipe Pipe
``` ```

View file

@ -46,5 +46,6 @@ portaudio-backend = ["portaudio-rs"]
pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"] pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"]
jackaudio-backend = ["jack"] jackaudio-backend = ["jack"]
rodio-backend = ["rodio", "cpal", "thiserror"] rodio-backend = ["rodio", "cpal", "thiserror"]
rodiojack-backend = ["rodio", "cpal/jack", "thiserror"]
sdl-backend = ["sdl2"] sdl-backend = ["sdl2"]
gstreamer-backend = ["gstreamer", "gstreamer-app", "glib", "zerocopy"] gstreamer-backend = ["gstreamer", "gstreamer-app", "glib", "zerocopy"]

View file

@ -42,10 +42,9 @@ mod gstreamer;
#[cfg(feature = "gstreamer-backend")] #[cfg(feature = "gstreamer-backend")]
use self::gstreamer::GstreamerSink; use self::gstreamer::GstreamerSink;
#[cfg(feature = "rodio-backend")] #[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))]
mod rodio; mod rodio;
#[cfg(feature = "rodio-backend")]
use self::rodio::RodioSink;
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
mod sdl; mod sdl;
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
@ -69,7 +68,9 @@ pub const BACKENDS: &'static [(&'static str, SinkBuilder)] = &[
#[cfg(feature = "gstreamer-backend")] #[cfg(feature = "gstreamer-backend")]
("gstreamer", mk_sink::<GstreamerSink>), ("gstreamer", mk_sink::<GstreamerSink>),
#[cfg(feature = "rodio-backend")] #[cfg(feature = "rodio-backend")]
("rodio", mk_sink::<RodioSink>), ("rodio", rodio::mk_rodio),
#[cfg(feature = "rodiojack-backend")]
("rodiojack", rodio::mk_rodiojack),
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
("sdl", mk_sink::<SdlSink>), ("sdl", mk_sink::<SdlSink>),
("pipe", mk_sink::<StdoutSink>), ("pipe", mk_sink::<StdoutSink>),

View file

@ -5,9 +5,28 @@ use std::{io, thread, time};
use cpal::traits::{DeviceTrait, HostTrait}; use cpal::traits::{DeviceTrait, HostTrait};
use thiserror::Error; use thiserror::Error;
use super::{Open, Sink}; use super::Sink;
use crate::audio::AudioPacket; 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<String>) -> Box<dyn Sink + Send> {
Box::new(open(cpal::default_host(), device))
}
#[cfg(feature = "rodiojack-backend")]
pub fn mk_rodiojack(device: Option<String>) -> Box<dyn Sink + Send> {
Box::new(open(
cpal::host_from_id(cpal::HostId::Jack).unwrap(),
device,
))
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum RodioError { pub enum RodioError {
#[error("Rodio: no device available")] #[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; 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(); default_device_name = default_device.name().ok();
println!( println!(
"Default Audio Device:\n {}", "Default Audio Device:\n {}",
@ -77,7 +96,7 @@ fn list_outputs() -> Result<(), cpal::DevicesError> {
warn!("No default device was found"); warn!("No default device was found");
} }
for device in cpal::default_host().output_devices()? { for device in host.output_devices()? {
match device.name() { match device.name() {
Ok(name) if Some(&name) == default_device_name.as_ref() => (), Ok(name) if Some(&name) == default_device_name.as_ref() => (),
Ok(name) => { Ok(name) => {
@ -95,14 +114,13 @@ fn list_outputs() -> Result<(), cpal::DevicesError> {
Ok(()) Ok(())
} }
fn get_default_device() -> Option<rodio::Device> { fn create_sink(
cpal::default_host().default_output_device() host: &cpal::Host,
} device: Option<String>,
) -> Result<(rodio::Sink, rodio::OutputStream), RodioError> {
fn create_sink(device: Option<String>) -> Result<(rodio::Sink, rodio::OutputStream), RodioError> {
let rodio_device = match device { let rodio_device = match device {
Some(ask) if &ask == "?" => { Some(ask) if &ask == "?" => {
let exit_code = match list_outputs() { let exit_code = match list_outputs(host) {
Ok(()) => 0, Ok(()) => 0,
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
@ -112,12 +130,13 @@ fn create_sink(device: Option<String>) -> Result<(rodio::Sink, rodio::OutputStre
exit(exit_code) exit(exit_code)
} }
Some(device_name) => { Some(device_name) => {
cpal::default_host() host.output_devices()?
.output_devices()?
.find(|d| d.name().ok().map_or(false, |name| name == device_name)) // Ignore devices for which getting name fails .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))? .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(); let name = rodio_device.name().ok();
@ -131,37 +150,32 @@ fn create_sink(device: Option<String>) -> Result<(rodio::Sink, rodio::OutputStre
Ok((sink, stream)) Ok((sink, stream))
} }
impl Open for RodioSink { pub fn open(host: cpal::Host, device: Option<String>) -> RodioSink {
fn open(device: Option<String>) -> RodioSink { debug!("Using rodio sink with cpal host: {}", host.id().name());
debug!(
"Using rodio sink with cpal host: {:?}",
cpal::default_host().id().name()
);
let (sink_tx, sink_rx) = mpsc::sync_channel(1); let (sink_tx, sink_rx) = mpsc::sync_channel(1);
let (close_tx, close_rx) = mpsc::sync_channel(1); let (close_tx, close_rx) = mpsc::sync_channel(1);
std::thread::spawn(move || match create_sink(device) { std::thread::spawn(move || match create_sink(&host, device) {
Ok((sink, stream)) => { Ok((sink, stream)) => {
sink_tx.send(Ok(sink)).unwrap(); sink_tx.send(Ok(sink)).unwrap();
close_rx.recv().unwrap_err(); // This will fail as soon as the sender is dropped close_rx.recv().unwrap_err(); // This will fail as soon as the sender is dropped
debug!("drop rodio::OutputStream"); debug!("drop rodio::OutputStream");
drop(stream); 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,
} }
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,
} }
} }