From f1616705af470cafc25487fabf7ba1984c6199e5 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Sat, 15 Aug 2026 11:14:17 +0200 Subject: [PATCH] fix(wasapi): don't panic when the COM enumerator fails to initialize --- CHANGELOG.md | 1 + src/host/wasapi/device.rs | 45 ++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3ec042b..4bb44cf3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **WASAPI**: `Stream::drop`, `play`, and `pause` no longer panic when the device is lost. - **WASAPI**: Capture streams no longer report a spurious xrun on the first buffer after starting. - **WASAPI**: Output streams no longer reject formats that the built-in resampler can convert. +- **WASAPI**: Device enumeration no longer panics if the COM enumerator fails to initialize. - **WebAudio**: Fix stale audio output when a data callback wrote a partial buffer. - **WebAudio**: Fix unsound `Send + Sync` on `Stream` when compiled with `+atomics`. - **WebAudio**: Fix `Host::is_available()` always returning `true`, even in non-window contexts. diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 351726f09..3068e29db 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -559,7 +559,10 @@ impl Device { DeviceHandle::DefaultInput => Audio::eCapture, DeviceHandle::Specific(_) => return Ok(None), }; - let enumerator = get_enumerator().0.clone(); + let enumerator = get_enumerator() + .context("Failed to get device enumerator")? + .0 + .clone(); DefaultDeviceMonitor::new(enumerator, flow).map(Some) } @@ -1153,7 +1156,7 @@ impl Endpoint { } } -static ENUMERATOR: OnceLock = OnceLock::new(); +static ENUMERATOR: OnceLock> = OnceLock::new(); /// Returns the current default audio endpoint for `flow`, or `None` if none exists. /// @@ -1166,30 +1169,33 @@ pub(super) fn current_default_endpoint(flow: Audio::EDataFlow) -> Option &'static Enumerator { - ENUMERATOR.get_or_init(|| { - // COM initialization is thread local, but we only need to have COM initialized in the - // thread we create the objects in - com::com_initialized(); - - // building the devices enumerator object - unsafe { - let enumerator = Com::CoCreateInstance::<_, Audio::IMMDeviceEnumerator>( - &Audio::MMDeviceEnumerator, - None, - Com::CLSCTX_ALL, - ) - .unwrap(); +fn get_enumerator() -> Result<&'static Enumerator, windows::core::Error> { + ENUMERATOR + .get_or_init(|| { + // COM initialization is thread local, but we only need to have COM initialized in the + // thread we create the objects in + com::com_initialized(); - Enumerator(enumerator) - } - }) + // SAFETY: `MMDeviceEnumerator` is a well-known in-process COM class; the returned + // interface pointer is only read through the safe `IMMDeviceEnumerator` wrapper. + unsafe { + Com::CoCreateInstance::<_, Audio::IMMDeviceEnumerator>( + &Audio::MMDeviceEnumerator, + None, + Com::CLSCTX_ALL, + ) + } + .map(Enumerator) + }) + .as_ref() + .map_err(Clone::clone) } // Helper function to query a DWORD property from a WASAPI device property store @@ -1271,6 +1277,7 @@ impl Devices { unsafe { // can fail because of wrong parameters (should never happen) or out of memory let collection = get_enumerator() + .context("Failed to get device enumerator")? .0 .EnumAudioEndpoints(Audio::eAll, Audio::DEVICE_STATE_ACTIVE) .context("Failed to enumerate audio endpoints")?;