From 3dbbd316b83643cb1a833b25a06a897255891b4a Mon Sep 17 00:00:00 2001 From: zarik5 Date: Thu, 28 Jan 2021 21:32:44 +0100 Subject: [PATCH] Add back option for muting PC speakers --- Cargo.lock | 2 + alvr/client/src/connection.rs | 6 +- alvr/common/Cargo.toml | 2 + alvr/common/src/audio.rs | 98 ++++++++++++++++++++++++++ alvr/server/src/connection.rs | 127 +++++++++++++++++----------------- 5 files changed, 167 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a016c1d50e..fd17fd285a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,7 +128,9 @@ dependencies = [ "settings-schema", "tokio 1.1.0", "tokio-util 0.6.2", + "winapi 0.3.9", "winit", + "wio", ] [[package]] diff --git a/alvr/client/src/connection.rs b/alvr/client/src/connection.rs index 3518aa1bb8..f0e4b28656 100644 --- a/alvr/client/src/connection.rs +++ b/alvr/client/src/connection.rs @@ -345,7 +345,7 @@ async fn connection_pipeline( let (_destroy_stream_park_notifier, destroy_stream_park_receiver) = smpsc::channel::<()>(); // blocking streams park - std::thread::spawn(move || -> StrResult { + std::thread::spawn(move || { let mut _game_audio_stream_guard = if let Switch::Enabled(desc) = settings.audio.game_audio { Some(audio::AudioPlayer::start( @@ -369,13 +369,11 @@ async fn connection_pipeline( // notified when the notifier counterpart gets dropped destroy_stream_park_receiver.recv().ok(); - error!("drop streams park"); - // the microphone gets stuck on stop(), drop the game audio stream first drop(_game_audio_stream_guard); drop(_microphone_stream_guard); - Ok(()) + StrResult::Ok(()) }); let keepalive_sender_loop = { diff --git a/alvr/common/Cargo.toml b/alvr/common/Cargo.toml index 3fdf4ae69b..8e2d3f5078 100644 --- a/alvr/common/Cargo.toml +++ b/alvr/common/Cargo.toml @@ -36,6 +36,8 @@ runas = "0.2" msgbox = "0.6" [target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["mmdeviceapi", "objbase", "endpointvolume"] } +wio = "0.2" gfx-backend-dx11 = "0.6" [target.'cfg(target_os = "linux")'.dependencies] diff --git a/alvr/common/src/audio.rs b/alvr/common/src/audio.rs index ceb1b6f712..c60bef6e5a 100644 --- a/alvr/common/src/audio.rs +++ b/alvr/common/src/audio.rs @@ -6,6 +6,104 @@ use cpal::{ use std::{collections::VecDeque, sync::mpsc as smpsc}; use tokio::sync::mpsc as tmpsc; +#[cfg(windows)] +use std::ptr; +#[cfg(windows)] +use winapi::{ + shared::winerror::*, + um::{combaseapi::*, endpointvolume::IAudioEndpointVolume, mmdeviceapi::*, objbase::*}, + Class, Interface, +}; +#[cfg(windows)] +use wio::com::ComPtr; + +#[cfg(windows)] +pub fn set_mute_audio_device(device_index: Option, mute: bool) -> StrResult { + unsafe { + CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED); + + let mut mm_device_enumerator_ptr: *mut IMMDeviceEnumerator = ptr::null_mut(); + let hr = CoCreateInstance( + &MMDeviceEnumerator::uuidof(), + ptr::null_mut(), + CLSCTX_ALL, + &IMMDeviceEnumerator::uuidof(), + &mut mm_device_enumerator_ptr as *mut _ as _, + ); + if FAILED(hr) { + return fmt_e!( + "CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x{:08x}", + hr + ); + } + let mm_device_enumerator = ComPtr::from_raw(mm_device_enumerator_ptr); + + let mm_device = if let Some(index) = device_index { + let mut mm_device_collection_ptr: *mut IMMDeviceCollection = ptr::null_mut(); + let hr = mm_device_enumerator.EnumAudioEndpoints( + eRender, + DEVICE_STATE_ACTIVE, + &mut mm_device_collection_ptr as _, + ); + if FAILED(hr) { + return fmt_e!( + "IMMDeviceEnumerator::EnumAudioEndpoints failed: hr = 0x{:08x}", + hr + ); + } + let mm_device_collection = ComPtr::from_raw(mm_device_collection_ptr); + + let mut mm_device_ptr: *mut IMMDevice = ptr::null_mut(); + let hr = mm_device_collection.Item(index as _, &mut mm_device_ptr as _); + if FAILED(hr) { + return fmt_e!("IMMDeviceCollection::Item failed: hr = 0x{:08x}", hr); + } + + ComPtr::from_raw(mm_device_ptr) + } else { + let mut mm_device_ptr: *mut IMMDevice = ptr::null_mut(); + let hr = mm_device_enumerator.GetDefaultAudioEndpoint( + eRender, + eConsole, + &mut mm_device_ptr as *mut _, + ); + if hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) { + return fmt_e!("No default audio endpoint found. No audio device?"); + } + if FAILED(hr) { + return fmt_e!( + "IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x{:08x}", + hr + ); + } + + ComPtr::from_raw(mm_device_ptr) + }; + + let mut endpoint_volume_ptr: *mut IAudioEndpointVolume = ptr::null_mut(); + let hr = mm_device.Activate( + &IAudioEndpointVolume::uuidof(), + CLSCTX_ALL, + ptr::null_mut(), + &mut endpoint_volume_ptr as *mut _ as _, + ); + if FAILED(hr) { + return fmt_e!( + "IMMDevice::Activate() for IAudioEndpointVolume failed: hr = 0x{:08x}", + hr, + ); + } + let endpoint_volume = ComPtr::from_raw(endpoint_volume_ptr); + + let hr = endpoint_volume.SetMute(mute as _, ptr::null_mut()); + if FAILED(hr) { + return fmt_e!("Failed to mute audio device: hr = 0x{:08x}", hr,); + } + } + + Ok(()) +} + pub fn get_vb_cable_audio_device_index() -> StrResult> { let host = cpal::default_host(); diff --git a/alvr/server/src/connection.rs b/alvr/server/src/connection.rs index f8aa76ea7f..7d85585b3c 100644 --- a/alvr/server/src/connection.rs +++ b/alvr/server/src/connection.rs @@ -13,7 +13,7 @@ use std::{ }; use tokio::{ sync::{mpsc as tmpsc, Mutex}, - task, time, + time, }; const RETRY_CONNECT_INTERVAL: Duration = Duration::from_millis(500); @@ -332,12 +332,9 @@ impl Drop for StreamCloseGuard { crate::DeinitializeStreaming() }; - let on_disconnect_script = SESSION_MANAGER - .lock() - .get() - .to_settings() - .connection - .on_disconnect_script; + let settings = SESSION_MANAGER.lock().get().to_settings(); + + let on_disconnect_script = settings.connection.on_disconnect_script; if !on_disconnect_script.is_empty() { info!( "Running on disconnect script (disconnect): {}", @@ -402,67 +399,74 @@ async fn connection_pipeline() -> StrResult { let _stream_guard = StreamCloseGuard; - let (_destroy_game_audio_stream_notifier, destroy_game_audio_stream_receiver) = - smpsc::channel::<()>(); - - let game_audio_loop: BoxFuture<_> = if let Switch::Enabled(desc) = settings.audio.game_audio { - let (sender, mut receiver) = tmpsc::unbounded_channel(); + let (game_audio_sender, mut game_audio_receiver) = tmpsc::unbounded_channel(); + let game_audio_loop: BoxFuture<_> = if let Switch::Enabled(_) = settings.audio.game_audio { let control_sender = control_sender.clone(); + Box::pin(async move { + while let Some(data) = game_audio_receiver.recv().await { + control_sender + .lock() + .await + .send(&ServerControlPacket::ReservedBuffer(data)) + .await?; + } - // AudioSession is !Send, so keep it in a separate thread. - Box::pin(futures::future::join( - task::spawn_blocking(move || { - let _audio_stream_guard = Some(AudioSession::start_recording( + StrResult::Ok(()) + }) + } else { + Box::pin(future::pending()) + }; + + let (_destroy_stream_park_notifier, destroy_stream_park_receiver) = smpsc::channel::<()>(); + let (microphone_sender, microphone_receiver) = smpsc::channel(); + std::thread::spawn({ + let game_audio_desc = settings.audio.game_audio; + let microphone_desc = settings.audio.microphone; + move || { + let mut maybe_muted_device_index = None; + let _audio_stream_guard = if let Switch::Enabled(desc) = game_audio_desc { + #[cfg(windows)] + if desc.mute_when_streaming { + audio::set_mute_audio_device(desc.device_index, true)?; + maybe_muted_device_index = Some(desc.device_index); + } + + Some(AudioSession::start_recording( desc.device_index, true, 2, trace_err!(audio::get_output_sample_rate(desc.device_index))?, - sender, - )?); - - // notified when _destroy_game_audio_stream_notifier goes out of scope - destroy_game_audio_stream_receiver.recv().ok(); - StrResult::Ok(()) - }), - async move { - while let Some(data) = receiver.recv().await { - control_sender - .lock() - .await - .send(&ServerControlPacket::ReservedBuffer(data)) - .await?; - } + game_audio_sender, + )?) + } else { + None + }; - StrResult::Ok(()) - }, - )) - } else { - Box::pin(future::pending()) - }; + let _microphone_stream_guard = if let Switch::Enabled(desc) = microphone_desc { + let device_index = desc + .device_index + .or(trace_err!(audio::get_vb_cable_audio_device_index())?); + Some(AudioSession::start_playing( + device_index, + 1, + trace_err!(audio::get_output_sample_rate(device_index))?, + desc.buffer_range_multiplier, + microphone_receiver, + )?) + } else { + None + }; - let (_destroy_microphone_stream_notifier, destroy_microphone_stream_receiver) = - smpsc::channel::<()>(); - let (microphone_sender, microphone_receiver) = smpsc::channel(); + destroy_stream_park_receiver.recv().ok(); + + #[cfg(windows)] + if let Some(index) = maybe_muted_device_index { + audio::set_mute_audio_device(index, false)?; + } - let microphone_stream: BoxFuture<_> = if let Switch::Enabled(desc) = settings.audio.microphone { - Box::pin(task::spawn_blocking(move || { - let device_index = desc - .device_index - .or(trace_err!(audio::get_vb_cable_audio_device_index())?); - let _audio_stream_guard = Some(AudioSession::start_playing( - device_index, - 1, - trace_err!(audio::get_output_sample_rate(device_index))?, - desc.buffer_range_multiplier, - microphone_receiver, - )?); - - destroy_microphone_stream_receiver.recv().ok(); StrResult::Ok(()) - })) - } else { - Box::pin(future::pending()) - }; + } + }); let keepalive_sender_loop = { let control_sender = control_sender.clone(); @@ -536,12 +540,7 @@ async fn connection_pipeline() -> StrResult { Ok(()) } - res = microphone_stream => trace_err!(res)?, - (res1, res2) = game_audio_loop => { - trace_err!(res1)??; - res2?; - Ok(()) - } + res = game_audio_loop => res, res = keepalive_sender_loop => res, res = control_loop => res, }