]> code.octet-stream.net Git - m17rt/blobdiff - m17app/src/soundcard.rs
Error handler for soundmodem components
[m17rt] / m17app / src / soundcard.rs
index f89d9f4e43a9ee6a1d34ee667a90b98a7c440b8e..dc08036effa7dbe99fc462b07a4d9e13283f7f2b 100644 (file)
@@ -8,12 +8,13 @@ use std::{
 
 use cpal::{
     traits::{DeviceTrait, HostTrait, StreamTrait},
 
 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 {
 };
 
 pub struct Soundcard {
@@ -21,14 +22,14 @@ pub struct Soundcard {
 }
 
 impl Soundcard {
 }
 
 impl Soundcard {
-    pub fn new<S: Into<String>>(card_name: S) -> Result<Self, M17Error> {
+    pub fn new<S: Into<String>>(card_name: S) -> Result<Self, SoundcardError> {
         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),
         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<SoundmodemEvent>,
     SetTxInverted(bool),
     StartInput {
         samples: SyncSender<SoundmodemEvent>,
+        errors: SoundmodemErrorSender,
     },
     CloseInput,
     StartOutput {
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
     },
     CloseInput,
     StartOutput {
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
+        errors: SoundmodemErrorSender,
     },
     CloseOutput,
 }
     },
     CloseOutput,
 }
@@ -118,12 +121,14 @@ pub struct SoundcardInputSource {
 }
 
 impl InputSource for SoundcardInputSource {
 }
 
 impl InputSource for SoundcardInputSource {
-    fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
-        Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?)
+    fn start(&self, samples: SyncSender<SoundmodemEvent>, 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<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
         &self,
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
-    ) -> 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<SoundcardEvent>,
     }
 }
 
 fn spawn_soundcard_worker(
     event_rx: Receiver<SoundcardEvent>,
-    setup_tx: SyncSender<Result<(), M17Error>>,
+    setup_tx: SyncSender<Result<(), SoundcardError>>,
     card_name: String,
 ) {
     std::thread::spawn(move || {
     card_name: String,
 ) {
     std::thread::spawn(move || {
@@ -159,7 +167,7 @@ fn spawn_soundcard_worker(
             .unwrap()
             .find(|d| d.name().unwrap() == card_name)
         else {
             .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;
         };
 
             return;
         };
 
@@ -173,81 +181,119 @@ fn spawn_soundcard_worker(
             match ev {
                 SoundcardEvent::SetRxInverted(inv) => rx_inverted = inv,
                 SoundcardEvent::SetTxInverted(inv) => tx_inverted = inv,
             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)
                         .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<i16> = 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<i16> = 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();
                 }
                     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)
                         .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 => {
                     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),
+}