mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge pull request #548 from Lcchy/rodiojack-backend
Use rodio for the jackaudio backend
This commit is contained in:
commit
f483075b2c
7 changed files with 610 additions and 361 deletions
|
@ -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
830
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"]
|
||||||
|
|
|
@ -54,6 +54,7 @@ ALSA
|
||||||
PortAudio
|
PortAudio
|
||||||
PulseAudio
|
PulseAudio
|
||||||
JACK
|
JACK
|
||||||
|
JACK over Rodio
|
||||||
SDL
|
SDL
|
||||||
Pipe
|
Pipe
|
||||||
```
|
```
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue