Merge pull request #548 from Lcchy/rodiojack-backend

Use rodio for the jackaudio backend
This commit is contained in:
Sasha Hilton 2021-02-11 02:24:48 +00:00 committed by GitHub
commit f483075b2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 610 additions and 361 deletions

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 | - | - | - |

830
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -63,6 +63,7 @@ alsa-backend = ["librespot-playback/alsa-backend"]
portaudio-backend = ["librespot-playback/portaudio-backend"] 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"]
rodiojack-backend = ["librespot-playback/rodiojack-backend"]
rodio-backend = ["librespot-playback/rodio-backend"] rodio-backend = ["librespot-playback/rodio-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

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

View file

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

View file

@ -34,15 +34,28 @@ mod jackaudio;
#[cfg(feature = "jackaudio-backend")] #[cfg(feature = "jackaudio-backend")]
use self::jackaudio::JackSink; use self::jackaudio::JackSink;
#[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(all(
feature = "rodiojack-backend",
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")
))]
use self::rodio::JackRodioSink;
#[cfg(feature = "gstreamer-backend")] #[cfg(feature = "gstreamer-backend")]
mod gstreamer; 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")] #[cfg(feature = "rodio-backend")]
use self::rodio::RodioSink; use self::rodio::RodioSink;
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
mod sdl; mod sdl;
#[cfg(feature = "sdl-backend")] #[cfg(feature = "sdl-backend")]
@ -63,6 +76,11 @@ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<dyn Sink>
("pulseaudio", mk_sink::<PulseAudioSink>), ("pulseaudio", mk_sink::<PulseAudioSink>),
#[cfg(feature = "jackaudio-backend")] #[cfg(feature = "jackaudio-backend")]
("jackaudio", mk_sink::<JackSink>), ("jackaudio", mk_sink::<JackSink>),
#[cfg(all(
feature = "rodiojack-backend",
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")
))]
("rodiojack", mk_sink::<JackRodioSink>),
#[cfg(feature = "gstreamer-backend")] #[cfg(feature = "gstreamer-backend")]
("gstreamer", mk_sink::<GstreamerSink>), ("gstreamer", mk_sink::<GstreamerSink>),
#[cfg(feature = "rodio-backend")] #[cfg(feature = "rodio-backend")]

View file

