[VCM] Track newly added microphones (#16199)

* [VCM] Track newly added microphones when [All] is selected in the settings

* [VCM] handle case when no mics are left

* fixup: fix crashes onNotify

* fixup: fix build
This commit is contained in:
Andrey Nekrasov 2022-03-21 12:48:11 +03:00 committed by GitHub
parent 2e3a2b3f96
commit 176f2c2870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 224 additions and 33 deletions

View File

@ -498,6 +498,7 @@ dxgi
dxgiformat dxgiformat
dxguid dxguid
ecount ecount
EData
EDITKEYBOARD EDITKEYBOARD
editkeyboardwindow editkeyboardwindow
editorconfig editorconfig
@ -527,6 +528,7 @@ EREOF
EResize EResize
ERRORMESSAGE ERRORMESSAGE
ERRORTITLE ERRORTITLE
ERole
ESettings ESettings
esize esize
esrp esrp
@ -1551,6 +1553,7 @@ programdata
PROGRAMFILES PROGRAMFILES
projectname projectname
PROPBAG PROPBAG
PROPERTYKEY
propkey propkey
propvarutil propvarutil
prvpane prvpane

View File

@ -132,7 +132,7 @@ public
auto names = gcnew List<String ^>(); auto names = gcnew List<String ^>();
for (const auto& device : MicrophoneDevice::getAllActive()) for (const auto& device : MicrophoneDevice::getAllActive())
{ {
names->Add(gcnew String(device.name().data())); names->Add(gcnew String(device->name().data()));
} }
return names; return names;
} }

View File

