[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
dxguid
ecount
EData
EDITKEYBOARD
editkeyboardwindow
editorconfig
@ -527,6 +528,7 @@ EREOF
EResize
ERRORMESSAGE
ERRORTITLE
ERole
ESettings
esize
esrp
@ -1551,6 +1553,7 @@ programdata
PROGRAMFILES
projectname
PROPBAG
PROPERTYKEY
propkey
propvarutil
prvpane

View File

@ -132,7 +132,7 @@ public
auto names = gcnew List<String ^>();
for (const auto& device : MicrophoneDevice::getAllActive())
{
names->Add(gcnew String(device.name().data()));
names->Add(gcnew String(device->name().data()));
}
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:
{
if (toolbar->audioConfChangesNotifier.PullPendingNotifications())
{
instance->onMicrophoneConfigurationChanged();
}
toolbar->microphoneMuted = instance->getMicrophoneMuteState();
if (toolbar->generalSettingsUpdateScheduled)
{
instance->onGeneralSettingsChanged();

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@
#include <shellapi.h>
#include <filesystem>
#include <unordered_set>
#include <common/debug_control.h>
#include <common/SettingsAPI/settings_helpers.h>
@ -51,20 +52,22 @@ void VideoConferenceModule::reverseMicrophoneMute()
bool muted = false;
for (auto& controlledMic : instance->_controlledMicrophones)
{
const bool was_muted = controlledMic.muted();
controlledMic.toggle_muted();
const bool was_muted = controlledMic->muted();
controlledMic->toggle_muted();
muted = muted || !was_muted;
}
if (muted)
{
Trace::MicrophoneMuted();
}
instance->_mic_muted_state_during_disconnect = !instance->_mic_muted_state_during_disconnect;
toolbar.setMicrophoneMute(muted);
}
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()
@ -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() :
_generalSettingsWatcher{ PTSettingsHelper::get_powertoys_general_save_file_location(), [this] {
toolbar.scheduleGeneralSettingsUpdate();
@ -388,47 +431,56 @@ void VideoConferenceModule::updateControlledMicrophones(const std::wstring_view
{
for (auto& controlledMic : _controlledMicrophones)
{
controlledMic.set_muted(false);
controlledMic->set_muted(false);
}
_controlledMicrophones.clear();
_microphoneTrackedInUI = nullptr;
auto allMics = MicrophoneDevice::getAllActive();
if (new_mic == L"[All]")
{
_controllingAllMics = true;
_controlledMicrophones = std::move(allMics);
if (auto defaultMic = MicrophoneDevice::getDefault())
{
for (auto& controlledMic : _controlledMicrophones)
{
if (controlledMic.id() == defaultMic->id())
{
_microphoneTrackedInUI = &controlledMic;
break;
}
}
}
_microphoneTrackedInUI = controlledDefaultMic();
}
else
{
_controllingAllMics = false;
for (auto& controlledMic : allMics)
{
if (controlledMic.name() == new_mic)
if (controlledMic->name() == new_mic)
{
_controlledMicrophones.emplace_back(std::move(controlledMic));
_microphoneTrackedInUI = &_controlledMicrophones[0];
_microphoneTrackedInUI = _controlledMicrophones[0].get();
break;
}
}
}
if (_microphoneTrackedInUI)
{
_microphoneTrackedInUI->set_mute_changed_callback([&](const bool muted) {
_microphoneTrackedInUI->set_mute_changed_callback([](const bool muted) {
toolbar.setMicrophoneMute(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)
{
if (!is_process_elevated())

View File

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

View File

@ -869,6 +869,13 @@ VideoCaptureProxyFilter::~VideoCaptureProxyFilter()
_worker_cv.notify_one();
_worker_thread.join();
if (_settingsUpdateChannel)
{
_settingsUpdateChannel->access([](auto settingsMemory) {
auto settings = reinterpret_cast<CameraSettingsUpdateChannel*>(settingsMemory._data);
settings->cameraInUse = false;
});
}
}
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());
}
std::optional<MicrophoneDevice> MicrophoneDevice::getDefault()
std::unique_ptr<MicrophoneDevice> MicrophoneDevice::getDefault()
{
auto deviceEnumerator = wil::CoCreateInstanceNoThrow<MMDeviceEnumerator, IMMDeviceEnumerator>();
if (!deviceEnumerator)
{
LOG("MicrophoneDevice::getDefault MMDeviceEnumerator returned null");
return std::nullopt;
return nullptr;
}
wil::com_ptr_nothrow<IMMDevice> captureDevice;
deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &captureDevice);
if (!captureDevice)
{
LOG("MicrophoneDevice::getDefault captureDevice is null");
return std::nullopt;
return nullptr;
}
wil::com_ptr_nothrow<IAudioEndpointVolume> microphoneEndpoint;
captureDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast<LPVOID*>(&microphoneEndpoint));
if (!microphoneEndpoint)
{
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>();
if (!deviceEnumerator)
{
@ -135,7 +135,7 @@ std::vector<MicrophoneDevice> MicrophoneDevice::getAllActive()
{
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;
}
@ -147,6 +147,8 @@ MicrophoneDevice::VolumeNotifier::VolumeNotifier(MicrophoneDevice* subscribedDev
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;
}

View File

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