librespot_playback/audio_backend/
mod.rs

1use crate::config::AudioFormat;
2use crate::convert::Converter;
3use crate::decoder::AudioPacket;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum SinkError {
8    #[error("Audio Sink Error Not Connected: {0}")]
9    NotConnected(String),
10    #[error("Audio Sink Error Connection Refused: {0}")]
11    ConnectionRefused(String),
12    #[error("Audio Sink Error On Write: {0}")]
13    OnWrite(String),
14    #[error("Audio Sink Error Invalid Parameters: {0}")]
15    InvalidParams(String),
16    #[error("Audio Sink Error Changing State: {0}")]
17    StateChange(String),
18}
19
20pub type SinkResult<T> = Result<T, SinkError>;
21
22pub trait Open {
23    fn open(_: Option<String>, format: AudioFormat) -> Self;
24}
25
26pub trait Sink {
27    fn start(&mut self) -> SinkResult<()> {
28        Ok(())
29    }
30    fn stop(&mut self) -> SinkResult<()> {
31        Ok(())
32    }
33    fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()>;
34}
35
36pub type SinkBuilder = fn(Option<String>, AudioFormat) -> Box<dyn Sink>;
37
38pub trait SinkAsBytes {
39    fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()>;
40}
41
42fn mk_sink<S: Sink + Open + 'static>(device: Option<String>, format: AudioFormat) -> Box<dyn Sink> {
43    Box::new(S::open(device, format))
44}
45
46// reuse code for various backends
47macro_rules! sink_as_bytes {
48    () => {
49        #[inline]
50        fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> {
51            use crate::convert::i24;
52            use zerocopy::IntoBytes;
53            match packet {
54                AudioPacket::Samples(samples) => match self.format {
55                    AudioFormat::F64 => self.write_bytes(samples.as_bytes()),
56                    AudioFormat::F32 => {
57                        let samples_f32: &[f32] = &converter.f64_to_f32(&samples);
58                        self.write_bytes(samples_f32.as_bytes())
59                    }
60                    AudioFormat::S32 => {
61                        let samples_s32: &[i32] = &converter.f64_to_s32(&samples);
62                        self.write_bytes(samples_s32.as_bytes())
63                    }
64                    AudioFormat::S24 => {
65                        let samples_s24: &[i32] = &converter.f64_to_s24(&samples);
66                        self.write_bytes(samples_s24.as_bytes())
67                    }
68                    AudioFormat::S24_3 => {
69                        let samples_s24_3: &[i24] = &converter.f64_to_s24_3(&samples);
70                        self.write_bytes(samples_s24_3.as_bytes())
71                    }
72                    AudioFormat::S16 => {
73                        let samples_s16: &[i16] = &converter.f64_to_s16(&samples);
74                        self.write_bytes(samples_s16.as_bytes())
75                    }
76                },
77                AudioPacket::Raw(samples) => self.write_bytes(&samples),
78            }
79        }
80    };
81}
82
83#[cfg(feature = "alsa-backend")]
84mod alsa;
85#[cfg(feature = "alsa-backend")]
86use self::alsa::AlsaSink;
87
88#[cfg(feature = "portaudio-backend")]
89mod portaudio;
90#[cfg(feature = "portaudio-backend")]
91use self::portaudio::PortAudioSink;
92
93#[cfg(feature = "pulseaudio-backend")]
94mod pulseaudio;
95#[cfg(feature = "pulseaudio-backend")]
96use self::pulseaudio::PulseAudioSink;
97
98#[cfg(feature = "jackaudio-backend")]
99mod jackaudio;
100#[cfg(feature = "jackaudio-backend")]
101use self::jackaudio::JackSink;
102
103#[cfg(feature = "gstreamer-backend")]
104mod gstreamer;
105#[cfg(feature = "gstreamer-backend")]
106use self::gstreamer::GstreamerSink;
107
108#[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))]
109mod rodio;
110#[cfg(feature = "rodio-backend")]
111use self::rodio::RodioSink;
112
113#[cfg(feature = "sdl-backend")]
114mod sdl;
115#[cfg(feature = "sdl-backend")]
116use self::sdl::SdlSink;
117
118mod pipe;
119use self::pipe::StdoutSink;
120
121mod subprocess;
122use self::subprocess::SubprocessSink;
123
124pub const BACKENDS: &[(&str, SinkBuilder)] = &[
125    #[cfg(feature = "rodio-backend")]
126    (RodioSink::NAME, rodio::mk_rodio), // default goes first
127    #[cfg(feature = "alsa-backend")]
128    (AlsaSink::NAME, mk_sink::<AlsaSink>),
129    #[cfg(feature = "portaudio-backend")]
130    (PortAudioSink::NAME, mk_sink::<PortAudioSink<'_>>),
131    #[cfg(feature = "pulseaudio-backend")]
132    (PulseAudioSink::NAME, mk_sink::<PulseAudioSink>),
133    #[cfg(feature = "jackaudio-backend")]
134    (JackSink::NAME, mk_sink::<JackSink>),
135    #[cfg(feature = "gstreamer-backend")]
136    (GstreamerSink::NAME, mk_sink::<GstreamerSink>),
137    #[cfg(feature = "rodiojack-backend")]
138    ("rodiojack", rodio::mk_rodiojack),
139    #[cfg(feature = "sdl-backend")]
140    (SdlSink::NAME, mk_sink::<SdlSink>),
141    (StdoutSink::NAME, mk_sink::<StdoutSink>),
142    (SubprocessSink::NAME, mk_sink::<SubprocessSink>),
143];
144
145pub fn find(name: Option<String>) -> Option<SinkBuilder> {
146    if let Some(name) = name {
147        BACKENDS
148            .iter()
149            .find(|backend| name == backend.0)
150            .map(|backend| backend.1)
151    } else {
152        BACKENDS.first().map(|backend| backend.1)
153    }
154}