@ -12,6 +12,17 @@ pub struct RodioSink {
stream: rodio::OutputStream, stream: rodio::OutputStream,
} }
#[cfg(all(
feature = "rodiojack-backend",
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")
))]
pub struct JackRodioSink {
jackrodio_sink: rodio::Sink,
// We have to keep hold of this object, or the Sink can't play...
#[allow(dead_code)]
stream: rodio::OutputStream,
}
fn list_formats(ref device: &rodio::Device) { fn list_formats(ref device: &rodio::Device) {
let default_fmt = match device.default_output_config() { let default_fmt = match device.default_output_config() {
Ok(fmt) => cpal::SupportedStreamConfig::from(fmt), Ok(fmt) => cpal::SupportedStreamConfig::from(fmt),
@ -39,17 +50,19 @@ fn list_formats(ref device: &rodio::Device) {
} }
} }
fn list_outputs() { fn list_outputs(ref host: &cpal::Host) {
let default_device = get_default_device(); let default_device = get_default_device(host);
let default_device_name = default_device.name().expect("cannot get output name"); let default_device_name = default_device.name().expect("cannot get output name");
println!("Default Audio Device:\n {}", default_device_name); println!("Default Audio Device:\n {}", default_device_name);
list_formats(&default_device); list_formats(&default_device);
println!("Other Available Audio Devices:"); println!("Other Available Audio Devices:");
for device in cpal::default_host()
.output_devices() let found_devices = host.output_devices().expect(&format!(
.expect("cannot get list of output devices") "Cannot get list of output devices of Host: {:?}",
{ host.id()
));
for device in found_devices {
let device_name = device.name().expect("cannot get output name"); let device_name = device.name().expect("cannot get output name");
if device_name != default_device_name { if device_name != default_device_name {
println!(" {}", device_name); println!(" {}", device_name);
@ -58,23 +71,24 @@ fn list_outputs() {
} }
} }
fn get_default_device() -> rodio::Device { fn get_default_device(ref host: &cpal::Host) -> rodio::Device {
cpal::default_host() host.default_output_device()
.default_output_device()
.expect("no default output device available") .expect("no default output device available")
} }
fn match_device(device: Option<String>) -> rodio::Device { fn match_device(ref host: &cpal::Host, device: Option<String>) -> rodio::Device {
match device { match device {
Some(device_name) => { Some(device_name) => {
if device_name == "?".to_string() { if device_name == "?".to_string() {
list_outputs(); list_outputs(host);
exit(0) exit(0)
} }
for d in cpal::default_host()
.output_devices() let found_devices = host.output_devices().expect(&format!(
.expect("cannot get list of output devices") "Cannot get list of output devices of Host: {:?}",
{ host.id()
));
for d in found_devices {
if d.name().expect("cannot get output name") == device_name { if d.name().expect("cannot get output name") == device_name {
return d; return d;
} }
@ -82,18 +96,16 @@ fn match_device(device: Option<String>) -> rodio::Device {
println!("No output sink matching '{}' found.", device_name); println!("No output sink matching '{}' found.", device_name);
exit(0) exit(0)
} }
None => return get_default_device(), None => return get_default_device(host),
} }
} }
impl Open for RodioSink { impl Open for RodioSink {
fn open(device: Option<String>) -> RodioSink { fn open(device: Option<String>) -> RodioSink {
debug!( let host = cpal::default_host();
"Using rodio sink with cpal host: {:?}", debug!("Using rodio sink with cpal host: {:?}", host.id());
cpal::default_host().id()
);
let rodio_device = match_device(device); let rodio_device = match_device(&host, device);
debug!("Using cpal device"); debug!("Using cpal device");
let stream = rodio::OutputStream::try_from_device(&rodio_device) let stream = rodio::OutputStream::try_from_device(&rodio_device)
.expect("Couldn't open output stream."); .expect("Couldn't open output stream.");
@ -108,6 +120,36 @@ impl Open for RodioSink {
} }
} }
#[cfg(all(
feature = "rodiojack-backend",
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")
))]
impl Open for JackRodioSink {
fn open(device: Option<String>) -> JackRodioSink {
let host = cpal::host_from_id(
cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect("Jack Host not found"),
)
.expect("Jack Host not found");
debug!("Using jack rodio sink with cpal Jack host");
let rodio_device = match_device(&host, device);
debug!("Using cpal device");
let stream = rodio::OutputStream::try_from_device(&rodio_device)
.expect("Couldn't open output stream.");
debug!("Using jack rodio stream");
let sink = rodio::Sink::try_new(&stream.1).expect("Couldn't create output sink.");
debug!("Using jack rodio sink");
JackRodioSink {
jackrodio_sink: sink,
stream: stream.0,
}
}
}
impl Sink for RodioSink { impl Sink for RodioSink {
fn start(&mut self) -> io::Result<()> { fn start(&mut self) -> io::Result<()> {
// More similar to an "unpause" than "play". Doesn't undo "stop". // More similar to an "unpause" than "play". Doesn't undo "stop".
@ -136,3 +178,36 @@ impl Sink for RodioSink {
Ok(()) Ok(())
} }
} }
#[cfg(all(
feature = "rodiojack-backend",
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")
))]
impl Sink for JackRodioSink {
fn start(&mut self) -> io::Result<()> {
// More similar to an "unpause" than "play". Doesn't undo "stop".
// self.rodio_sink.play();
Ok(())
}
fn stop(&mut self) -> io::Result<()> {
// This will immediately stop playback, but the sink is then unusable.
// We just have to let the current buffer play till the end.
// self.rodio_sink.stop();
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
let source = rodio::buffer::SamplesBuffer::new(2, 44100, data);
self.jackrodio_sink.append(source);
// Chunk sizes seem to be about 256 to 3000 ish items long.
// Assuming they're on average 1628 then a half second buffer is:
// 44100 elements --> about 27 chunks
while self.jackrodio_sink.len() > 26 {
// sleep and wait for rodio to drain a bit
thread::sleep(time::Duration::from_millis(10));
}
Ok(())
}
}