@ -0,0 +1,78 @@
#include "pch.h"
#include "AudioDeviceNotificationClient.h"
AudioDeviceNotificationClient::AudioDeviceNotificationClient()
{
(void)CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_deviceEnumerator));
if (!_deviceEnumerator)
{
return;
}
if (FAILED(_deviceEnumerator->RegisterEndpointNotificationCallback(this)))
{
_deviceEnumerator->Release();
_deviceEnumerator = nullptr;
}
}
AudioDeviceNotificationClient::~AudioDeviceNotificationClient()
{
if (!_deviceEnumerator)
{
return;
}
_deviceEnumerator->UnregisterEndpointNotificationCallback(this);
_deviceEnumerator->Release();
}
ULONG AudioDeviceNotificationClient::AddRef()
{
return 1;
}
ULONG AudioDeviceNotificationClient::Release()
{
return 1;
}
HRESULT AudioDeviceNotificationClient::QueryInterface(REFIID, void**)
{
return S_OK;
}
HRESULT AudioDeviceNotificationClient::OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY)
{
_deviceConfigurationChanged = true;
return S_OK;
}
HRESULT AudioDeviceNotificationClient::OnDeviceAdded(LPCWSTR)
{
_deviceConfigurationChanged = true;
return S_OK;
}
HRESULT AudioDeviceNotificationClient::OnDeviceRemoved(LPCWSTR)
{
_deviceConfigurationChanged = true;
return S_OK;
}
HRESULT AudioDeviceNotificationClient::OnDeviceStateChanged(LPCWSTR, DWORD)
{
_deviceConfigurationChanged = true;
return S_OK;
}
HRESULT AudioDeviceNotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR)
{
if (role == eConsole && (flow == eCapture || flow == eAll))
{
_deviceConfigurationChanged = true;
}
return S_OK;
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <MMDeviceAPI.h>
struct AudioDeviceNotificationClient : IMMNotificationClient
{
AudioDeviceNotificationClient();
~AudioDeviceNotificationClient();
bool PullPendingNotifications()
{
const bool result = _deviceConfigurationChanged;
_deviceConfigurationChanged = false;
return result;
}
private:
ULONG AddRef() override;
ULONG Release() override;
HRESULT QueryInterface(REFIID, void**) override;
HRESULT OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override;
HRESULT OnDeviceAdded(LPCWSTR) override;
HRESULT OnDeviceRemoved(LPCWSTR) override;
HRESULT OnDeviceStateChanged(LPCWSTR, DWORD) override;
HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override;
IMMDeviceEnumerator* _deviceEnumerator = nullptr;
bool _deviceConfigurationChanged = false;
};

View File

@ -126,6 +126,12 @@ LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARA
} }
case WM_TIMER: case WM_TIMER:
{ {
if (toolbar->audioConfChangesNotifier.PullPendingNotifications())
{
instance->onMicrophoneConfigurationChanged();
}
toolbar->microphoneMuted = instance->getMicrophoneMuteState();
if (toolbar->generalSettingsUpdateScheduled) if (toolbar->generalSettingsUpdateScheduled)
{ {
instance->onGeneralSettingsChanged(); instance->onGeneralSettingsChanged();

View File

@ -6,6 +6,8 @@
#include <common/Display/monitors.h> #include <common/Display/monitors.h>
#include "AudioDeviceNotificationClient.h"
struct ToolbarImages struct ToolbarImages
{ {
Gdiplus::Image* camOnMicOn = nullptr; Gdiplus::Image* camOnMicOn = nullptr;
@ -43,6 +45,7 @@ private:
ToolbarImages darkImages; ToolbarImages darkImages;
ToolbarImages lightImages; ToolbarImages lightImages;
AudioDeviceNotificationClient audioConfChangesNotifier;
bool cameraMuted = false; bool cameraMuted = false;
bool cameraInUse = false; bool cameraInUse = false;

View File

@ -111,6 +111,7 @@ xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
</PostBuildEvent> </PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="AudioDeviceNotificationClient.h" />
<ClInclude Include="framework.h" /> <ClInclude Include="framework.h" />
<ClInclude Include="Toolbar.h" /> <ClInclude Include="Toolbar.h" />
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />
@ -118,6 +119,7 @@ xcopy /y /I "$(ProjectDir)black.bmp*" "$(OutDir)"</Command>
<ClInclude Include="VideoConferenceModule.h" /> <ClInclude Include="VideoConferenceModule.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="AudioDeviceNotificationClient.cpp" />
<ClCompile Include="dllmain.cpp" /> <ClCompile Include="dllmain.cpp" />
<ClCompile Include="trace.cpp" /> <ClCompile Include="trace.cpp" />
<ClCompile Include="Toolbar.cpp" /> <ClCompile Include="Toolbar.cpp" />

View File

@ -6,6 +6,7 @@
<ClCompile Include="pch.cpp" /> <ClCompile Include="pch.cpp" />
<ClCompile Include="VideoConferenceModule.cpp" /> <ClCompile Include="VideoConferenceModule.cpp" />
<ClCompile Include="trace.cpp" /> <ClCompile Include="trace.cpp" />
<ClCompile Include="AudioDeviceNotificationClient.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="framework.h" /> <ClInclude Include="framework.h" />
@ -13,6 +14,7 @@
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />
<ClInclude Include="VideoConferenceModule.h" /> <ClInclude Include="VideoConferenceModule.h" />
<ClInclude Include="trace.h" /> <ClInclude Include="trace.h" />
<ClInclude Include="AudioDeviceNotificationClient.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="black.bmp" /> <Image Include="black.bmp" />

View File

@ -8,6 +8,7 @@
#include <shellapi.h> #include <shellapi.h>
#include <filesystem> #include <filesystem>
#include <unordered_set>
#include <common/debug_control.h> #include <common/debug_control.h>
#include <common/SettingsAPI/settings_helpers.h> #include <common/SettingsAPI/settings_helpers.h>
@ -51,20 +52,22 @@ void VideoConferenceModule::reverseMicrophoneMute()
bool muted = false; bool muted = false;
for (auto& controlledMic : instance->_controlledMicrophones) for (auto& controlledMic : instance->_controlledMicrophones)
{ {
const bool was_muted = controlledMic.muted(); const bool was_muted = controlledMic->muted();
controlledMic.toggle_muted(); controlledMic->toggle_muted();
muted = muted || !was_muted; muted = muted || !was_muted;
} }
if (muted) if (muted)
{ {
Trace::MicrophoneMuted(); Trace::MicrophoneMuted();
} }
instance->_mic_muted_state_during_disconnect = !instance->_mic_muted_state_during_disconnect;
toolbar.setMicrophoneMute(muted); toolbar.setMicrophoneMute(muted);
} }
bool VideoConferenceModule::getMicrophoneMuteState() bool VideoConferenceModule::getMicrophoneMuteState()
{ {
return instance->_microphoneTrackedInUI ? instance->_microphoneTrackedInUI->muted() : false; return instance->_microphoneTrackedInUI ? instance->_microphoneTrackedInUI->muted() : instance->_mic_muted_state_during_disconnect;
} }
void VideoConferenceModule::reverseVirtualCameraMuteState() void VideoConferenceModule::reverseVirtualCameraMuteState()
@ -268,6 +271,46 @@ void VideoConferenceModule::onModuleSettingsChanged()
} }
} }
void VideoConferenceModule::onMicrophoneConfigurationChanged()
{
if (!_controllingAllMics)
{
// Don't care if we don't control all the mics
return;
}
const bool mutedStateForNewMics = _microphoneTrackedInUI ? _microphoneTrackedInUI->muted() : _mic_muted_state_during_disconnect;
std::unordered_set<std::wstring_view> currentlyTrackedMicsIds;
for (const auto& controlledMic : _controlledMicrophones)
{
currentlyTrackedMicsIds.emplace(controlledMic->id());
}
auto allMics = MicrophoneDevice::getAllActive();
for (auto& newMic : allMics)
{
if (currentlyTrackedMicsIds.contains(newMic->id()))
{
continue;
}
if (mutedStateForNewMics)
{
newMic->set_muted(true);
}
_controlledMicrophones.emplace_back(std::move(newMic));
}
// Restore invalidated pointer
_microphoneTrackedInUI = controlledDefaultMic();
if (_microphoneTrackedInUI)
{
_microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
toolbar.setMicrophoneMute(muted);
});
}
}
VideoConferenceModule::VideoConferenceModule() : VideoConferenceModule::VideoConferenceModule() :
_generalSettingsWatcher{ PTSettingsHelper::get_powertoys_general_save_file_location(), [this] { _generalSettingsWatcher{ PTSettingsHelper::get_powertoys_general_save_file_location(), [this] {
toolbar.scheduleGeneralSettingsUpdate(); toolbar.scheduleGeneralSettingsUpdate();
@ -388,47 +431,56 @@ void VideoConferenceModule::updateControlledMicrophones(const std::wstring_view
{ {
for (auto& controlledMic : _controlledMicrophones) for (auto& controlledMic : _controlledMicrophones)
{ {
controlledMic.set_muted(false); controlledMic->set_muted(false);
} }
_controlledMicrophones.clear(); _controlledMicrophones.clear();
_microphoneTrackedInUI = nullptr; _microphoneTrackedInUI = nullptr;
auto allMics = MicrophoneDevice::getAllActive(); auto allMics = MicrophoneDevice::getAllActive();
if (new_mic == L"[All]") if (new_mic == L"[All]")
{ {
_controllingAllMics = true;
_controlledMicrophones = std::move(allMics); _controlledMicrophones = std::move(allMics);
if (auto defaultMic = MicrophoneDevice::getDefault()) _microphoneTrackedInUI = controlledDefaultMic();
{
for (auto& controlledMic : _controlledMicrophones)
{
if (controlledMic.id() == defaultMic->id())
{
_microphoneTrackedInUI = &controlledMic;
break;
}
}
}
} }
else else
{ {
_controllingAllMics = false;
for (auto& controlledMic : allMics) for (auto& controlledMic : allMics)
{ {
if (controlledMic.name() == new_mic) if (controlledMic->name() == new_mic)
{ {
_controlledMicrophones.emplace_back(std::move(controlledMic)); _controlledMicrophones.emplace_back(std::move(controlledMic));
_microphoneTrackedInUI = &_controlledMicrophones[0]; _microphoneTrackedInUI = _controlledMicrophones[0].get();
break; break;
} }
} }
} }
if (_microphoneTrackedInUI) if (_microphoneTrackedInUI)
{ {
_microphoneTrackedInUI->set_mute_changed_callback([&](const bool muted) { _microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
toolbar.setMicrophoneMute(muted); toolbar.setMicrophoneMute(muted);
}); });
toolbar.setMicrophoneMute(_microphoneTrackedInUI->muted()); toolbar.setMicrophoneMute(_microphoneTrackedInUI->muted());
} }
} }
MicrophoneDevice* VideoConferenceModule::controlledDefaultMic()
{
if (auto defaultMic = MicrophoneDevice::getDefault())
{
for (auto& controlledMic : _controlledMicrophones)
{
if (controlledMic->id() == defaultMic->id())
{
return controlledMic.get();
}
}
}
return nullptr;
}
void toggleProxyCamRegistration(const bool enable) void toggleProxyCamRegistration(const bool enable)
{ {
if (!is_process_elevated()) if (!is_process_elevated())

View File

@ -61,10 +61,14 @@ public:
void onGeneralSettingsChanged(); void onGeneralSettingsChanged();
void onModuleSettingsChanged(); void onModuleSettingsChanged();
void onMicrophoneConfigurationChanged();
private: private:
void init_settings(); void init_settings();
void updateControlledMicrophones(const std::wstring_view new_mic); void updateControlledMicrophones(const std::wstring_view new_mic);
MicrophoneDevice* controlledDefaultMic();
// all callback methods and used by callback have to be static // all callback methods and used by callback have to be static
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
static bool isKeyPressed(unsigned int keyCode); static bool isKeyPressed(unsigned int keyCode);
@ -73,7 +77,9 @@ private:
static HHOOK hook_handle; static HHOOK hook_handle;
bool _enabled = false; bool _enabled = false;
std::vector<MicrophoneDevice> _controlledMicrophones; bool _mic_muted_state_during_disconnect = false;
bool _controllingAllMics = false;
std::vector<std::unique_ptr<MicrophoneDevice>> _controlledMicrophones;
MicrophoneDevice* _microphoneTrackedInUI = nullptr; MicrophoneDevice* _microphoneTrackedInUI = nullptr;
std::optional<SerializedSharedMemory> _imageOverlayChannel; std::optional<SerializedSharedMemory> _imageOverlayChannel;

View File

@ -869,6 +869,13 @@ VideoCaptureProxyFilter::~VideoCaptureProxyFilter()
_worker_cv.notify_one(); _worker_cv.notify_one();
_worker_thread.join(); _worker_thread.join();
if (_settingsUpdateChannel)
{
_settingsUpdateChannel->access([](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
settings->cameraInUse = false;
});
}
} }
VideoCaptureProxyFilter::SyncedSettings VideoCaptureProxyFilter::SyncCurrentSettings() VideoCaptureProxyFilter::SyncedSettings VideoCaptureProxyFilter::SyncCurrentSettings()

View File

@ -76,34 +76,34 @@ void MicrophoneDevice::set_mute_changed_callback(mute_changed_cb_t callback) noe
_endpoint->RegisterControlChangeNotify(_notifier.get()); _endpoint->RegisterControlChangeNotify(_notifier.get());
} }
std::optional<MicrophoneDevice> MicrophoneDevice::getDefault() std::unique_ptr<MicrophoneDevice> MicrophoneDevice::getDefault()
{ {
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>(); auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
if (!deviceEnumerator) if (!deviceEnumerator)
{ {
LOG("MicrophoneDevice::getDefault MMDeviceEnumerator returned null"); LOG("MicrophoneDevice::getDefault MMDeviceEnumerator returned null");
return std::nullopt; return nullptr;
} }
wil::com_ptr_nothrow<IMMDevice> captureDevice; wil::com_ptr_nothrow<IMMDevice> captureDevice;
deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice); deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice);
if (!captureDevice) if (!captureDevice)
{ {
LOG("MicrophoneDevice::getDefault captureDevice is null"); LOG("MicrophoneDevice::getDefault captureDevice is null");
return std::nullopt; return nullptr;
} }
wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint; wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint;
captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(&microphoneEndpoint)); captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(&microphoneEndpoint));
if (!microphoneEndpoint) if (!microphoneEndpoint)
{ {
LOG("MicrophoneDevice::getDefault captureDevice is null"); LOG("MicrophoneDevice::getDefault captureDevice is null");
return std::nullopt; return nullptr;
} }
return std::make_optional<MicrophoneDevice>(std::move(captureDevice), std::move(microphoneEndpoint)); return std::make_unique<MicrophoneDevice>(std::move(captureDevice), std::move(microphoneEndpoint));
} }
std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive() std::vector<std::unique_ptr<MicrophoneDevice>> MicrophoneDevice::getAllActive()
{ {
std::vector<MicrophoneDevice> microphoneDevices; std::vector<std::unique_ptr<MicrophoneDevice>> microphoneDevices;
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>(); auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
if (!deviceEnumerator) if (!deviceEnumerator)
{ {
@ -135,7 +135,7 @@ std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive()
{ {
continue; continue;
} }
microphoneDevices.emplace_back(std::move(device), std::move(microphoneEndpoint)); microphoneDevices.push_back(std::make_unique<MicrophoneDevice>(std::move(device), std::move(microphoneEndpoint)));
} }
return microphoneDevices; return microphoneDevices;
} }
@ -147,6 +147,8 @@ MicrophoneDevice::VolumeNotifier::VolumeNotifier(MicrophoneDevice* subscribedDev
HRESULT __stdcall MicrophoneDevice::VolumeNotifier::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data) HRESULT __stdcall MicrophoneDevice::VolumeNotifier::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA data)
{ {
_subscribedDevice->_mute_changed_callback(data->bMuted); if (_subscribedDevice && _subscribedDevice->_mute_changed_callback)
_subscribedDevice->_mute_changed_callback(data->bMuted);
return S_OK; return S_OK;
} }

