use symphonia::core::audio::*;
use symphonia::core::units::Duration;
use libpulse_binding as pulse;
use libpulse_simple_binding as psimple;
#[allow(dead_code)]
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub enum AudioOutputError {
OpenStreamError,
PlayStreamError,
StreamClosedError,
}
pub type Result<T> = std::result::Result<T, AudioOutputError>;
pub trait AudioOutput {
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>;
fn flush(&mut self);
}
pub struct PulseAudioOutput {
pa: psimple::Simple,
sample_buf: RawSampleBuffer<f32>,
}
impl PulseAudioOutput {
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
let sample_buf = RawSampleBuffer::<f32>::new(duration, spec);
let pa_spec = pulse::sample::Spec {
format: pulse::sample::Format::FLOAT32NE,
channels: spec.channels.count() as u8,
rate: spec.rate,
};
assert!(pa_spec.is_valid());
let pa_ch_map = map_channels_to_pa_channelmap(spec.channels);
let pa_result = psimple::Simple::new(
None, "Symphonia Player", pulse::stream::Direction::Playback, None, "Music", &pa_spec, pa_ch_map.as_ref(), None, );
match pa_result {
Ok(pa) => Ok(Box::new(PulseAudioOutput { pa, sample_buf })),
Err(_err) => {
Err(AudioOutputError::OpenStreamError)
}
}
}
}
impl AudioOutput for PulseAudioOutput {
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
if decoded.frames() == 0 {
return Ok(());
}
self.sample_buf.copy_interleaved_ref(decoded);
match self.pa.write(self.sample_buf.as_bytes()) {
Err(_err) => {
Err(AudioOutputError::StreamClosedError)
}
_ => Ok(()),
}
}
fn flush(&mut self) {
let _ = self.pa.drain();
}
}
fn map_channels_to_pa_channelmap(channels: Channels) -> Option<pulse::channelmap::Map> {
let mut map: pulse::channelmap::Map = Default::default();
map.init();
map.set_len(channels.count() as u8);
let is_mono = channels.count() == 1;
for (i, channel) in channels.iter().enumerate() {
map.get_mut()[i] = match channel {
Channels::FRONT_LEFT if is_mono => pulse::channelmap::Position::Mono,
Channels::FRONT_LEFT => pulse::channelmap::Position::FrontLeft,
Channels::FRONT_RIGHT => pulse::channelmap::Position::FrontRight,
Channels::FRONT_CENTRE => pulse::channelmap::Position::FrontCenter,
Channels::REAR_LEFT => pulse::channelmap::Position::RearLeft,
Channels::REAR_CENTRE => pulse::channelmap::Position::RearCenter,
Channels::REAR_RIGHT => pulse::channelmap::Position::RearRight,
Channels::LFE1 => pulse::channelmap::Position::Lfe,
Channels::FRONT_LEFT_CENTRE => pulse::channelmap::Position::FrontLeftOfCenter,
Channels::FRONT_RIGHT_CENTRE => pulse::channelmap::Position::FrontRightOfCenter,
Channels::SIDE_LEFT => pulse::channelmap::Position::SideLeft,
Channels::SIDE_RIGHT => pulse::channelmap::Position::SideRight,
Channels::TOP_CENTRE => pulse::channelmap::Position::TopCenter,
Channels::TOP_FRONT_LEFT => pulse::channelmap::Position::TopFrontLeft,
Channels::TOP_FRONT_CENTRE => pulse::channelmap::Position::TopFrontCenter,
Channels::TOP_FRONT_RIGHT => pulse::channelmap::Position::TopFrontRight,
Channels::TOP_REAR_LEFT => pulse::channelmap::Position::TopRearLeft,
Channels::TOP_REAR_CENTRE => pulse::channelmap::Position::TopRearCenter,
Channels::TOP_REAR_RIGHT => pulse::channelmap::Position::TopRearRight,
_ => {
println!("failed to map channel {:?} to output", channel);
return None;
}
}
}
Some(map)
}