From 7c25471e007cd9c2195f02f210c313ce0281eabc Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Mon, 18 May 2026 20:41:14 +0200 Subject: [PATCH 1/3] style: standardize error messages across backends --- src/host/aaudio/mod.rs | 36 +++++----- src/host/alsa/mod.rs | 45 +++++------- src/host/asio/device.rs | 4 +- src/host/asio/stream.rs | 43 +++++++----- src/host/audioworklet/mod.rs | 33 +++++---- src/host/coreaudio/ios/mod.rs | 14 ++-- .../coreaudio/ios/session_event_manager.rs | 10 +-- src/host/coreaudio/macos/device.rs | 70 +++++++++---------- src/host/coreaudio/macos/loopback.rs | 5 +- src/host/coreaudio/macos/mod.rs | 20 +++--- src/host/coreaudio/mod.rs | 2 +- src/host/jack/device.rs | 16 ++--- src/host/jack/mod.rs | 12 ++-- src/host/jack/stream.rs | 17 ++--- src/host/pipewire/device.rs | 8 +-- src/host/pipewire/mod.rs | 5 +- src/host/pipewire/stream.rs | 10 +-- src/host/pulseaudio/mod.rs | 40 +++++------ src/host/pulseaudio/stream.rs | 4 +- src/host/wasapi/device.rs | 70 +++++++++---------- src/host/wasapi/stream.rs | 24 +++---- src/host/webaudio/mod.rs | 68 +++++++++++------- 22 files changed, 277 insertions(+), 279 deletions(-) diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index cb87f52ec..1abf86870 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -568,7 +568,7 @@ impl DeviceTrait for Device { if matches!(info.direction, DeviceDirection::Output) { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "output-only device does not support input", + "Device does not support input", )); } Ok(device_supported_configs(info)) @@ -583,7 +583,7 @@ impl DeviceTrait for Device { if matches!(info.direction, DeviceDirection::Input) { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "input-only device does not support output", + "Device does not support output", )); } Ok(device_supported_configs(info)) @@ -598,7 +598,7 @@ impl DeviceTrait for Device { let range = configs.into_iter().next().ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "no supported input configuration", + "No supported input configuration", ) })?; let config = range @@ -613,7 +613,7 @@ impl DeviceTrait for Device { let range = configs.into_iter().next().ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "no supported output configuration", + "No supported output configuration", ) })?; let config = range @@ -640,7 +640,7 @@ impl DeviceTrait for Device { sample_format => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("{sample_format} format is not supported on Android"), + format!("Sample format {sample_format} is not supported"), )) } }; @@ -651,7 +651,7 @@ impl DeviceTrait for Device { // TODO: more channels available in native AAudio return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("{channels} channels are not supported yet (only 1 or 2)"), + format!("Channel count {channels} is not supported"), )); } }; @@ -689,7 +689,7 @@ impl DeviceTrait for Device { sample_format => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("{sample_format} format is not supported on Android"), + format!("Sample format {sample_format} is not supported"), )) } }; @@ -700,7 +700,7 @@ impl DeviceTrait for Device { // TODO: more channels available in native AAudio return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("{channels} channels are not supported yet (only 1 or 2)"), + format!("Channel count {channels} is not supported"), )); } }; @@ -749,42 +749,38 @@ impl Hash for Device { impl StreamTrait for Stream { fn play(&self) -> Result<(), Error> { let stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; - stream - .request_start() - .context("failed to start AAudio stream")?; + stream.request_start().context("Failed to start stream")?; stream .wait_for_state_change( ndk::audio::AudioStreamState::Starting, DEFAULT_TIMEOUT_NANOS, ) .map(|_| ()) - .context("failed to wait for AAudio stream to start") + .context("Failed to wait for stream to start") } fn pause(&self) -> Result<(), Error> { match self.direction { DeviceDirection::Output => { let stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; - stream - .request_pause() - .context("failed to pause AAudio stream")?; + stream.request_pause().context("Failed to pause stream")?; stream .wait_for_state_change( ndk::audio::AudioStreamState::Pausing, DEFAULT_TIMEOUT_NANOS, ) .map(|_| ()) - .context("failed to wait for AAudio stream to pause") + .context("Failed to wait for stream to pause") } _ => Err(Error::with_message( ErrorKind::UnsupportedOperation, - "pause only supported on output streams", + "Pause is not supported on input streams", )), } } @@ -795,7 +791,7 @@ impl StreamTrait for Stream { fn buffer_size(&self) -> Result { let stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; // frames_per_data_callback is only set for BufferSize::Fixed; for Default AAudio diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 35c3aab89..2ab69e5b8 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -106,7 +106,10 @@ pub struct Host { impl Host { pub fn new() -> Result { let inner = AlsaContext::new().map_err(|e| { - Error::with_message(ErrorKind::HostUnavailable, format!("ALSA unavailable: {e}")) + Error::with_message( + ErrorKind::HostUnavailable, + format!("ALSA is not available: {e}"), + ) })?; Ok(Self { inner: Arc::new(inner), @@ -365,7 +368,7 @@ impl Device { if !(min..=max).contains(&requested_size) { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("buffer size {requested_size} is not in the supported range {min}..={max}"), + format!("Buffer size {requested_size} is not in the supported range {min}..={max}"), )); } } @@ -380,22 +383,13 @@ impl Device { let hw_params = set_hw_params_from_format(&handle, conf, sample_format)?; let (buffer_size, period_size) = set_sw_params_from_format(&handle, stream_type)?; if buffer_size == 0 { - return Err(Error::with_message( - ErrorKind::DeviceNotAvailable, - format!( - "device '{}': initialization resulted in a null buffer", - self.pcm_id - ), - )); + return Err(ErrorKind::DeviceNotAvailable.into()); } handle.prepare()?; if handle.count() == 0 { - return Err(Error::with_message( - ErrorKind::DeviceNotAvailable, - format!("device '{}': poll descriptor count is 0", self.pcm_id), - )); + return Err(ErrorKind::DeviceNotAvailable.into()); } // A zero get_htstamp() at prepare time indicates the device does not support hardware timestamps (e.g. PulseAudio ALSA plugin). @@ -417,7 +411,6 @@ impl Device { dropping: AtomicBool::new(false), direction: stream_type.into(), handle, - pcm_id: self.pcm_id.clone(), sample_format, sample_rate: conf.sample_rate, frame_size, @@ -607,12 +600,13 @@ impl Device { match self.supported_configs(stream_t) { // EINVAL when querying direction the device does not support (input-only or output-only) Err(err) if err.kind() == ErrorKind::InvalidInput => { + let dir = match stream_t { + alsa::Direction::Capture => "input", + alsa::Direction::Playback => "output", + }; return Err(Error::with_message( ErrorKind::UnsupportedOperation, - format!( - "device '{}' does not support the requested direction", - self.pcm_id - ), + format!("Device does not support {dir}"), )); } Err(err) => return Err(err), @@ -628,7 +622,7 @@ impl Device { .unwrap_or_else(|| f.with_max_sample_rate())), None => Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("device '{}': no supported configuration", self.pcm_id), + "No supported configuration", )), } } @@ -745,9 +739,6 @@ struct StreamInner { // The ALSA handle. handle: alsa::pcm::PCM, - // ALSA PCM identifier used to open this stream. - pcm_id: String, - // Format of the samples. sample_format: SampleFormat, @@ -1055,7 +1046,7 @@ fn try_resume(handle: &alsa::PCM) -> Result { if !hw_params.can_resume() { return Err(Error::with_message( ErrorKind::Xrun, // treat as xrun so the worker calls prepare() - "hardware suspend/resume not supported", + "Device does not support suspend/resume", )); } @@ -1129,7 +1120,7 @@ fn poll_for_period( if revents.intersects(alsa::poll::Flags::HUP | alsa::poll::Flags::NVAL) { return Err(Error::with_message( ErrorKind::DeviceNotAvailable, - format!("device '{}' disconnected", stream.pcm_id), + "Device disconnected", )); } // POLLERR signals an xrun or suspend; avail_delay() below returns EPIPE/ESTRPIPE accordingly. @@ -1432,7 +1423,7 @@ impl StreamTrait for Stream { if !hw_params.can_pause() { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - format!("device '{}' does not support pausing", self.inner.pcm_id), + "Device does not support pausing", )); } if self.inner.handle.state() != alsa::pcm::State::Paused { @@ -1558,7 +1549,7 @@ fn sample_format_to_alsa_format( _ => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format '{sample_format}' is not supported"), + format!("Sample format {sample_format} is not supported"), )) } }; @@ -1575,7 +1566,7 @@ fn sample_format_to_alsa_format( Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format '{sample_format}' is not supported by hardware in any endianness"), + format!("Sample format {sample_format} is not supported in any byte order"), )) } diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index 0fb6c2dca..925a142dd 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -92,13 +92,13 @@ impl Device { if channels == 0 { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "ASIO device reports no channels for this direction", + "Device reports no channels for this direction", )); } let sample_format = sample_format.ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedOperation, - "no supported sample format for this ASIO device", + "No supported sample format", ) })?; Ok(SupportedStreamConfig { diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 72005381f..dec23b1ac 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -116,7 +116,7 @@ impl Stream { pub fn buffer_size(&self) -> Result { let streams = self.asio_streams.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; Ok(streams .output @@ -145,7 +145,10 @@ impl Device { let driver = super::GLOBAL_ASIO .get() .ok_or_else(|| { - Error::with_message(ErrorKind::DeviceNotAvailable, "ASIO driver not initialized") + Error::with_message( + ErrorKind::DeviceNotAvailable, + "ASIO driver is not initialized", + ) })? .load_driver(description.name()) .map_err(load_driver_err)?; @@ -157,14 +160,14 @@ impl Device { super::device::convert_data_type(&stream_type).ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("ASIO input data type {stream_type:?} is not supported"), + "Input sample format is not supported", ) })?; if sample_format != expected_sample_format { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample format {sample_format} does not match ASIO input format {expected_sample_format}" + "Sample format {sample_format} is not supported; expected {expected_sample_format}" ), )); } @@ -474,7 +477,10 @@ impl Device { let driver = super::GLOBAL_ASIO .get() .ok_or_else(|| { - Error::with_message(ErrorKind::DeviceNotAvailable, "ASIO driver not initialized") + Error::with_message( + ErrorKind::DeviceNotAvailable, + "ASIO driver is not initialized", + ) })? .load_driver(description.name()) .map_err(load_driver_err)?; @@ -486,14 +492,14 @@ impl Device { super::device::convert_data_type(&stream_type).ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("ASIO output data type {stream_type:?} is not supported"), + "Output sample format is not supported", ) })?; if sample_format != expected_sample_format { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample format {sample_format} does not match ASIO output format {expected_sample_format}" + "Sample format {sample_format} is not supported; expected {expected_sample_format}" ), )); } @@ -851,7 +857,7 @@ impl Device { check_config(driver, config, sample_format, num_asio_channels)?; let num_channels = config.channels as usize; let mut streams = self.asio_streams.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; let buffer_size = match config.buffer_size { @@ -893,7 +899,7 @@ impl Device { check_config(driver, config, sample_format, num_asio_channels)?; let num_channels = config.channels as usize; let mut streams = self.asio_streams.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; let buffer_size = match config.buffer_size { @@ -980,7 +986,7 @@ impl Device { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to spawn ASIO event timer thread: {e}"), + format!("Failed to spawn event timer thread: {e}"), ) })?; @@ -1004,7 +1010,7 @@ impl Device { if StreamState::load(&state) != StreamState::Starting { let _ = timer_tx.send(Error::with_message( ErrorKind::StreamInvalidated, - "ASIO driver requested stream reset", + "Stream reset was requested by the ASIO driver", )); } true @@ -1016,7 +1022,7 @@ impl Device { if StreamState::load(&state) != StreamState::Starting { let _ = timer_tx.send(Error::with_message( ErrorKind::StreamInvalidated, - "ASIO driver requested stream resynchronization", + "Stream resynchronization was requested by the ASIO driver", )); } true @@ -1068,7 +1074,7 @@ impl Device { if should_notify && StreamState::load(&state) != StreamState::Starting { let _ = timer_tx.send(Error::with_message( ErrorKind::StreamInvalidated, - format!("ASIO driver changed sample rate to {new_rate} Hz"), + format!("Sample rate changed to {new_rate} Hz by the ASIO driver"), )); } false @@ -1112,8 +1118,9 @@ fn check_config( return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "buffer size {requested_size} is not in the supported range {}..={}", - range.min, range.max + "Buffer size {requested_size} is not in the supported range {min}..={max}", + min = range.min, + max = range.max ), )); } @@ -1132,7 +1139,7 @@ fn check_config( } else { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample rate {sample_rate} Hz is not supported by the ASIO driver"), + format!("Sample rate {sample_rate} Hz is not supported"), )); } } @@ -1142,14 +1149,14 @@ fn check_config( _ => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format {sample_format} is not supported by ASIO"), + format!("Sample format {sample_format} is not supported"), )) } } if channels > num_asio_channels { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("{channels} channels exceeds the ASIO device maximum of {num_asio_channels}"), + format!("Channel count {channels} exceeds the maximum of {num_asio_channels}"), )); } Ok(()) diff --git a/src/host/audioworklet/mod.rs b/src/host/audioworklet/mod.rs index f5087832e..2fa05dfb1 100644 --- a/src/host/audioworklet/mod.rs +++ b/src/host/audioworklet/mod.rs @@ -66,7 +66,7 @@ impl Host { } else { Err(Error::with_message( ErrorKind::HostUnavailable, - "AudioWorklet API is not available", + "AudioWorklet is not available", )) } } @@ -157,7 +157,7 @@ impl DeviceTrait for Device { fn default_input_config(&self) -> Result { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "AudioWorklet does not support audio input", + "Device does not support input", )) } @@ -168,7 +168,7 @@ impl DeviceTrait for Device { .ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "AudioWorklet has no supported output configurations", + "No supported output configuration", ) })?; let config = range @@ -192,7 +192,7 @@ impl DeviceTrait for Device { { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "AudioWorklet does not support audio input", + "Device does not support input", )) } @@ -225,7 +225,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "{} channels is not supported; AudioWorklet supports {} to {}", + "Channel count {} is not in the supported range {} to {}", config.channels, MIN_CHANNELS, MAX_CHANNELS ), )); @@ -234,7 +234,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "{} Hz is not supported; AudioWorklet supports {} to {} Hz", + "Sample rate {} Hz is not in the supported range {} to {} Hz", config.sample_rate, MIN_SAMPLE_RATE, MAX_SAMPLE_RATE ), )); @@ -243,7 +243,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample format {sample_format} is not supported; AudioWorklet requires {SUPPORTED_SAMPLE_FORMAT}" + "Sample format {sample_format} is not supported; required format is {SUPPORTED_SAMPLE_FORMAT}" ), )); } @@ -258,8 +258,13 @@ impl DeviceTrait for Device { ); } - let audio_context = web_sys::AudioContext::new_with_context_options(&stream_opts) - .map_err(|err| Error::with_message(ErrorKind::UnsupportedConfig, format!("{err:?}")))?; + let audio_context = + web_sys::AudioContext::new_with_context_options(&stream_opts).map_err(|_| { + Error::with_message( + ErrorKind::UnsupportedConfig, + "Failed to create audio context", + ) + })?; let destination = audio_context.destination(); @@ -350,7 +355,7 @@ impl DeviceTrait for Device { if let Err(err) = result { let message = err .as_string() - .unwrap_or_else(|| format!("Browser error initializing stream: {err:?}")); + .unwrap_or_else(|| "Failed to initialize audio worklet".to_string()); error_callback(Error::with_message( ErrorKind::UnsupportedOperation, message, @@ -373,9 +378,9 @@ impl StreamTrait for Stream { fn play(&self) -> Result<(), Error> { match self.audio_context.resume() { Ok(_) => Ok(()), - Err(err) => Err(Error::with_message( + Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - format!("{err:?}"), + "Failed to resume audio context", )), } } @@ -383,9 +388,9 @@ impl StreamTrait for Stream { fn pause(&self) -> Result<(), Error> { match self.audio_context.suspend() { Ok(_) => Ok(()), - Err(err) => Err(Error::with_message( + Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - format!("{err:?}"), + "Failed to suspend audio context", )), } } diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index f6b029644..50b75b9dd 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -112,7 +112,7 @@ impl Device { let range = get_supported_stream_configs(true).next().ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "no supported input configuration", + "No supported input configuration", ) })?; Ok(range @@ -125,7 +125,7 @@ impl Device { let range = get_supported_stream_configs(false).last().ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "no supported output configuration", + "No supported output configuration", ) })?; Ok(range @@ -281,13 +281,13 @@ impl Drop for Stream { impl StreamTrait for Stream { fn play(&self) -> Result<(), Error> { let mut stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; if !stream.playing { stream .audio_unit .start() - .context("failed to start audio unit")?; + .context("Failed to start audio unit")?; stream.playing = true; } Ok(()) @@ -295,13 +295,13 @@ impl StreamTrait for Stream { fn pause(&self) -> Result<(), Error> { let mut stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; if stream.playing { stream .audio_unit .stop() - .context("failed to stop audio unit")?; + .context("Failed to stop audio unit")?; stream.playing = false; } Ok(()) @@ -369,7 +369,7 @@ fn set_audio_session_buffer_size( .map_err(|e| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("failed to set preferred I/O buffer duration: {e}"), + format!("Failed to set preferred I/O buffer duration: {e}"), ) })?; } diff --git a/src/host/coreaudio/ios/session_event_manager.rs b/src/host/coreaudio/ios/session_event_manager.rs index f40a63943..e0b017c49 100644 --- a/src/host/coreaudio/ios/session_event_manager.rs +++ b/src/host/coreaudio/ios/session_event_manager.rs @@ -26,19 +26,19 @@ unsafe fn route_change_error(notification: &NSNotification) -> Option { match reason { AVAudioSessionRouteChangeReason::OldDeviceUnavailable => Some(Error::with_message( ErrorKind::DeviceChanged, - "audio route changed", + "Audio route changed", )), AVAudioSessionRouteChangeReason::CategoryChange | AVAudioSessionRouteChangeReason::Override | AVAudioSessionRouteChangeReason::RouteConfigurationChange => Some(Error::with_message( ErrorKind::StreamInvalidated, - "audio route changed", + "Audio route changed", )), AVAudioSessionRouteChangeReason::NoSuitableRouteForCategory => Some(Error::with_message( ErrorKind::DeviceNotAvailable, - "no suitable audio route for the session category", + "No suitable audio route for the session category", )), _ => None, @@ -90,7 +90,7 @@ impl SessionEventManager { &cb, Error::with_message( ErrorKind::DeviceNotAvailable, - "audio media services were lost", + "Audio media services were lost", ), ); } @@ -112,7 +112,7 @@ impl SessionEventManager { &cb, Error::with_message( ErrorKind::StreamInvalidated, - "audio media services were reset", + "Audio media services were reset", ), ); } diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 2f0f4b794..0df7d3406 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -153,7 +153,7 @@ fn set_sample_rate( { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample rate {sample_rate} Hz is not supported by this device"), + format!("Sample rate {sample_rate} Hz is not supported"), )); } @@ -195,13 +195,13 @@ fn set_sample_rate( Err(RecvTimeoutError::Timeout) => { return Err(Error::with_message( ErrorKind::DeviceNotAvailable, - "timeout waiting for sample rate update for device", + "Sample rate update timed out", )); } Err(RecvTimeoutError::Disconnected) => { return Err(Error::with_message( ErrorKind::StreamInvalidated, - "sample rate listener channel disconnected unexpectedly", + "Sample rate listener disconnected unexpectedly", )); } } @@ -403,7 +403,7 @@ impl Device { } fn description(&self) -> Result { - let name = get_device_name(self.audio_device_id).context("failed to get device name")?; + let name = get_device_name(self.audio_device_id).context("Failed to get device name")?; let input_configs = self .supported_input_configs() @@ -458,10 +458,7 @@ impl Device { let uid_string = unsafe { CFString::wrap_under_create_rule(uid).to_string() }; Ok(DeviceId(crate::platform::HostId::CoreAudio, uid_string)) } else { - Err(Error::with_message( - ErrorKind::DeviceNotAvailable, - "device UID is null", - )) + Err(ErrorKind::DeviceNotAvailable.into()) } } @@ -565,7 +562,7 @@ impl Device { _ => { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - format!("unexpected scope (neither input nor output): {scope:?}"), + "Unexpected audio property scope", )) } } @@ -618,35 +615,36 @@ impl Device { ); check_os_status(status)?; - let sample_format = - { - let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag( - asbd.mFormatID, - Some(asbd.mFormatFlags), - ); - let flags = match audio_format { - Some(coreaudio::audio_unit::AudioFormat::LinearPCM(flags)) => flags, - _ => { - return Err(Error::with_message( - ErrorKind::UnsupportedConfig, - "device audio format is not linear PCM", - )) - } - }; - let maybe_sample_format = - coreaudio::audio_unit::SampleFormat::from_flags_and_bits_per_sample( - flags, - asbd.mBitsPerChannel, - ); - match maybe_sample_format { - Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32, - Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16, - _ => return Err(Error::with_message( + let sample_format = { + let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag( + asbd.mFormatID, + Some(asbd.mFormatFlags), + ); + let flags = match audio_format { + Some(coreaudio::audio_unit::AudioFormat::LinearPCM(flags)) => flags, + _ => { + return Err(Error::with_message( ErrorKind::UnsupportedConfig, - "device sample format is not supported; only F32 and I16 are supported", - )), + "Audio format is not linear PCM", + )) } }; + let maybe_sample_format = + coreaudio::audio_unit::SampleFormat::from_flags_and_bits_per_sample( + flags, + asbd.mBitsPerChannel, + ); + match maybe_sample_format { + Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32, + Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16, + _ => { + return Err(Error::with_message( + ErrorKind::UnsupportedConfig, + "Sample format is not supported; supported formats are F32 and I16", + )) + } + } + }; #[allow(non_upper_case_globals)] match scope { @@ -654,7 +652,7 @@ impl Device { _ => { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - format!("unexpected scope (neither input nor output): {scope:?}"), + "Unexpected audio property scope", )) } } diff --git a/src/host/coreaudio/macos/loopback.rs b/src/host/coreaudio/macos/loopback.rs index b4f0b9596..b38e9a992 100644 --- a/src/host/coreaudio/macos/loopback.rs +++ b/src/host/coreaudio/macos/loopback.rs @@ -55,10 +55,7 @@ impl Device { check_os_status(status)?; if cfstring.is_null() { - return Err(Error::with_message( - ErrorKind::DeviceNotAvailable, - "device UID is null", - )); + return Err(ErrorKind::DeviceNotAvailable.into()); } let ns_string: Retained = unsafe { diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index d39b2d57d..82b0b0ee1 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -139,7 +139,7 @@ impl DisconnectManager { AudioObjectPropertyListener::new(device_id, alive_address, move || { let _ = disconnect_tx_alive.send(Error::with_message( ErrorKind::DeviceNotAvailable, - "device disconnected", + "Device disconnected", )); }); @@ -152,7 +152,7 @@ impl DisconnectManager { AudioObjectPropertyListener::new(device_id, rate_address, move || { let _ = disconnect_tx_rate.send(Error::with_message( ErrorKind::StreamInvalidated, - "device sample rate changed", + "Device sample rate changed", )); }); @@ -171,7 +171,7 @@ impl DisconnectManager { ready_rx.recv().map_err(|_| { Error::with_message( ErrorKind::StreamInvalidated, - "disconnect listener thread terminated unexpectedly", + "Stream monitor terminated unexpectedly", ) })??; @@ -199,7 +199,7 @@ impl DisconnectManager { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to spawn disconnect delivery thread: {e}"), + format!("Failed to spawn disconnect thread: {e}"), ) })?; @@ -312,7 +312,7 @@ impl StreamInner { if !self.playing { self.audio_unit .start() - .context("failed to start audio unit")?; + .context("Failed to start audio unit")?; self.playing = true; } Ok(()) @@ -322,7 +322,7 @@ impl StreamInner { if self.playing { self.audio_unit .stop() - .context("failed to stop audio unit")?; + .context("Failed to stop audio unit")?; self.playing = false; } Ok(()) @@ -355,14 +355,14 @@ impl StreamTrait for Stream { fn play(&self) -> Result<(), Error> { self.inner .lock() - .map_err(|_| Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned"))? + .map_err(|_| Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned"))? .play() } fn pause(&self) -> Result<(), Error> { self.inner .lock() - .map_err(|_| Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned"))? + .map_err(|_| Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned"))? .pause() } @@ -373,11 +373,11 @@ impl StreamTrait for Stream { fn buffer_size(&self) -> Result { let stream = self.inner.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "stream lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; device::get_device_buffer_frame_size(&stream.audio_unit) .map(|size| size as FrameCount) - .context("failed to get buffer frame size") + .context("Failed to get buffer frame size") } } diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index a3a42f2fc..82c0b506c 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -72,7 +72,7 @@ fn host_time_to_stream_instant(m_host_time: u64) -> Result check_os_status(res)?; let nanos = m_host_time as u128 * info.numer as u128 / info.denom as u128; let secs = u64::try_from(nanos / 1_000_000_000) - .map_err(|_| Error::with_message(ErrorKind::Other, "mach absolute time overflow"))?; + .map_err(|_| Error::with_message(ErrorKind::Other, "Timestamp conversion overflowed"))?; let subsec_nanos = (nanos % 1_000_000_000) as u32; Ok(StreamInstant::new(secs, subsec_nanos)) } diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 1cea18b71..411040f98 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -184,13 +184,13 @@ impl DeviceTrait for Device { if self.is_output() { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support input", + "Device does not support input", )); } if sample_format != JACK_SAMPLE_FORMAT { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format {sample_format} is not supported; JACK requires {JACK_SAMPLE_FORMAT}"), + format!("Sample format {sample_format} is not supported; required format is {JACK_SAMPLE_FORMAT}"), )); } @@ -205,7 +205,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample rate {} Hz does not match JACK server rate {} Hz", + "Sample rate {} Hz does not match the server rate {} Hz", conf.sample_rate, client.sample_rate() ), @@ -216,7 +216,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "buffer size {size} does not match JACK server buffer size {}", + "Buffer size {size} does not match the server buffer size {}", client.buffer_size() ), )); @@ -262,13 +262,13 @@ impl DeviceTrait for Device { if self.is_input() { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support output", + "Device does not support output", )); } if sample_format != JACK_SAMPLE_FORMAT { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format {sample_format} is not supported; JACK requires {JACK_SAMPLE_FORMAT}"), + format!("Sample format {sample_format} is not supported; required format is {JACK_SAMPLE_FORMAT}"), )); } @@ -284,7 +284,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample rate {} Hz does not match JACK server rate {} Hz", + "Sample rate {} Hz does not match the server rate {} Hz", conf.sample_rate, client.sample_rate() ), @@ -295,7 +295,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "buffer size {size} does not match JACK server buffer size {}", + "Buffer size {size} does not match the server buffer size {}", client.buffer_size() ), )); diff --git a/src/host/jack/mod.rs b/src/host/jack/mod.rs index 83a217e83..d4d668fc8 100644 --- a/src/host/jack/mod.rs +++ b/src/host/jack/mod.rs @@ -197,7 +197,7 @@ fn get_client(name: &str, client_options: jack::ClientOptions) -> Result Result jack::Control { if StreamState::load(&self.state, Ordering::Acquire) != StreamState::Starting { - let _ = try_emit_error( - &self.error_callback_ptr, - Error::with_message(ErrorKind::Xrun, "JACK xrun detected"), - ); + let _ = try_emit_error(&self.error_callback_ptr, ErrorKind::Xrun.into()); } jack::Control::Continue } diff --git a/src/host/pipewire/device.rs b/src/host/pipewire/device.rs index a7b9187d5..f40e1af5d 100644 --- a/src/host/pipewire/device.rs +++ b/src/host/pipewire/device.rs @@ -300,7 +300,7 @@ impl DeviceTrait for Device { if !self.supports_input() { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support input", + "Device does not support input", )); } Ok(SupportedStreamConfig { @@ -318,7 +318,7 @@ impl DeviceTrait for Device { if !self.supports_output() { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support output", + "Device does not support output", )); } Ok(SupportedStreamConfig { @@ -476,7 +476,7 @@ impl DeviceTrait for Device { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to create thread: {e}"), + format!("Failed to create thread: {e}"), ) })?; @@ -641,7 +641,7 @@ impl DeviceTrait for Device { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to create thread: {e}"), + format!("Failed to create thread: {e}"), ) })?; diff --git a/src/host/pipewire/mod.rs b/src/host/pipewire/mod.rs index 695f2056d..d223e6733 100644 --- a/src/host/pipewire/mod.rs +++ b/src/host/pipewire/mod.rs @@ -18,10 +18,7 @@ impl Host { pub fn new() -> Result { let _pw = PwInitGuard::new(); let devices = init_devices().ok_or_else(|| { - Error::with_message( - ErrorKind::HostUnavailable, - "PipeWire host initialization failed", - ) + Error::with_message(ErrorKind::HostUnavailable, "PipeWire is not available") })?; Ok(Self { _pw, devices }) } diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index 0779bd9f6..3ff90f2d9 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -240,7 +240,7 @@ impl UserData { { emit_error( &self.error_callback, - Error::with_message(ErrorKind::DeviceNotAvailable, "device disconnected"), + Error::with_message(ErrorKind::DeviceNotAvailable, "Device disconnected"), ); } } @@ -598,7 +598,7 @@ where &user_data.error_callback, Error::with_message( ErrorKind::UnsupportedConfig, - format!("negotiated format mismatch: expected channels={channels} rate={rate} format={expected_fmt:?}, got channels={current_channels} rate={current_rate} format={current_fmt:?}"), + format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz"), ), ); if let Err(e) = stream.set_active(false) { @@ -641,7 +641,7 @@ where if user_data.pending_device_changed.load(Ordering::Relaxed) && try_emit_error( &user_data.error_callback, - Error::with_message(ErrorKind::DeviceChanged, "default device changed"), + Error::with_message(ErrorKind::DeviceChanged, "Default device changed"), ) .is_ok() { @@ -831,7 +831,7 @@ where &user_data.error_callback, Error::with_message( ErrorKind::UnsupportedConfig, - format!("negotiated format mismatch: expected channels={channels} rate={rate} format={expected_fmt:?}, got channels={current_channels} rate={current_rate} format={current_fmt:?}"), + format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz"), ), ); if let Err(e) = stream.set_active(false) { @@ -874,7 +874,7 @@ where if user_data.pending_device_changed.load(Ordering::Relaxed) && try_emit_error( &user_data.error_callback, - Error::with_message(ErrorKind::DeviceChanged, "default device changed"), + Error::with_message(ErrorKind::DeviceChanged, "Default device changed"), ) .is_ok() { diff --git a/src/host/pulseaudio/mod.rs b/src/host/pulseaudio/mod.rs index 40fddbbc6..bf1187d52 100644 --- a/src/host/pulseaudio/mod.rs +++ b/src/host/pulseaudio/mod.rs @@ -101,12 +101,11 @@ impl From for Error { match err { ServerUnavailable => { - Error::with_message(ErrorKind::HostUnavailable, "PulseAudio server unavailable") + Error::with_message(ErrorKind::HostUnavailable, "PulseAudio is not available") + } + UnexpectedSequenceNumber | Disconnected => { + Error::with_message(ErrorKind::StreamInvalidated, "PulseAudio disconnected") } - UnexpectedSequenceNumber | Disconnected => Error::with_message( - ErrorKind::StreamInvalidated, - "PulseAudio client disconnected", - ), Io(e) => Error::with_message(ErrorKind::StreamInvalidated, format!("I/O error: {e}")), ServerError(e) => Error::with_message(pulse_error_kind(e), format!("{e}")), Protocol(e) => { @@ -143,11 +142,11 @@ impl Host { .map_err(|err| match err { mpsc::RecvTimeoutError::Timeout => Error::with_message( ErrorKind::HostUnavailable, - "timed out waiting for PulseAudio", + "PulseAudio is not available: connection timed out", ), mpsc::RecvTimeoutError::Disconnected => Error::with_message( ErrorKind::HostUnavailable, - "PulseAudio initialization thread disconnected before sending a result", + "PulseAudio is not available: initialization failed", ), }) .and_then(|r| r.map_err(Error::from))?; @@ -165,11 +164,9 @@ impl HostTrait for Host { } fn devices(&self) -> Result { - let sinks = - block_on(self.client.list_sinks()).context("failed to list PulseAudio sinks")?; + let sinks = block_on(self.client.list_sinks()).context("Failed to list sinks")?; - let sources = - block_on(self.client.list_sources()).context("failed to list PulseAudio sources")?; + let sources = block_on(self.client.list_sources()).context("Failed to list sources")?; Ok(sinks .into_iter() @@ -253,10 +250,7 @@ fn default_config_from_spec( let sample_format: SampleFormat = sample_spec.format.try_into().map_err(|_| { Error::with_message( ErrorKind::UnsupportedConfig, - format!( - "PulseAudio sample format {:?} is not supported", - sample_spec.format - ), + "Sample format is not supported", ) })?; let bytes_per_frame = channel_map.num_channels() as usize * sample_format.sample_size(); @@ -295,7 +289,7 @@ impl DeviceTrait for Device { let Device::Source { info, .. } = self else { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support input", + "Device does not support input", )); }; default_config_from_spec(&info.sample_spec, &info.channel_map) @@ -305,7 +299,7 @@ impl DeviceTrait for Device { let Device::Sink { info, .. } = self else { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support output", + "Device does not support output", )); }; default_config_from_spec(&info.sample_spec, &info.channel_map) @@ -326,14 +320,14 @@ impl DeviceTrait for Device { let Device::Source { client, info } = self else { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support input", + "Device does not support input", )); }; let format: protocol::SampleFormat = sample_format.try_into().map_err(|_| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format {sample_format} is not supported by PulseAudio"), + format!("Sample format {sample_format} is not supported"), ) })?; @@ -376,7 +370,7 @@ impl DeviceTrait for Device { Ok(result) => result, Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - "timed out waiting for PulseAudio server", + "Stream creation timed out", )), } } else { @@ -401,14 +395,14 @@ impl DeviceTrait for Device { let Device::Sink { client, info } = self else { return Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support output", + "Device does not support output", )); }; let format: protocol::SampleFormat = sample_format.try_into().map_err(|_| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("sample format {sample_format} is not supported by PulseAudio"), + format!("Sample format {sample_format} is not supported"), ) })?; @@ -451,7 +445,7 @@ impl DeviceTrait for Device { Ok(result) => result, Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - "timed out waiting for PulseAudio server", + "Stream creation timed out", )), } } else { diff --git a/src/host/pulseaudio/stream.rs b/src/host/pulseaudio/stream.rs index 0c796b249..d7c0c2e49 100644 --- a/src/host/pulseaudio/stream.rs +++ b/src/host/pulseaudio/stream.rs @@ -168,7 +168,7 @@ impl Stream { let format: SampleFormat = pa_format.try_into().map_err(|_| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("PulseAudio sample format {pa_format:?} is not supported"), + "Sample format is not supported", ) })?; @@ -342,7 +342,7 @@ impl Stream { let format: SampleFormat = pa_format.try_into().map_err(|_| { Error::with_message( ErrorKind::UnsupportedConfig, - format!("PulseAudio sample format {pa_format:?} is not supported"), + "Sample format is not supported", ) })?; diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 88ce45664..6e7ae7359 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -414,7 +414,7 @@ unsafe fn activate_audio_interface_sync( impl Device { pub fn description(&self) -> Result { let device = self.immdevice().ok_or_else(|| { - Error::with_message(ErrorKind::DeviceNotAvailable, "default device not found") + Error::with_message(ErrorKind::DeviceNotAvailable, "Default device not found") })?; unsafe { // Open the device's property store. @@ -457,7 +457,7 @@ impl Device { let name = friendly_name.or(device_desc).ok_or_else(|| { Error::with_message( ErrorKind::DeviceNotAvailable, - "failed to retrieve device name", + "Failed to retrieve device name", ) })?; @@ -502,7 +502,7 @@ impl Device { fn id(&self) -> Result { let device = self.immdevice().ok_or_else(|| { - Error::with_message(ErrorKind::DeviceNotAvailable, "default device not found") + Error::with_message(ErrorKind::DeviceNotAvailable, "Default device not found") })?; unsafe { match device.GetId() { @@ -510,7 +510,7 @@ impl Device { Ok(id_str) => Ok(DeviceId(crate::platform::HostId::Wasapi, id_str)), Err(e) => Err(Error::with_message( ErrorKind::BackendError, - format!("failed to convert device ID to string: {e}"), + format!("Failed to convert device ID to string: {e}"), )), }, Err(e) => Err(Error::from(e)), @@ -565,7 +565,7 @@ impl Device { activation_timeout: Option, ) -> Result>, Error> { let mut lock = self.future_audio_client.lock().map_err(|_| { - Error::with_message(ErrorKind::StreamInvalidated, "audio client lock poisoned") + Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned") })?; if lock.is_some() { return Ok(lock); @@ -626,7 +626,7 @@ impl Device { // Retrieve the `IAudioClient`. let lock = self .ensure_future_audio_client(None) - .context("failed to get audio client")?; + .context("Failed to get audio client")?; let client = &lock.as_ref().unwrap().0; unsafe { @@ -634,13 +634,13 @@ impl Device { let default_waveformatex_ptr = client .GetMixFormat() .map(WaveFormatExPtr) - .context("failed to get mix format")?; + .context("Failed to get mix format")?; // If the default format can't succeed we have no hope of finding other formats. if !is_format_supported(client, default_waveformatex_ptr.0)? { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - "could not determine support for default WAVEFORMATEX", + "Could not determine support for default audio format", )); } @@ -649,7 +649,7 @@ impl Device { None => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - "could not create a SupportedStreamConfig from WAVEFORMATEX", + "Default audio format could not be mapped to a supported configuration", )); } }; @@ -731,19 +731,19 @@ impl Device { let lock = self .ensure_future_audio_client(None) - .context("failed to get audio client")?; + .context("Failed to get audio client")?; let client = &lock.as_ref().unwrap().0; unsafe { let format_ptr = client .GetMixFormat() .map(WaveFormatExPtr) - .context("failed to get mix format")?; + .context("Failed to get mix format")?; format_from_waveformatex_ptr(format_ptr.0, client).ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "device audio format could not be mapped to a supported CPAL format", + "Device audio format could not be mapped to a supported format", ) }) } @@ -766,7 +766,7 @@ impl Device { } else { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support input", + "Device does not support input", )) } } @@ -778,7 +778,7 @@ impl Device { } else { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "device does not support output", + "Device does not support output", )) } } @@ -797,7 +797,7 @@ impl Device { // Obtaining a `IAudioClient`. let audio_client = self .build_audioclient(activation_timeout) - .context("failed to build audio client")?; + .context("Failed to build audio client")?; // Note: Buffer size validation is not needed here - `IAudioClient::Initialize` // will return `AUDCLNT_E_BUFFER_SIZE_ERROR` if the buffer size is not supported. @@ -815,7 +815,7 @@ impl Device { .ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "stream config could not be converted to a WASAPI-compatible format", + "Stream configuration could not be converted to a compatible format", ) })?; let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; @@ -825,7 +825,7 @@ impl Device { Ok(false) => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - "stream config is not supported by this WASAPI device in shared mode", + "Stream configuration is not supported in shared mode", )) } Err(e) => return Err(e), @@ -842,7 +842,7 @@ impl Device { &format_attempt.Format, None, ) - .context("failed to initialize audio client")?; + .context("Failed to initialize audio client")?; format_attempt.Format }; @@ -850,7 +850,7 @@ impl Device { // obtaining the size of the samples buffer in number of frames let max_frames_in_buffer = audio_client .GetBufferSize() - .context("failed to get buffer size")?; + .context("Failed to get buffer size")?; let period_frames = shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer); @@ -858,16 +858,16 @@ impl Device { // Creating the event that will be signalled whenever we need to submit some samples. let event = Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null())) - .context("failed to create event")?; + .context("Failed to create event")?; audio_client .SetEventHandle(event) - .context("failed to set event handle")?; + .context("Failed to set event handle")?; // Building a `IAudioCaptureClient` that will be used to read captured samples. let capture_client = audio_client .GetService::() - .context("failed to get capture client")?; + .context("Failed to get capture client")?; // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. @@ -878,7 +878,7 @@ impl Device { let stream_latency = { let hns = audio_client .GetStreamLatency() - .context("failed to get stream latency")?; + .context("Failed to get stream latency")?; Duration::from_nanos(hns.max(0) as u64 * 100) }; @@ -912,7 +912,7 @@ impl Device { // Obtaining a `IAudioClient`. let audio_client = self .build_audioclient(activation_timeout) - .context("failed to build audio client")?; + .context("Failed to build audio client")?; // Note: Buffer size validation is not needed here - `IAudioClient::Initialize` // will return `AUDCLNT_E_BUFFER_SIZE_ERROR` if the buffer size is not supported. @@ -924,7 +924,7 @@ impl Device { .ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "stream config could not be converted to a WASAPI-compatible format", + "Stream configuration could not be converted to a compatible format", ) })?; let share_mode = Audio::AUDCLNT_SHAREMODE_SHARED; @@ -934,7 +934,7 @@ impl Device { Ok(false) => { return Err(Error::with_message( ErrorKind::UnsupportedConfig, - "stream config is not supported by this WASAPI device in shared mode", + "Stream configuration is not supported in shared mode", )) } Err(e) => return Err(e), @@ -953,7 +953,7 @@ impl Device { &format_attempt.Format, None, ) - .context("failed to initialize audio client")?; + .context("Failed to initialize audio client")?; format_attempt.Format }; @@ -961,16 +961,16 @@ impl Device { // Creating the event that will be signalled whenever we need to submit some samples. let event = Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null())) - .context("failed to create event")?; + .context("Failed to create event")?; audio_client .SetEventHandle(event) - .context("failed to set event handle")?; + .context("Failed to set event handle")?; // obtaining the size of the samples buffer in number of frames let max_frames_in_buffer = audio_client .GetBufferSize() - .context("failed to get buffer size")?; + .context("Failed to get buffer size")?; let period_frames = shared_mode_period_frames(&audio_client, config.sample_rate, max_frames_in_buffer); @@ -978,7 +978,7 @@ impl Device { // Building a `IAudioRenderClient` that will be used to fill the samples buffer. let render_client = audio_client .GetService::() - .context("failed to get render client")?; + .context("Failed to get render client")?; // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. @@ -989,7 +989,7 @@ impl Device { let stream_latency = { let hns = audio_client .GetStreamLatency() - .context("failed to get stream latency")?; + .context("Failed to get stream latency")?; Duration::from_nanos(hns.max(0) as u64 * 100) }; @@ -1234,11 +1234,11 @@ impl Devices { let collection = get_enumerator() .0 .EnumAudioEndpoints(Audio::eAll, Audio::DEVICE_STATE_ACTIVE) - .context("failed to enumerate audio endpoints")?; + .context("Failed to enumerate audio endpoints")?; let count = collection .GetCount() - .context("failed to get device count")?; + .context("Failed to get device count")?; Ok(Self { collection, @@ -1288,7 +1288,7 @@ pub fn default_output_device() -> Option { unsafe fn get_audio_clock(audio_client: &Audio::IAudioClient) -> Result { audio_client .GetService::() - .context("failed to get audio clock") + .context("Failed to get audio clock") } // Sample rate range supported by the Media Foundation Resampler MFT used by AUTOCONVERTPCM. diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index dbb1b9195..6e8a97061 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -363,7 +363,7 @@ impl Stream { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to create thread: {e}"), + format!("Failed to create audio thread: {e}"), ) })?; @@ -433,7 +433,7 @@ impl Stream { .map_err(|e| { Error::with_message( ErrorKind::ResourceExhausted, - format!("failed to create thread: {e}"), + format!("Failed to create audio thread: {e}"), ) })?; @@ -487,7 +487,7 @@ impl StreamTrait for Stream { self.push_command(Command::PlayStream).map_err(|_| { Error::with_message( ErrorKind::StreamInvalidated, - "stream command channel closed", + "Stream command channel closed", ) })?; Ok(()) @@ -497,7 +497,7 @@ impl StreamTrait for Stream { self.push_command(Command::PauseStream).map_err(|_| { Error::with_message( ErrorKind::StreamInvalidated, - "stream command channel closed", + "Stream command channel closed", ) })?; Ok(()) @@ -545,7 +545,7 @@ fn process_commands(run_context: &mut RunContext) -> Result { .stream .audio_client .Start() - .context("failed to start audio client")?; + .context("Failed to start audio client")?; run_context.stream.playing = true; } }, @@ -555,7 +555,7 @@ fn process_commands(run_context: &mut RunContext) -> Result { .stream .audio_client .Stop() - .context("failed to stop audio client")?; + .context("Failed to stop audio client")?; run_context.stream.playing = false; } }, @@ -589,7 +589,7 @@ fn wait_for_handle_signal(handles: &[Foundation::HANDLE]) -> Result Result { let padding = stream .audio_client .GetCurrentPadding() - .context("failed to get current padding")?; + .context("Failed to get current padding")?; Ok(stream.max_frames_in_buffer - padding) } } @@ -713,7 +713,7 @@ fn process_commands_and_await_signal( if flag.swap(false, Ordering::Relaxed) { emit_error( error_callback, - Error::with_message(ErrorKind::DeviceChanged, "default audio device changed"), + Error::with_message(ErrorKind::DeviceChanged, "Default audio device changed"), ); } } @@ -734,7 +734,7 @@ fn process_commands_and_await_signal( if handle_idx >= 2 { emit_error( error_callback, - Error::with_message(ErrorKind::DeviceChanged, "default audio device changed"), + Error::with_message(ErrorKind::DeviceChanged, "Default audio device changed"), ); return ControlFlow::Continue(false); } @@ -801,7 +801,7 @@ fn process_input( // Release the buffer. let result = capture_client .ReleaseBuffer(frames_available) - .context("failed to release capture buffer"); + .context("Failed to release capture buffer"); if let Err(err) = result { emit_error(error_callback, err); return ControlFlow::Break(()); @@ -876,7 +876,7 @@ fn stream_instant(stream: &StreamInner) -> Result { stream .audio_clock .GetPosition(&mut position, Some(&mut qpc_position)) - .context("failed to get clock position")?; + .context("Failed to get clock position")?; }; // The `qpc_position` is in 100-nanosecond units. let nanos = qpc_position as u128 * 100; diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index a4c092745..ce5684318 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -150,7 +150,7 @@ impl Device { fn default_input_config(&self) -> Result { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "WebAudio does not support audio input", + "Device does not support input", )) } @@ -161,7 +161,7 @@ impl Device { .ok_or_else(|| { Error::with_message( ErrorKind::UnsupportedConfig, - "WebAudio has no supported output configurations", + "No supported output configuration", ) })?; let config = range @@ -215,7 +215,7 @@ impl DeviceTrait for Device { { Err(Error::with_message( ErrorKind::UnsupportedOperation, - "WebAudio does not support audio input", + "Device does not support input", )) } @@ -236,7 +236,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "sample format {sample_format} or channel count {} is not supported by WebAudio", + "Sample format {sample_format} or channel count {} is not supported", config.channels ), )); @@ -250,7 +250,7 @@ impl DeviceTrait for Device { return Err(Error::with_message( ErrorKind::UnsupportedConfig, format!( - "buffer size {v} is out of the supported range {MIN_BUFFER_SIZE}..={MAX_BUFFER_SIZE}" + "Buffer size {v} is not in the supported range {MIN_BUFFER_SIZE}..={MAX_BUFFER_SIZE}" ), )); } @@ -268,8 +268,12 @@ impl DeviceTrait for Device { // Create the WebAudio stream. let stream_opts = AudioContextOptions::new(); stream_opts.set_sample_rate(config.sample_rate as f32); - let ctx = AudioContext::new_with_context_options(&stream_opts) - .map_err(|err| Error::with_message(ErrorKind::UnsupportedConfig, format!("{err:?}")))?; + let ctx = AudioContext::new_with_context_options(&stream_opts).map_err(|_| { + Error::with_message( + ErrorKind::UnsupportedConfig, + "Failed to create audio context", + ) + })?; let destination = ctx.destination(); @@ -326,8 +330,11 @@ impl DeviceTrait for Device { buffer_size_frames as u32, config.sample_rate as f32, ) - .map_err(|err| { - Error::with_message(ErrorKind::UnsupportedConfig, format!("{err:?}")) + .map_err(|_| { + Error::with_message( + ErrorKind::UnsupportedConfig, + "Failed to create audio buffer", + ) })?; // A self reference to this closure for passing to future audio event calls. @@ -394,7 +401,7 @@ impl DeviceTrait for Device { .unwrap_or_else(|e| e.into_inner()))( Error::with_message( ErrorKind::StreamInvalidated, - "data callback lock poisoned", + "Stream lock poisoned", ), ); return; @@ -413,7 +420,7 @@ impl DeviceTrait for Device { #[cfg(not(target_feature = "atomics"))] { - if let Err(err) = ctx_buffer + if let Err(_) = ctx_buffer .copy_to_channel(&temporary_channel_buffer, channel as i32) { (error_callback_handle @@ -421,7 +428,7 @@ impl DeviceTrait for Device { .unwrap_or_else(|e| e.into_inner()))( Error::with_message( ErrorKind::StreamInvalidated, - format!("{err:?}"), + "Failed to copy audio data", ), ); return; @@ -436,7 +443,7 @@ impl DeviceTrait for Device { #[cfg(target_feature = "atomics")] { temporary_channel_array_view.copy_from(&temporary_channel_buffer); - if let Err(err) = ctx_buffer + if let Err(_) = ctx_buffer .unchecked_ref::() .copy_to_channel(&temporary_channel_array_view, channel as i32) { @@ -445,7 +452,7 @@ impl DeviceTrait for Device { .unwrap_or_else(|e| e.into_inner()))( Error::with_message( ErrorKind::StreamInvalidated, - format!("{err:?}"), + "Failed to copy audio data", ), ); return; @@ -457,29 +464,32 @@ impl DeviceTrait for Device { // in the future. let source = match ctx_handle.create_buffer_source() { Ok(s) => s, - Err(err) => { + Err(_) => { // create_buffer_source is documented not to throw; defensive only. (error_callback_handle .lock() .unwrap_or_else(|e| e.into_inner()))( Error::with_message( ErrorKind::StreamInvalidated, - format!("{err:?}"), + "Failed to create audio buffer source", ), ); return; } }; source.set_buffer(Some(&ctx_buffer)); - if let Err(err) = source.connect_with_audio_node(&ctx_handle.destination()) { + if let Err(_) = source.connect_with_audio_node(&ctx_handle.destination()) { (error_callback_handle .lock() .unwrap_or_else(|e| e.into_inner()))( - Error::with_message(ErrorKind::StreamInvalidated, format!("{err:?}")), + Error::with_message( + ErrorKind::StreamInvalidated, + "Failed to connect audio node", + ), ); return; } - if let Err(err) = source.add_event_listener_with_callback( + if let Err(_) = source.add_event_listener_with_callback( "ended", on_ended_closure_handle .read() @@ -493,16 +503,22 @@ impl DeviceTrait for Device { (error_callback_handle .lock() .unwrap_or_else(|e| e.into_inner()))( - Error::with_message(ErrorKind::StreamInvalidated, format!("{err:?}")), + Error::with_message( + ErrorKind::StreamInvalidated, + "Failed to register audio event listener", + ), ); return; } - if let Err(err) = source.start_with_when(time_at_start_of_buffer) { + if let Err(_) = source.start_with_when(time_at_start_of_buffer) { // InvalidStateError (already started) is the expected failure mode. (error_callback_handle .lock() .unwrap_or_else(|e| e.into_inner()))( - Error::with_message(ErrorKind::StreamInvalidated, format!("{err:?}")), + Error::with_message( + ErrorKind::StreamInvalidated, + "Failed to start audio buffer source", + ), ); return; } @@ -568,9 +584,9 @@ impl StreamTrait for Stream { } Ok(()) } - Err(err) => Err(Error::with_message( + Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - format!("{err:?}"), + "Failed to resume audio context", )), } } @@ -578,9 +594,9 @@ impl StreamTrait for Stream { fn pause(&self) -> Result<(), Error> { match self.ctx.suspend() { Ok(_) => Ok(()), - Err(err) => Err(Error::with_message( + Err(_) => Err(Error::with_message( ErrorKind::DeviceNotAvailable, - format!("{err:?}"), + "Failed to suspend audio context", )), } } From 7baf00f3805e9821e88b4be69ece70635a8d62ed Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Mon, 18 May 2026 21:05:45 +0200 Subject: [PATCH 2/3] fix: address review points --- src/host/pipewire/stream.rs | 14 ++++++++++++-- src/host/wasapi/stream.rs | 1 - src/host/webaudio/mod.rs | 36 ++++++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index 3ff90f2d9..d4d4f4445 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -594,11 +594,16 @@ where || current_rate != rate || current_fmt != expected_fmt; if mismatch && !user_data.invalidated.swap(true, Ordering::Relaxed) { + let fmt_note = if current_fmt != expected_fmt { + "; sample format differs" + } else { + "" + }; emit_error( &user_data.error_callback, Error::with_message( ErrorKind::UnsupportedConfig, - format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz"), + format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz{fmt_note}"), ), ); if let Err(e) = stream.set_active(false) { @@ -827,11 +832,16 @@ where || current_rate != rate || current_fmt != expected_fmt; if mismatch && !user_data.invalidated.swap(true, Ordering::Relaxed) { + let fmt_note = if current_fmt != expected_fmt { + "; sample format differs" + } else { + "" + }; emit_error( &user_data.error_callback, Error::with_message( ErrorKind::UnsupportedConfig, - format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz"), + format!("Negotiated format mismatch: expected {channels} channels at {rate} Hz, got {current_channels} channels at {current_rate} Hz{fmt_note}"), ), ); if let Err(e) = stream.set_active(false) { diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 6e8a97061..5df4aa895 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -586,7 +586,6 @@ fn wait_for_handle_signal(handles: &[Foundation::HANDLE]) -> Result() .copy_to_channel(&temporary_channel_array_view, channel as i32) + .is_err() { (error_callback_handle .lock() @@ -478,7 +480,10 @@ impl DeviceTrait for Device { } }; source.set_buffer(Some(&ctx_buffer)); - if let Err(_) = source.connect_with_audio_node(&ctx_handle.destination()) { + if source + .connect_with_audio_node(&ctx_handle.destination()) + .is_err() + { (error_callback_handle .lock() .unwrap_or_else(|e| e.into_inner()))( @@ -489,16 +494,19 @@ impl DeviceTrait for Device { ); return; } - if let Err(_) = source.add_event_listener_with_callback( - "ended", - on_ended_closure_handle - .read() - .unwrap() - .as_ref() - .unwrap() - .as_ref() - .unchecked_ref(), - ) { + if source + .add_event_listener_with_callback( + "ended", + on_ended_closure_handle + .read() + .unwrap() + .as_ref() + .unwrap() + .as_ref() + .unchecked_ref(), + ) + .is_err() + { // addEventListener is documented not to throw; defensive only. (error_callback_handle .lock() @@ -510,7 +518,7 @@ impl DeviceTrait for Device { ); return; } - if let Err(_) = source.start_with_when(time_at_start_of_buffer) { + if source.start_with_when(time_at_start_of_buffer).is_err() { // InvalidStateError (already started) is the expected failure mode. (error_callback_handle .lock() From bc32548642afa7aef3bda9b43997e28a6149cb60 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Mon, 18 May 2026 21:15:44 +0200 Subject: [PATCH 3/3] doc: log error message consistency --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8417c2c3d..6d27b4b6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `audio_thread_priority` feature renamed to `realtime-dbus`. - `audio_thread_priority` dependency bumped to 0.35. - `DeviceTrait` now requires `PartialEq + Eq + Hash + Debug + Display` with a stable device ID. +- Error messages are now consistent across all hosts. - **AAudio**: Device names now include the device type suffix (e.g. "Speaker (Builtin Speaker)") for easier identification when enumerating devices. - **AAudio**: `supported_input_configs()` and `supported_output_configs()` now return an error for