Skip to content

Commit 04c8eaa

Browse files
committed
refactor(wasapi): move virtual default devices to default-on feature
1 parent 6a17582 commit 04c8eaa

File tree

2 files changed

+68
-46
lines changed

2 files changed

+68
-46
lines changed

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ edition = "2021"
1010
rust-version = "1.70"
1111

1212
[features]
13+
default = ["wasapi-virtual-default-devices"]
14+
1315
asio = [
1416
"asio-sys",
1517
"num-traits",
1618
] # Only available on Windows. See README for setup instructions.
1719

20+
# Enable virtual default devices for WASAPI, so that audio will be
21+
# automatically rerouted when the default input or output device is changed.
22+
#
23+
# Note that this only works on Windows 8 and above. It is turned on by default,
24+
# but consider turning it off if you are supporting an older version of Windows.
25+
wasapi-virtual-default-devices = []
26+
1827
# Deprecated, the `oboe` backend has been removed
1928
oboe-shared-stdcxx = []
2029

src/host/wasapi/device.rs

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,20 @@ use std::mem;
1111
use std::os::windows::ffi::OsStringExt;
1212
use std::ptr;
1313
use std::slice;
14-
use std::sync::mpsc::Sender;
1514
use std::sync::OnceLock;
1615
use std::sync::{Arc, Mutex, MutexGuard};
1716
use std::time::Duration;
1817

1918
use super::com;
2019
use super::{windows_err_to_cpal_err, windows_err_to_cpal_err_message};
20+
use windows::core::Interface;
2121
use windows::core::GUID;
22-
use windows::core::{implement, IUnknown, Interface, HRESULT, PCWSTR};
2322
use windows::Win32::Devices::Properties;
2423
use windows::Win32::Foundation;
2524
use windows::Win32::Media::Audio::IAudioRenderClient;
2625
use windows::Win32::Media::{Audio, KernelStreaming, Multimedia};
2726
use windows::Win32::System::Com;
28-
use windows::Win32::System::Com::{CoTaskMemFree, StringFromIID, StructuredStorage, STGM_READ};
27+
use windows::Win32::System::Com::{StructuredStorage, STGM_READ};
2928
use windows::Win32::System::Threading;
3029
use windows::Win32::System::Variant::VT_LPWSTR;
3130

@@ -274,53 +273,50 @@ unsafe fn format_from_waveformatex_ptr(
274273
Some(format)
275274
}
276275

