X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/2fb25de49daca6ddff6f5af13bcf7c314aafafb3..64599440f241f7bb897a95b72ed7130231966518:/m17app/src/soundcard.rs diff --git a/m17app/src/soundcard.rs b/m17app/src/soundcard.rs index f89d9f4..dc08036 100644 --- a/m17app/src/soundcard.rs +++ b/m17app/src/soundcard.rs @@ -8,12 +8,13 @@ use std::{ use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, - SampleFormat, SampleRate, Stream, + BuildStreamError, DevicesError, PlayStreamError, SampleFormat, SampleRate, Stream, StreamError, + SupportedStreamConfigsError, }; +use thiserror::Error; -use crate::{ - error::{M17Error, SoundmodemError}, - soundmodem::{InputSource, OutputBuffer, OutputSink, SoundmodemEvent}, +use crate::soundmodem::{ + InputSource, OutputBuffer, OutputSink, SoundmodemErrorSender, SoundmodemEvent, }; pub struct Soundcard { @@ -21,14 +22,14 @@ pub struct Soundcard { } impl Soundcard { - pub fn new>(card_name: S) -> Result { + pub fn new>(card_name: S) -> Result { let (card_tx, card_rx) = sync_channel(128); let (setup_tx, setup_rx) = sync_channel(1); spawn_soundcard_worker(card_rx, setup_tx, card_name.into()); match setup_rx.recv() { Ok(Ok(())) => Ok(Self { event_tx: card_tx }), Ok(Err(e)) => Err(e), - Err(_) => Err(M17Error::SoundcardInit), + Err(_) => Err(SoundcardError::SoundcardInit), } } @@ -104,11 +105,13 @@ enum SoundcardEvent { SetTxInverted(bool), StartInput { samples: SyncSender, + errors: SoundmodemErrorSender, }, CloseInput, StartOutput { event_tx: SyncSender, buffer: Arc>, + errors: SoundmodemErrorSender, }, CloseOutput, } @@ -118,12 +121,14 @@ pub struct SoundcardInputSource { } impl InputSource for SoundcardInputSource { - fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { - Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?) + fn start(&self, samples: SyncSender, errors: SoundmodemErrorSender) { + let _ = self + .event_tx + .send(SoundcardEvent::StartInput { samples, errors }); } - fn close(&self) -> Result<(), SoundmodemError> { - Ok(self.event_tx.send(SoundcardEvent::CloseInput)?) + fn close(&self) { + let _ = self.event_tx.send(SoundcardEvent::CloseInput); } } @@ -136,20 +141,23 @@ impl OutputSink for SoundcardOutputSink { &self, event_tx: SyncSender, buffer: Arc>, - ) -> Result<(), SoundmodemError> { - Ok(self - .event_tx - .send(SoundcardEvent::StartOutput { event_tx, buffer })?) + errors: SoundmodemErrorSender, + ) { + let _ = self.event_tx.send(SoundcardEvent::StartOutput { + event_tx, + buffer, + errors, + }); } - fn close(&self) -> Result<(), SoundmodemError> { - Ok(self.event_tx.send(SoundcardEvent::CloseOutput)?) + fn close(&self) { + let _ = self.event_tx.send(SoundcardEvent::CloseOutput); } } fn spawn_soundcard_worker( event_rx: Receiver, - setup_tx: SyncSender>, + setup_tx: SyncSender>, card_name: String, ) { std::thread::spawn(move || { @@ -159,7 +167,7 @@ fn spawn_soundcard_worker( .unwrap() .find(|d| d.name().unwrap() == card_name) else { - let _ = setup_tx.send(Err(M17Error::SoundcardNotFound(card_name))); + let _ = setup_tx.send(Err(SoundcardError::CardNotFound(card_name))); return; }; @@ -173,81 +181,119 @@ fn spawn_soundcard_worker( match ev { SoundcardEvent::SetRxInverted(inv) => rx_inverted = inv, SoundcardEvent::SetTxInverted(inv) => tx_inverted = inv, - SoundcardEvent::StartInput { samples } => { - let mut input_configs = device.supported_input_configs().unwrap(); - let input_config = input_configs + SoundcardEvent::StartInput { samples, errors } => { + let mut input_configs = match device.supported_input_configs() { + Ok(c) => c, + Err(e) => { + errors.send_error(SoundcardError::SupportedConfigs(e)); + continue; + } + }; + let input_config = match input_configs .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16) - .unwrap() - .with_sample_rate(SampleRate(48000)); - let stream = device - .build_input_stream( - &input_config.into(), - move |data: &[i16], _info: &cpal::InputCallbackInfo| { - let out: Vec = data - .iter() - .map(|s| if rx_inverted { s.saturating_neg() } else { *s }) - .collect(); - let _ = - samples.try_send(SoundmodemEvent::BasebandInput(out.into())); - }, - |e| { - // TODO: abort? - log::debug!("error occurred in soundcard input: {e:?}"); - }, - None, - ) - .unwrap(); - stream.play().unwrap(); + { + Some(c) => c, + None => { + errors.send_error(SoundcardError::NoValidConfigAvailable); + continue; + } + }; + let input_config = input_config.with_sample_rate(SampleRate(48000)); + let errors_1 = errors.clone(); + let stream = match device.build_input_stream( + &input_config.into(), + move |data: &[i16], _info: &cpal::InputCallbackInfo| { + let out: Vec = data + .iter() + .map(|s| if rx_inverted { s.saturating_neg() } else { *s }) + .collect(); + let _ = samples.try_send(SoundmodemEvent::BasebandInput(out.into())); + }, + move |e| { + errors_1.send_error(SoundcardError::Stream(e)); + }, + None, + ) { + Ok(s) => s, + Err(e) => { + errors.send_error(SoundcardError::StreamBuild(e)); + continue; + } + }; + if let Err(e) = stream.play() { + errors.send_error(SoundcardError::StreamPlay(e)); + continue; + } input_stream = Some(stream); } SoundcardEvent::CloseInput => { let _ = input_stream.take(); } - SoundcardEvent::StartOutput { event_tx, buffer } => { - let mut output_configs = device.supported_output_configs().unwrap(); - // TODO: more error handling - let output_config = output_configs + SoundcardEvent::StartOutput { + event_tx, + buffer, + errors, + } => { + let mut output_configs = match device.supported_output_configs() { + Ok(c) => c, + Err(e) => { + errors.send_error(SoundcardError::SupportedConfigs(e)); + continue; + } + }; + let output_config = match output_configs .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16) - .unwrap() - .with_sample_rate(SampleRate(48000)); - let stream = device - .build_output_stream( - &output_config.into(), - move |data: &mut [i16], info: &cpal::OutputCallbackInfo| { - let mut taken = 0; - let ts = info.timestamp(); - let latency = ts - .playback - .duration_since(&ts.callback) - .unwrap_or(Duration::ZERO); - let mut buffer = buffer.write().unwrap(); - buffer.latency = latency; - for out in data.iter_mut() { - if let Some(s) = buffer.samples.pop_front() { - *out = if tx_inverted { s.saturating_neg() } else { s }; - taken += 1; - } else if buffer.idling { - *out = 0; - } else { - log::debug!("output soundcard had underrun"); - let _ = event_tx.send(SoundmodemEvent::OutputUnderrun); - break; - } + { + Some(c) => c, + None => { + errors.send_error(SoundcardError::NoValidConfigAvailable); + continue; + } + }; + let output_config = output_config.with_sample_rate(SampleRate(48000)); + let errors_1 = errors.clone(); + let stream = match device.build_output_stream( + &output_config.into(), + move |data: &mut [i16], info: &cpal::OutputCallbackInfo| { + let mut taken = 0; + let ts = info.timestamp(); + let latency = ts + .playback + .duration_since(&ts.callback) + .unwrap_or(Duration::ZERO); + let mut buffer = buffer.write().unwrap(); + buffer.latency = latency; + for out in data.iter_mut() { + if let Some(s) = buffer.samples.pop_front() { + *out = if tx_inverted { s.saturating_neg() } else { s }; + taken += 1; + } else if buffer.idling { + *out = 0; + } else { + let _ = event_tx.send(SoundmodemEvent::OutputUnderrun); + break; } - //debug!("latency is {} ms, taken {taken}", latency.as_millis()); - let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer { - len: taken, - timestamp: Instant::now(), - }); - }, - |e| { - // TODO: abort? - log::debug!("error occurred in soundcard output: {e:?}"); - }, - None, - ) - .unwrap(); - stream.play().unwrap(); + } + let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer { + len: taken, + timestamp: Instant::now(), + }); + }, + move |e| { + errors_1.send_error(SoundcardError::Stream(e)); + }, + None, + ) { + Ok(s) => s, + Err(e) => { + errors.send_error(SoundcardError::StreamBuild(e)); + continue; + } + }; + if let Err(e) = stream.play() { + errors.send_error(SoundcardError::StreamPlay(e)); + continue; + } output_stream = Some(stream); } SoundcardEvent::CloseOutput => { @@ -257,3 +303,30 @@ fn spawn_soundcard_worker( } }); } + +#[derive(Debug, Error)] +pub enum SoundcardError { + #[error("sound card init aborted unexpectedly")] + SoundcardInit, + + #[error("unable to enumerate devices: {0}")] + Host(DevicesError), + + #[error("unable to locate sound card '{0}' - is it in use?")] + CardNotFound(String), + + #[error("error occurred in soundcard i/o: {0}")] + Stream(#[source] StreamError), + + #[error("unable to retrieve supported configs for soundcard: {0}")] + SupportedConfigs(#[source] SupportedStreamConfigsError), + + #[error("could not find a suitable soundcard config")] + NoValidConfigAvailable, + + #[error("unable to build soundcard stream: {0}")] + StreamBuild(#[source] BuildStreamError), + + #[error("unable to play stream")] + StreamPlay(#[source] PlayStreamError), +}