2016-03-20 19:16:32 +00:00
|
|
|
use super::{Open, Sink};
|
2021-01-07 06:42:38 +00:00
|
|
|
use crate::audio::AudioPacket;
|
2020-11-27 05:08:16 +00:00
|
|
|
use libpulse_binding::{self as pulse, stream::Direction};
|
2020-11-27 01:06:25 +00:00
|
|
|
use libpulse_simple_binding::Simple;
|
2018-02-26 01:50:41 +00:00
|
|
|
use std::io;
|
2016-03-20 19:16:32 +00:00
|
|
|
|
2020-11-27 01:06:25 +00:00
|
|
|
const APP_NAME: &str = "librespot";
|
|
|
|
const STREAM_NAME: &str = "Spotify endpoint";
|
2017-11-27 19:01:30 +00:00
|
|
|
|
2020-11-27 01:06:25 +00:00
|
|
|
pub struct PulseAudioSink {
|
|
|
|
s: Option<Simple>,
|
|
|
|
ss: pulse::sample::Spec,
|
|
|
|
device: Option<String>,
|
2017-11-27 19:01:30 +00:00
|
|
|
}
|
|
|
|
|
2016-03-20 19:16:32 +00:00
|
|
|
impl Open for PulseAudioSink {
|
2018-02-26 01:50:41 +00:00
|
|
|
fn open(device: Option<String>) -> PulseAudioSink {
|
2016-07-06 09:54:46 +00:00
|
|
|
debug!("Using PulseAudio sink");
|
|
|
|
|
2020-11-27 01:06:25 +00:00
|
|
|
let ss = pulse::sample::Spec {
|
|
|
|
format: pulse::sample::Format::S16le,
|
2016-03-20 19:16:32 +00:00
|
|
|
channels: 2, // stereo
|
2018-02-26 01:50:41 +00:00
|
|
|
rate: 44100,
|
2016-03-20 19:16:32 +00:00
|
|
|
};
|
2020-11-27 01:06:25 +00:00
|
|
|
debug_assert!(ss.is_valid());
|
2016-03-20 19:16:32 +00:00
|
|
|
|
2017-01-14 21:22:33 +00:00
|
|
|
PulseAudioSink {
|
2020-11-27 01:06:25 +00:00
|
|
|
s: None,
|
2017-01-14 21:22:33 +00:00
|
|
|
ss: ss,
|
2020-11-27 01:06:25 +00:00
|
|
|
device: device,
|
2017-01-14 21:22:33 +00:00
|
|
|
}
|
2016-03-20 19:16:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sink for PulseAudioSink {
|
2016-08-01 19:20:17 +00:00
|
|
|
fn start(&mut self) -> io::Result<()> {
|
2020-11-27 01:06:25 +00:00
|
|
|
if self.s.is_some() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let device = self.device.as_ref().map(|s| (*s).as_str());
|
|
|
|
let result = Simple::new(
|
2020-11-27 05:08:16 +00:00
|
|
|
None, // Use the default server.
|
|
|
|
APP_NAME, // Our application's name.
|
|
|
|
Direction::Playback, // Direction.
|
|
|
|
device, // Our device (sink) name.
|
|
|
|
STREAM_NAME, // Description of our stream.
|
|
|
|
&self.ss, // Our sample format.
|
|
|
|
None, // Use default channel map.
|
|
|
|
None, // Use default buffering attributes.
|
2020-11-27 01:06:25 +00:00
|
|
|
);
|
|
|
|
match result {
|
|
|
|
Ok(s) => {
|
|
|
|
self.s = Some(s);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => Err(io::Error::new(
|
2018-02-26 01:50:41 +00:00
|
|
|
io::ErrorKind::ConnectionRefused,
|
2020-11-27 01:06:25 +00:00
|
|
|
e.to_string().unwrap(),
|
|
|
|
)),
|
2017-01-14 21:22:33 +00:00
|
|
|
}
|
2016-03-20 19:16:32 +00:00
|
|
|
}
|
|
|
|
|
2016-08-01 19:20:17 +00:00
|
|
|
fn stop(&mut self) -> io::Result<()> {
|
2020-11-27 01:06:25 +00:00
|
|
|
self.s = None;
|
2016-03-20 19:16:32 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-07 06:42:38 +00:00
|
|
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
2020-11-27 01:06:25 +00:00
|
|
|
if let Some(s) = &self.s {
|
2021-01-29 02:01:38 +00:00
|
|
|
// SAFETY: An i16 consists of two bytes, so that the given slice can be interpreted
|
|
|
|
// as a byte array of double length. Each byte pointer is validly aligned, and so
|
|
|
|
// is the newly created slice.
|
2021-01-07 06:42:38 +00:00
|
|
|
let d: &[u8] = unsafe {
|
|
|
|
std::slice::from_raw_parts(
|
|
|
|
packet.samples().as_ptr() as *const u8,
|
|
|
|
packet.samples().len() * 2,
|
|
|
|
)
|
|
|
|
};
|
2020-11-27 01:06:25 +00:00
|
|
|
|
|
|
|
match s.write(d) {
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
Err(e) => Err(io::Error::new(
|
|
|
|
io::ErrorKind::BrokenPipe,
|
|
|
|
e.to_string().unwrap(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
} else {
|
2018-02-26 01:50:41 +00:00
|
|
|
Err(io::Error::new(
|
|
|
|
io::ErrorKind::NotConnected,
|
|
|
|
"Not connected to pulseaudio",
|
|
|
|
))
|
2017-11-27 19:01:30 +00:00
|
|
|
}
|
2016-03-20 19:16:32 +00:00
|
|
|
}
|
|
|
|
}
|