277-
#[implement(Audio::IActivateAudioInterfaceCompletionHandler)]
278-
struct CompletionHandler(Sender<windows::core::Result<IUnknown>>);
276+
#[cfg(feature = "wasapi-virtual-default-devices")]
277+
unsafe fn activate_audio_interface_sync(
278+
deviceinterfacepath: windows::core::PWSTR,
279+
) -> windows::core::Result<Audio::IAudioClient> {
280+
use windows::core::IUnknown;
279281

280-
fn retrieve_result(
281-
operation: &Audio::IActivateAudioInterfaceAsyncOperation,
282-
) -> windows::core::Result<IUnknown> {
283-
let mut result = HRESULT::default();
284-
let mut interface: Option<IUnknown> = None;
285-
unsafe {
286-
operation.GetActivateResult(&mut result, &mut interface)?;
282+
#[windows::core::implement(Audio::IActivateAudioInterfaceCompletionHandler)]
283+
struct CompletionHandler(std::sync::mpsc::Sender<windows::core::Result<IUnknown>>);
284+
285+
fn retrieve_result(
286+
operation: &Audio::IActivateAudioInterfaceAsyncOperation,
287+
) -> windows::core::Result<IUnknown> {
288+
let mut result = windows::core::HRESULT::default();
289+
let mut interface: Option<IUnknown> = None;
290+
unsafe {
291+
operation.GetActivateResult(&mut result, &mut interface)?;
292+
}
293+
result.ok()?;
294+
interface.ok_or_else(|| {
295+
windows::core::Error::new(
296+
Audio::AUDCLNT_E_DEVICE_INVALIDATED,
297+
"audio interface could not be retrieved during activation",
298+
)
299+
})
287300
}
288-
result.ok()?;
289-
interface.ok_or_else(|| {
290-
windows::core::Error::new(
291-
Audio::AUDCLNT_E_DEVICE_INVALIDATED,
292-
"audio interface could not be retrieved during activation",
293-
)
294-
})
295-
}
296301

297-
impl Audio::IActivateAudioInterfaceCompletionHandler_Impl for CompletionHandler_Impl {
298-
fn ActivateCompleted(
299-
&self,
300-
operation: windows::core::Ref<Audio::IActivateAudioInterfaceAsyncOperation>,
301-
) -> windows::core::Result<()> {
302-
let result = operation.ok().and_then(retrieve_result);
303-
let _ = self.0.send(result);
304-
Ok(())
302+
impl Audio::IActivateAudioInterfaceCompletionHandler_Impl for CompletionHandler_Impl {
303+
fn ActivateCompleted(
304+
&self,
305+
operation: windows::core::Ref<Audio::IActivateAudioInterfaceAsyncOperation>,
306+
) -> windows::core::Result<()> {
307+
let result = operation.ok().and_then(retrieve_result);
308+
let _ = self.0.send(result);
309+
Ok(())
310+
}
305311
}
306-
}
307312

308-
#[allow(non_snake_case)]
309-
unsafe fn ActivateAudioInterfaceSync<P0, T>(
310-
deviceinterfacepath: P0,
311-
activationparams: Option<*const StructuredStorage::PROPVARIANT>,
312-
) -> windows::core::Result<T>
313-
where
314-
P0: windows::core::Param<PCWSTR>,
315-
T: Interface,
316-
{
317313
let (sender, receiver) = std::sync::mpsc::channel();
318314
let completion: Audio::IActivateAudioInterfaceCompletionHandler =
319315
CompletionHandler(sender).into();
320316
Audio::ActivateAudioInterfaceAsync(
321317
deviceinterfacepath,
322-
&T::IID,
323-
activationparams,
318+
&Audio::IAudioClient::IID,
319+
None,
324320
&completion,
325321
)?;
326322
let result = receiver.recv_timeout(Duration::from_secs(2)).unwrap()?;
@@ -439,18 +435,25 @@ impl Device {
439435
return Ok(lock);
440436
}
441437

438+
// When using virtual default devices, we use `ActivateAudioInterfaceAsync` to get
439+
// an `IAudioClient` for the default output or input device. When retrieved this way,
440+
// streams will be automatically rerouted if the default device is changed.
441+
//
442+
// Otherwise, we use `Activate` to get an `IAudioClient` for the current device.
443+
444+
#[cfg(feature = "wasapi-virtual-default-devices")]
442445
let audio_client: Audio::IAudioClient = unsafe {
443446
match &self.device {
444447
DeviceType::DefaultOutput => {
445-
let default_audio = StringFromIID(&Audio::DEVINTERFACE_AUDIO_RENDER)?;
446-
let result = ActivateAudioInterfaceSync(PCWSTR(default_audio.as_ptr()), None);
447-
CoTaskMemFree(Some(default_audio.as_ptr() as _));
448+
let default_audio = Com::StringFromIID(&Audio::DEVINTERFACE_AUDIO_RENDER)?;
449+
let result = activate_audio_interface_sync(default_audio);
450+
Com::CoTaskMemFree(Some(default_audio.as_ptr() as _));
448451
result?
449452
}
450453
DeviceType::DefaultInput => {
451-
let default_audio = StringFromIID(&Audio::DEVINTERFACE_AUDIO_CAPTURE)?;
452-
let result = ActivateAudioInterfaceSync(PCWSTR(default_audio.as_ptr()), None);
453-
CoTaskMemFree(Some(default_audio.as_ptr() as _));
454+
let default_audio = Com::StringFromIID(&Audio::DEVINTERFACE_AUDIO_CAPTURE)?;
455+
let result = activate_audio_interface_sync(default_audio);
456+
Com::CoTaskMemFree(Some(default_audio.as_ptr() as _));
454457
result?
455458
}
456459
DeviceType::Specific(device) => {
@@ -461,6 +464,16 @@ impl Device {
461464
}
462465
};
463466

467+
#[cfg(not(feature = "wasapi-virtual-default-devices"))]
468+
let audio_client = unsafe {
469+
self.immdevice()
470+
.ok_or(windows::core::Error::new(
471+
Audio::AUDCLNT_E_DEVICE_INVALIDATED,
472+
"device not found while getting audio client",
473+
))?
474+
.Activate(Com::CLSCTX_ALL, None)?
475+
};
476+
464477
*lock = Some(IAudioClientWrapper(audio_client));
465478
Ok(lock)
466479
}

0 commit comments

Comments
 (0)