View File

@ -10,7 +10,6 @@
#include <wil/resource.h> #include <wil/resource.h>
#include <wil/com.h> #include <wil/com.h>
#include <string_view> #include <string_view>
#include <optional> #include <optional>
@ -47,7 +46,8 @@ private:
constexpr static inline std::wstring_view FALLBACK_ID = L"UNKNOWN_ID"; constexpr static inline std::wstring_view FALLBACK_ID = L"UNKNOWN_ID";
public: public:
MicrophoneDevice(MicrophoneDevice&&) noexcept = default; MicrophoneDevice(MicrophoneDevice&&) noexcept = delete;
MicrophoneDevice(const MicrophoneDevice&) noexcept = delete;
MicrophoneDevice(wil::com_ptr_nothrow<IMMDevice> device, wil::com_ptr_nothrow<IAudioEndpointVolume> endpoint); MicrophoneDevice(wil::com_ptr_nothrow<IMMDevice> device, wil::com_ptr_nothrow<IAudioEndpointVolume> endpoint);
~MicrophoneDevice(); ~MicrophoneDevice();
@ -60,6 +60,6 @@ public:
std::wstring_view name() const noexcept; std::wstring_view name() const noexcept;
void set_mute_changed_callback(mute_changed_cb_t callback) noexcept; void set_mute_changed_callback(mute_changed_cb_t callback) noexcept;
static std::optional<MicrophoneDevice> getDefault(); static std::unique_ptr<MicrophoneDevice> getDefault();
static std::vector<MicrophoneDevice> getAllActive(); static std::vector<std::unique_ptr<MicrophoneDevice>> getAllActive();
}; };