use std::result;
use symphonia::core::audio::{AudioBufferRef, SignalSpec};
use symphonia::core::units::Duration;
pub trait AudioOutput {
fn write(&mut self, decoded: AudioBufferRef<'_>, volume: f32) -> Result<()>;
fn flush(&mut self);
}
#[allow(dead_code)]
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub enum AudioOutputError {
OpenStreamError,
PlayStreamError,
StreamClosedError,
}
pub type Result<T> = result::Result<T, AudioOutputError>;
#[cfg(target_os = "linux")]
mod pulseaudio {
use libpulse_binding as pulse;
use libpulse_simple_binding as psimple;
use symphonia::core::audio::*;
use symphonia::core::units::Duration;
use super::{AudioOutput, AudioOutputError, Result};
fn vf_to_u8(v: &[f32]) -> &[u8] {
unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4) }
}
pub struct PulseAudioOutput {
pa: psimple::Simple,
sample_buf: SampleBuffer<f32>,
}
impl PulseAudioOutput {
pub fn try_open(
spec: SignalSpec,
duration: Duration,
app_name: &str,
) -> Result<Box<dyn AudioOutput>> {
let sample_buf = SampleBuffer::<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, app_name, 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) => {
eprintln!("audio output stream open error: {}", err);
Err(AudioOutputError::OpenStreamError)
}
}
}
}
impl AudioOutput for PulseAudioOutput {
fn write(&mut self, decoded: AudioBufferRef<'_>, volume: f32) -> Result<()> {
if decoded.frames() == 0 {
return Ok(());
}
self.sample_buf.copy_interleaved_ref(decoded);
let mut samples: Vec<f32> = self.sample_buf.samples().to_vec();
for sample in samples.iter_mut() {
*sample *= volume;
}
match self.pa.write(vf_to_u8(&samples)) {
Err(err) => {
eprintln!("audio output stream write error: {}", 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,
_ => {
eprintln!("failed to map channel {:?} to output", channel);
return None;
}
}
}
Some(map)
}
}
#[cfg(not(target_os = "linux"))]
mod cpal {
use cpal;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use dasp::Sample;
use rb::*;
use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec};
use symphonia::core::conv::ConvertibleSample;
use symphonia::core::units::Duration;
use super::{AudioOutput, AudioOutputError, Result};
pub struct CpalAudioOutput;
trait AudioOutputSample:
cpal::Sample + ConvertibleSample + RawSample + std::marker::Send + 'static
{
}
impl AudioOutputSample for f32 {}
impl AudioOutputSample for i16 {}
impl AudioOutputSample for u16 {}
impl CpalAudioOutput {
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
let host = cpal::default_host();
let device = match host.default_output_device() {
Some(device) => device,
_ => {
eprintln!("Failed to get default audio output device");
return Err(AudioOutputError::OpenStreamError);
}
};
let config = match device.default_output_config() {
Ok(config) => config,
Err(err) => {
eprintln!("Failed to get default audio output device config: {}", err);
return Err(AudioOutputError::OpenStreamError);
}
};
match config.sample_format() {
cpal::SampleFormat::F32 => {
CpalAudioOutputImpl::<f32>::try_open(spec, duration, &device)
}
cpal::SampleFormat::I16 => {
CpalAudioOutputImpl::<i16>::try_open(spec, duration, &device)
}
cpal::SampleFormat::U16 => {
CpalAudioOutputImpl::<u16>::try_open(spec, duration, &device)
}
}
}
}
struct CpalAudioOutputImpl<T: AudioOutputSample>
where
T: AudioOutputSample,
{
ring_buf_producer: rb::Producer<T>,
sample_buf: SampleBuffer<T>,
stream: cpal::Stream,
}
impl<T: AudioOutputSample> CpalAudioOutputImpl<T> {
pub fn try_open(
spec: SignalSpec,
duration: Duration,
device: &cpal::Device,
) -> Result<Box<dyn AudioOutput>> {
let num_channels = spec.channels.count();
let config = cpal::StreamConfig {
channels: num_channels as cpal::ChannelCount,
sample_rate: cpal::SampleRate(spec.rate),
buffer_size: cpal::BufferSize::Default,
};
let ring_len = ((200 * spec.rate as usize) / 1000) * num_channels;
let ring_buf = SpscRb::new(ring_len);
let (ring_buf_producer, ring_buf_consumer) = (ring_buf.producer(), ring_buf.consumer());
let stream_result = device.build_output_stream(
&config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
let written = ring_buf_consumer.read(data).unwrap_or(0);
data[written..].iter_mut().for_each(|s| *s = T::MID);
},
move |err| eprintln!("audio output error: {}", err),
);
if let Err(err) = stream_result {
eprintln!("audio output stream open error: {}", err);
return Err(AudioOutputError::OpenStreamError);
}
let stream = stream_result.unwrap();
if let Err(err) = stream.play() {
eprintln!("audio output stream play error: {}", err);
return Err(AudioOutputError::PlayStreamError);
}
let sample_buf = SampleBuffer::<T>::new(duration, spec);
Ok(Box::new(CpalAudioOutputImpl {
ring_buf_producer,
sample_buf,
stream,
}))
}
}
impl<T: AudioOutputSample> AudioOutput for CpalAudioOutputImpl<T> {
fn write(&mut self, decoded: AudioBufferRef<'_>, volume: f32) -> Result<()> {
if decoded.frames() == 0 {
return Ok(());
}
self.sample_buf.copy_interleaved_ref(decoded);
let mut samples: Vec<T> = self.sample_buf.samples().to_vec();
for sample in samples.iter_mut() {
sample.to_f32().mul_amp(volume);
}
while let Some(written) = self.ring_buf_producer.write_blocking(&samples) {
samples = (&samples[written..]).to_vec();
}
Ok(())
}
fn flush(&mut self) {
let _ = self.stream.pause();
}
}
}
#[cfg(target_os = "linux")]
pub fn try_open(
spec: SignalSpec,
duration: Duration,
app_name: &str,
) -> Result<Box<dyn AudioOutput>> {
pulseaudio::PulseAudioOutput::try_open(spec, duration, app_name)
}
#[cfg(not(target_os = "linux"))]
pub fn try_open(
spec: SignalSpec,
duration: Duration,
_app_name: &str,
) -> Result<Box<dyn AudioOutput>> {
cpal::CpalAudioOutput::try_open(spec, duration)
}