sang/src/audio_transmitter/mod.rs

158 lines
6 KiB
Rust

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<PcmSampleMessage>,
cpal_output: &cpal::Device,
) -> Result<cpal::Stream> {
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::<u8>(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::<i8>(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::<u16>(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::<i16>(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::<u32>(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::<i32>(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::<f32>(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::<f64>(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::<u64>(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::<i64>(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<T>(pcm: &mut [T], channel: &mut Receiver<PcmSampleMessage>)
where
T: Sample + std::fmt::Debug + FromSample<i64> + FromSample<f64>,
{
trace!("ALSA demands {} samples...", pcm.len());
for outgoing in pcm.iter_mut() {
*outgoing = match channel.try_recv() {
Ok(sample) => sample.to_sample::<T>(),
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");
}