From b621d1266c8b772838124ed2bbe3923d86f6417d Mon Sep 17 00:00:00 2001 From: Asuro Date: Fri, 15 Jul 2022 02:34:34 +0200 Subject: [PATCH] live update --- AsuroTool/ApplicationData.cpp | 42 ++++++- AsuroTool/ApplicationData.h | 6 + AsuroTool/AsuroTool.cpp | 29 ++++- AsuroTool/AsuroTool.vcxproj | 2 + AsuroTool/AsuroTool.vcxproj.filters | 24 +++- AsuroTool/AudioApi.cpp | 80 ++++++------- AsuroTool/AudioApi.h | 2 +- AsuroTool/AudioNotificationListener.cpp | 150 ++++++++++++++++++++++++ AsuroTool/AudioNotificationListener.h | 23 ++++ 9 files changed, 303 insertions(+), 55 deletions(-) create mode 100644 AsuroTool/AudioNotificationListener.cpp create mode 100644 AsuroTool/AudioNotificationListener.h diff --git a/AsuroTool/ApplicationData.cpp b/AsuroTool/ApplicationData.cpp index a19c1cf..8f22789 100644 --- a/AsuroTool/ApplicationData.cpp +++ b/AsuroTool/ApplicationData.cpp @@ -1,9 +1,49 @@ #include "ApplicationData.h" +AudioDevice::AudioDevice() +{ +} + +AudioDevice::AudioDevice(AudioDevice&& other) noexcept + : device(other.device), volumeInterface(other.volumeInterface), meterInterface(other.meterInterface), + id(other.id), name(other.name), state(other.state), + isDefaultConsole(other.isDefaultConsole), isDefaultMedia(other.isDefaultMedia), isDefaultCommunication(other.isDefaultCommunication) +{ + other.device = nullptr; + other.volumeInterface = nullptr; + other.meterInterface = nullptr; +} + +AudioDevice& AudioDevice::operator=(AudioDevice&& other) noexcept +{ + this->device = other.device; + this->volumeInterface = other.volumeInterface; + this->meterInterface = other.meterInterface; + this->id = other.id; + this->name = other.name; + this->state = other.state; + this->isDefaultConsole = other.isDefaultConsole; + this->isDefaultMedia = other.isDefaultMedia; + this->isDefaultCommunication = other.isDefaultCommunication; + + other.device = nullptr; + other.volumeInterface = nullptr; + other.meterInterface = nullptr; + return *this; +} + AudioDevice::~AudioDevice() { + if (volumeInterface) + { + volumeInterface->Release(); + } + if (meterInterface) + { + meterInterface->Release(); + } if (device) { - //device->Release(); + device->Release(); } } diff --git a/AsuroTool/ApplicationData.h b/AsuroTool/ApplicationData.h index 3d42430..b899e4b 100644 --- a/AsuroTool/ApplicationData.h +++ b/AsuroTool/ApplicationData.h @@ -4,6 +4,7 @@ #include #include #include +#include "AudioNotificationListener.h" class AudioDevice { public: @@ -17,6 +18,9 @@ public: bool isDefaultMedia = {}; bool isDefaultCommunication = {}; + AudioDevice(); + AudioDevice(AudioDevice&& other) noexcept; + AudioDevice& operator=(AudioDevice&& other) noexcept; ~AudioDevice(); }; @@ -31,4 +35,6 @@ public: ApplicationSettings settings = {}; std::vector playbackDevices = {}; std::vector recordingDevices = {}; + IMMDeviceEnumerator* deviceEnumerator = nullptr; + AudioNotificationListener* audioNotificationListener = nullptr; }; diff --git a/AsuroTool/AsuroTool.cpp b/AsuroTool/AsuroTool.cpp index 6ed6ae2..1ee9386 100644 --- a/AsuroTool/AsuroTool.cpp +++ b/AsuroTool/AsuroTool.cpp @@ -36,8 +36,16 @@ void init(DrawData& drawData, void* customData) io.Fonts->AddFontFromFileTTF("remixicon.ttf", 14.0f, &icons_config, icons_ranges); // Set up audio device api - HRESULT initResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); - isError(initResult, "Failed to initialize COM: "); + HRESULT audioResult; + audioResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); + isError(audioResult, "Failed to initialize COM: "); + + audioResult = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&appData->deviceEnumerator)); + isError(audioResult, "Failed to set up audio device enumerator: "); + + appData->audioNotificationListener = new AudioNotificationListener(appData); + audioResult = appData->deviceEnumerator->RegisterEndpointNotificationCallback(appData->audioNotificationListener); + isError(audioResult, "Failed to register audio notification listener: "); reloadDeviceLists(appData); @@ -54,20 +62,24 @@ void draw(DrawData& drawData, void* customData) { ApplicationData* appData = static_cast(customData); float customYCursor = 0; + ImVec2 viewportSize = ImGui::GetMainViewport()->Size; + + // Menu Bar customYCursor += menuBar(appData).y; - ImVec2 containingSize = ImGui::GetMainViewport()->Size; - + // Playback Devices ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); - ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0)); + ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0)); customYCursor += audioDeviceWindow(appData, appData->playbackDevices, " \xEE\xB8\x84 Playback").y; customYCursor += 5.; + // Recording devices ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); - ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0)); + ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0)); customYCursor += audioDeviceWindow(appData, appData->recordingDevices, " \xEE\xBD\x8F Recording").y; + // Resize viewport if (appData->settings.fitWindowHeight) { drawData.window_size.y = customYCursor; @@ -150,14 +162,18 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector& dev ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.)); } + // Device Name ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(dev.name.c_str()); + // Volume ImGui::TableNextColumn(); if (dev.state == DEVICE_STATE_ACTIVE) { + + float volume = log10f(getMeterValue(dev.meterInterface) * 9. + 1.); auto drawList = ImGui::GetWindowDrawList(); @@ -170,6 +186,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector& dev drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x * volume, lineY), IM_COL32(200, 200, 255, 255), 3.); } + // Defaults ImGui::TableNextColumn(); if (dev.state == DEVICE_STATE_ACTIVE) { diff --git a/AsuroTool/AsuroTool.vcxproj b/AsuroTool/AsuroTool.vcxproj index 3bccf41..77f6d6f 100644 --- a/AsuroTool/AsuroTool.vcxproj +++ b/AsuroTool/AsuroTool.vcxproj @@ -145,6 +145,7 @@ + @@ -152,6 +153,7 @@ + diff --git a/AsuroTool/AsuroTool.vcxproj.filters b/AsuroTool/AsuroTool.vcxproj.filters index 0ff0079..311795c 100644 --- a/AsuroTool/AsuroTool.vcxproj.filters +++ b/AsuroTool/AsuroTool.vcxproj.filters @@ -13,20 +13,29 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {37b6f2f7-f9d0-428e-9a8f-4b034610a39a} + + + {b65d213d-ddf6-4816-90d1-bf0811a51abf} + Source Files - - Source Files - Source Files Source Files + + Source Files\Audio + + + Source Files\Audio + @@ -38,15 +47,18 @@ Header Files - - Header Files - Header Files Header Files + + Header Files\Audio + + + Header Files\Audio + diff --git a/AsuroTool/AudioApi.cpp b/AsuroTool/AudioApi.cpp index fe44616..425adc7 100644 --- a/AsuroTool/AudioApi.cpp +++ b/AsuroTool/AudioApi.cpp @@ -38,18 +38,14 @@ void setDefaultAudioDevice(ApplicationData* appData, const wchar_t* deviceId, ER } } -void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType) +void loadAudioDevices(ApplicationData* appData, std::vector& deviceList, EDataFlow deviceType) { deviceList.clear(); HRESULT err; - IMMDeviceEnumerator* deviceEnumerator = NULL; IMMDeviceCollection* deviceCollection = NULL; - err = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)); - if (isError(err, "Failed to set up audio device enumerator: ")) return; - - err = deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection); + err = appData->deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection); if (isError(err, "Failed to enumerate audio devices: ")) return; UINT deviceCount; @@ -58,7 +54,7 @@ void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType IMMDevice* defaultConsoleDevice = NULL; LPWSTR defaultConsoleId = nullptr; - err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice); + err = appData->deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice); if (!FAILED(err)) { defaultConsoleDevice->GetId(&defaultConsoleId); @@ -66,7 +62,7 @@ void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType IMMDevice* defaultMediaOutput = NULL; LPWSTR defaultMediaId = nullptr; - err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput); + err = appData->deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput); if (!FAILED(err)) { defaultMediaOutput->GetId(&defaultMediaId); @@ -74,7 +70,7 @@ void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType IMMDevice* defaultCommunicationOutput = NULL; LPWSTR defaultCommunicationId = nullptr; - err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput); + err = appData->deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput); if (!FAILED(err)) { defaultCommunicationOutput->GetId(&defaultCommunicationId); @@ -82,48 +78,54 @@ void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType for (UINT i = 0; i < deviceCount; i += 1) { - IMMDevice* device; - err = deviceCollection->Item(i, &device); - isError(err, std::stringstream("Failed to get device ") << i << ": "); + AudioDevice deviceData{}; + + err = deviceCollection->Item(i, &deviceData.device); + if (isError(err, std::stringstream("Failed to get device ") << i << ": ")) + { + continue; + } LPWSTR deviceId; - err = device->GetId(&deviceId); + err = deviceData.device->GetId(&deviceId); isError(err, std::stringstream("Failed to get device id ") << i << ": "); + deviceData.id = std::wstring(deviceId); IPropertyStore* propertyStore; - err = device->OpenPropertyStore(STGM_READ, &propertyStore); + err = deviceData.device->OpenPropertyStore(STGM_READ, &propertyStore); isError(err, std::stringstream("Failed to open device ") << i << "prop store: "); PROPVARIANT deviceNameProp; const wchar_t* deviceName; err = getDevicePropertyString(propertyStore, PKEY_Device_FriendlyName, &deviceNameProp, deviceName); isError(err, std::stringstream("Failed to read name of device ") << i << ": "); + deviceData.name = utf8Encode(deviceName); - DWORD deviceState; - err = device->GetState(&deviceState); + err = deviceData.device->GetState(&deviceData.state); isError(err, std::stringstream("Failed to reat state of device ") << i << ": "); - - IAudioEndpointVolume* volumeInterface; - err = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&volumeInterface); + + err = deviceData.device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.volumeInterface); isError(err, "Failed to get audio endpoint volume interface: "); IAudioMeterInformation* meterInterface; - err = device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&meterInterface); + err = deviceData.device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.meterInterface); isError(err, "Failed to get audio meter interface: "); - deviceList.push_back({ - device, - volumeInterface, - meterInterface, - std::wstring(deviceId), - utf8Encode(deviceName), - deviceState, - utf8Encode(defaultConsoleId) == utf8Encode(deviceId), - utf8Encode(defaultMediaId) == utf8Encode(deviceId), - utf8Encode(defaultCommunicationId) == utf8Encode(deviceId), - }); + if (defaultConsoleId) + { + deviceData.isDefaultConsole = wcscmp(defaultConsoleId, deviceId) == 0; + } + if (defaultMediaId) + { + deviceData.isDefaultMedia = wcscmp(defaultMediaId, deviceId) == 0; + } + if (defaultCommunicationId) + { + deviceData.isDefaultCommunication = wcscmp(defaultCommunicationId, deviceId) == 0; + } + + deviceList.push_back(std::move(deviceData)); - // Free stuff if (propertyStore) { propertyStore->Release(); @@ -131,22 +133,18 @@ void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType CoTaskMemFree(deviceId); } - if (deviceEnumerator) - { - deviceEnumerator->Release(); - } + std::sort(deviceList.begin(), deviceList.end(), [](AudioDevice& a, AudioDevice& b) { return a.state < b.state; }); + if (deviceCollection) { deviceCollection->Release(); } - - std::sort(deviceList.begin(), deviceList.end(), [](AudioDevice& a, AudioDevice& b) { return a.state < b.state; }); } void reloadDeviceLists(ApplicationData* appData) { - loadAudioDevices(appData->playbackDevices, EDataFlow::eRender); - loadAudioDevices(appData->recordingDevices, EDataFlow::eCapture); + loadAudioDevices(appData, appData->playbackDevices, EDataFlow::eRender); + loadAudioDevices(appData, appData->recordingDevices, EDataFlow::eCapture); } float getVolume(IAudioEndpointVolume* volumeInterface) @@ -169,4 +167,4 @@ float getMeterValue(IAudioMeterInformation* meterInterface) } return volume; -} \ No newline at end of file +} diff --git a/AsuroTool/AudioApi.h b/AsuroTool/AudioApi.h index e61c70b..3a1bf1b 100644 --- a/AsuroTool/AudioApi.h +++ b/AsuroTool/AudioApi.h @@ -7,7 +7,7 @@ HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData); HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr = L"Unknown"); void setDefaultAudioDevice(ApplicationData* appData, const wchar_t* deviceId, ERole role); -void loadAudioDevices(std::vector& deviceList, EDataFlow deviceType); +void loadAudioDevices(ApplicationData* appData, std::vector& deviceList, EDataFlow deviceType); void reloadDeviceLists(ApplicationData* appData); float getVolume(IAudioEndpointVolume* volumeInterface); float getMeterValue(IAudioMeterInformation* meterInterface); diff --git a/AsuroTool/AudioNotificationListener.cpp b/AsuroTool/AudioNotificationListener.cpp new file mode 100644 index 0000000..f62939e --- /dev/null +++ b/AsuroTool/AudioNotificationListener.cpp @@ -0,0 +1,150 @@ +#include + +#include "AudioApi.h" +#include "AudioNotificationListener.h" + + +AudioNotificationListener::AudioNotificationListener(ApplicationData* appData) : appData(appData) +{ + +} + +HRESULT __stdcall AudioNotificationListener::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) +{ + auto updateDevice = [pwstrDeviceId, dwNewState](std::vector& deviceList) + { + for (auto& audioDevice : deviceList) + { + if (wcscmp(audioDevice.id.c_str(), pwstrDeviceId) == 0) + { + audioDevice.state = dwNewState; + } + } + }; + + updateDevice(appData->playbackDevices); + updateDevice(appData->recordingDevices); + + return S_OK; +} + +HRESULT __stdcall AudioNotificationListener::OnDeviceAdded(LPCWSTR pwstrDeviceId) +{ + IMMDevice* newDevice; + HRESULT result = appData->deviceEnumerator->GetDevice(pwstrDeviceId, &newDevice); + if (SUCCEEDED(result) && newDevice != nullptr) + { + // TODO: add to device list + } + + return result; +} + +HRESULT __stdcall AudioNotificationListener::OnDeviceRemoved(LPCWSTR pwstrDeviceId) +{ + auto deleteDevice = [pwstrDeviceId](std::vector& deviceList) + { + auto deviceListIterator = deviceList.begin(); + while (deviceListIterator != deviceList.end()) + { + if (wcscmp(deviceListIterator->id.c_str(), pwstrDeviceId) == 0) + { + deviceListIterator = deviceList.erase(deviceListIterator); + } + } + }; + + deleteDevice(appData->playbackDevices); + deleteDevice(appData->recordingDevices); + + return S_OK; +} + +HRESULT __stdcall AudioNotificationListener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) +{ + std::vector* deviceList; + if (flow == EDataFlow::eRender) + { + deviceList = &appData->playbackDevices; + } + else + { + deviceList = &appData->recordingDevices; + } + + for (AudioDevice& audioDevice : *deviceList) + { + if (wcscmp(audioDevice.id.c_str(), pwstrDefaultDeviceId) == 0) + { + switch (role) + { + case ERole::eCommunications: + audioDevice.isDefaultCommunication = true; + break; + case ERole::eConsole: + audioDevice.isDefaultConsole = true; + break; + case ERole::eMultimedia: + audioDevice.isDefaultMedia = true; + break; + } + } + else + { + switch (role) + { + case ERole::eCommunications: + audioDevice.isDefaultCommunication = false; + break; + case ERole::eConsole: + audioDevice.isDefaultConsole = false; + break; + case ERole::eMultimedia: + audioDevice.isDefaultMedia = false; + break; + } + } + } + + return S_OK; +} + +HRESULT __stdcall AudioNotificationListener::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) +{ + return S_OK; +} + +// Copy pasted stuff from Windows SDK Example +// No clue what it does lol +// https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/audio/osd/endpointMonitor.cpp +HRESULT __stdcall AudioNotificationListener::QueryInterface(REFIID riid, void** ppvObject) +{ + if ((riid == __uuidof(IUnknown)) || + (riid == __uuidof(IMMNotificationClient))) + { + *ppvObject = static_cast(this); + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +ULONG __stdcall AudioNotificationListener::AddRef(void) +{ + return InterlockedIncrement(&refCount); +} + +ULONG __stdcall AudioNotificationListener::Release(void) +{ + long refCountResult = InterlockedDecrement(&refCount); + if (refCountResult == 0) + { + delete this; + } + return refCountResult; +} diff --git a/AsuroTool/AudioNotificationListener.h b/AsuroTool/AudioNotificationListener.h new file mode 100644 index 0000000..2242590 --- /dev/null +++ b/AsuroTool/AudioNotificationListener.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class ApplicationData; +class AudioNotificationListener : public IMMNotificationClient { +public: + ApplicationData* appData; + long refCount = 1; + + AudioNotificationListener(ApplicationData* appData); + + virtual HRESULT __stdcall OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override; + virtual HRESULT __stdcall OnDeviceAdded(LPCWSTR pwstrDeviceId) override; + virtual HRESULT __stdcall OnDeviceRemoved(LPCWSTR pwstrDeviceId) override; + virtual HRESULT __stdcall OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override; + virtual HRESULT __stdcall OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override; + + virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject) override; + virtual ULONG __stdcall AddRef(void) override; + virtual ULONG __stdcall Release(void) override; +}; +