use anyhow::{Context, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{FromSample, Sample, SampleFormat}; use tracing::{error, info, trace, warn}; use tokio::sync::mpsc::Receiver; use crate::task_msg::{PcmFrameMessage, PcmSampleMessage}; pub fn audio_transmitter_main( mut rx_for_wire: Receiver, cpal_output: &cpal::Device, ) -> Result { trace!("audio_transmitter started"); let cpal_output_config = cpal_output.supported_output_configs() .context("could not find a suitable audio output configuration. are you root, trying to use a normal user's audio stack? try setting the capability CAP_NET_ADMIN=ep on this binary, and running as the user who is running the audio stack.")? .max_by_key(|cfg| { if cfg.channels() > 1 { return -999; } match cfg.sample_format() { SampleFormat::U8 => 10, SampleFormat::I8 => 11, SampleFormat::U16 => 20, SampleFormat::I16 => 21, SampleFormat::U32 => 30, SampleFormat::I32 => 31, SampleFormat::F32 => 40, SampleFormat::F64 => 50, SampleFormat::U64 => 60, SampleFormat::I64 => 61, x => { warn!("Backend offered unknown sample format {x}",); -999 } } }).expect("output device has no supported configurations available") .with_max_sample_rate(); info!( "output samples are {} @ {}hz on {} channels", &cpal_output_config.sample_format(), &cpal_output_config.sample_rate().0, &cpal_output_config.channels(), ); let failed_pcm_give = move |e| { warn!("dropped some PCM data on output stream?! {}", e); }; let output_stream = match cpal_output_config.sample_format() { cpal::SampleFormat::U8 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::I8 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::U16 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::I16 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::U32 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::I32 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::F32 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::F64 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::U64 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, cpal::SampleFormat::I64 => cpal_output .build_output_stream( &cpal_output_config.into(), move |pcm, _: &_| give_output_pcm::(pcm, &mut rx_for_wire), failed_pcm_give, None, ) .context("failed to build output stream")?, sample_format => Err(anyhow::Error::msg(format!( "unsupported sample format '{}'! please ask developers to add it", sample_format )))?, }; output_stream .play() .context("failed to start output audio stream")?; Ok(output_stream) } fn give_output_pcm(pcm: &mut [T], channel: &mut Receiver) where T: Sample + std::fmt::Debug + FromSample + FromSample, { trace!("ALSA demands {} samples...", pcm.len()); for outgoing in pcm.iter_mut() { *outgoing = match channel.try_recv() { Ok(sample) => sample.to_sample::(), Err(_) => { warn!("rx_for_wire has no samples for us! carrier dead!"); warn!("are we able to produce samples quickly enough? CPU OK?"); T::EQUILIBRIUM } }; } trace!("gave ALSA the samples"); }