From 45de4762fa03d7c18cd30ac17587bf9800a37a14 Mon Sep 17 00:00:00 2001 From: Mooneer Salem Date: Fri, 18 Jul 2025 23:59:26 -0700 Subject: [PATCH] Windows: Fix intermittent disappearance of audio devices (#974) * Wrap all COM pointer accesses in a smart pointer to better track refcounts. * Fix compiler error. * We probably need to take a few more references when creating devices. * Adjust how UTF8 strings are created just in case. * Try caching device list to avoid as many calls into Windows. * Fix typos. * Add PR #974 to changelog. --- USER_MANUAL.md | 1 + src/audio/WASAPIAudioDevice.cpp | 101 +++++---------- src/audio/WASAPIAudioDevice.h | 14 ++- src/audio/WASAPIAudioEngine.cpp | 135 ++++++++++---------- src/audio/WASAPIAudioEngine.h | 12 +- src/util/Win32COMPointer.h | 212 ++++++++++++++++++++++++++++++++ 6 files changed, 327 insertions(+), 148 deletions(-) create mode 100644 src/util/Win32COMPointer.h diff --git a/USER_MANUAL.md b/USER_MANUAL.md index 4f2faa5b..6f37f567 100644 --- a/USER_MANUAL.md +++ b/USER_MANUAL.md @@ -862,6 +862,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes * PulseAudio/PortAudio: Only support default sample rate. (PR #964) * Force left-to-right rendering of UI elements. (PR #966) * macOS: improve behavior with Bluetooth devices. (PR #971) + * Windows: Fix intermittent disappearance of audio devices. (PR #974) 2. Documentation: * Add missing dependency for macOS builds to README. (PR #925; thanks @relistan!) * Add note about using XWayland on Linux. (PR #926) diff --git a/src/audio/WASAPIAudioDevice.cpp b/src/audio/WASAPIAudioDevice.cpp index 67e30f2e..faab71f3 100644 --- a/src/audio/WASAPIAudioDevice.cpp +++ b/src/audio/WASAPIAudioDevice.cpp @@ -36,8 +36,9 @@ thread_local HANDLE WASAPIAudioDevice::HelperTask_ = nullptr; -WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels) +WASAPIAudioDevice::WASAPIAudioDevice(ComPtr client, ComPtr device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels) : client_(client) + , device_(device) , renderClient_(nullptr) , captureClient_(nullptr) , direction_(direction) @@ -51,7 +52,7 @@ WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDi , semaphore_(nullptr) , tmpBuf_(nullptr) { - client_->AddRef(); + // empty } WASAPIAudioDevice::~WASAPIAudioDevice() @@ -63,7 +64,10 @@ WASAPIAudioDevice::~WASAPIAudioDevice() auto prom = std::make_shared >(); auto fut = prom->get_future(); enqueue_([&]() { - client_->Release(); + renderClient_ = nullptr; + captureClient_ = nullptr; + client_ = nullptr; + device_ = nullptr; prom->set_value(); }); fut.wait(); @@ -283,13 +287,13 @@ void WASAPIAudioDevice::start() { hr = client_->GetService( IID_IAudioCaptureClient, - (void**)&captureClient_); + (void**)captureClient_.GetAddressOf()); } else { hr = client_->GetService( IID_IAudioRenderClient, - (void**)&renderClient_); + (void**)renderClient_.GetAddressOf()); } if (FAILED(hr)) { @@ -322,7 +326,6 @@ void WASAPIAudioDevice::start() { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - renderClient_->Release(); renderClient_ = nullptr; delete[] tmpBuf_; @@ -350,7 +353,6 @@ void WASAPIAudioDevice::start() { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - renderClient_->Release(); renderClient_ = nullptr; delete[] tmpBuf_; @@ -381,16 +383,8 @@ void WASAPIAudioDevice::start() { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - if (renderClient_ != nullptr) - { - renderClient_->Release(); - renderClient_ = nullptr; - } - if (captureClient_ != nullptr) - { - captureClient_->Release(); - captureClient_ = nullptr; - } + renderClient_ = nullptr; + captureClient_ = nullptr; delete[] tmpBuf_; tmpBuf_ = nullptr; @@ -403,6 +397,11 @@ void WASAPIAudioDevice::start() isRenderCaptureRunning_ = true; renderCaptureThread_ = std::thread([this]() { log_info("Starting render/capture thread"); + + // Capture references for use by this thread. + ComPtr renderClientRef = renderClient_; + ComPtr captureClientRef = captureClient_; + ComPtr clientRef = client_; HRESULT res = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); if (FAILED(res)) @@ -410,18 +409,6 @@ void WASAPIAudioDevice::start() log_warn("Could not initialize COM (res = %d)", res); } - // Increment refcounts of COM objects used by thread - // to avoid instability during stop/restart. - client_->AddRef(); - if (renderClient_ != nullptr) - { - renderClient_->AddRef(); - } - if (captureClient_ != nullptr) - { - captureClient_->AddRef(); - } - // Temporarily raise priority of task setHelperRealTime(); @@ -432,30 +419,18 @@ void WASAPIAudioDevice::start() { if (direction_ == IAudioEngine::AUDIO_ENGINE_OUT) { - renderAudio_(); + renderAudio_(renderClientRef); } else { - captureAudio_(); + captureAudio_(captureClientRef); } } } log_info("Exiting render/capture thread"); - clearHelperRealTime(); - - // Decrement refcounts prior to exit. - client_->Release(); - if (renderClient_ != nullptr) - { - renderClient_->Release(); - } - if (captureClient_ != nullptr) - { - captureClient_->Release(); - } - + clearHelperRealTime(); CoUninitialize(); }); @@ -478,7 +453,7 @@ void WASAPIAudioDevice::stop() renderCaptureThread_.join(); } - if (renderClient_ != nullptr || captureClient_ != nullptr) + if (renderClient_ || captureClient_) { HRESULT hr = client_->Stop(); if (FAILED(hr)) @@ -492,6 +467,9 @@ void WASAPIAudioDevice::stop() } } } + + renderClient_ = nullptr; + captureClient_ = nullptr; if (renderCaptureEvent_ != nullptr) { @@ -499,17 +477,6 @@ void WASAPIAudioDevice::stop() renderCaptureEvent_ = nullptr; } - if (renderClient_ != nullptr) - { - renderClient_->Release(); - renderClient_ = nullptr; - } - if (captureClient_ != nullptr) - { - captureClient_->Release(); - captureClient_ = nullptr; - } - if (semaphore_ != nullptr) { // Set semaphore_ to nullptr first in case someone could be potentially @@ -535,7 +502,7 @@ void WASAPIAudioDevice::stop() bool WASAPIAudioDevice::isRunning() { - return (renderClient_ != nullptr) || (captureClient_ != nullptr); + return (renderClient_) || (captureClient_); } int WASAPIAudioDevice::getLatencyInMicroseconds() @@ -588,10 +555,10 @@ void WASAPIAudioDevice::clearHelperRealTime() } } -void WASAPIAudioDevice::renderAudio_() +void WASAPIAudioDevice::renderAudio_(ComPtr renderClient) { // If client is no longer available, abort - if (renderClient_ == nullptr) + if (!renderClient) { return; } @@ -612,7 +579,7 @@ void WASAPIAudioDevice::renderAudio_() } framesAvailable = bufferFrameCount_ - padding; - hr = renderClient_->GetBuffer(framesAvailable, &data); + hr = renderClient->GetBuffer(framesAvailable, &data); if (FAILED(hr)) { // Note: don't call to event handler to avoid annoying the user @@ -635,7 +602,7 @@ void WASAPIAudioDevice::renderAudio_() } // Release render buffer - hr = renderClient_->ReleaseBuffer(framesAvailable, 0); + hr = renderClient->ReleaseBuffer(framesAvailable, 0); if (FAILED(hr)) { // Note: don't call to event handler to avoid annoying the user @@ -647,17 +614,17 @@ void WASAPIAudioDevice::renderAudio_() } } -void WASAPIAudioDevice::captureAudio_() +void WASAPIAudioDevice::captureAudio_(ComPtr captureClient) { // If client is no longer available, abort - if (captureClient_ == nullptr) + if (!captureClient) { return; } // Get packet length UINT32 packetLength = 0; - HRESULT hr = captureClient_->GetNextPacketSize(&packetLength); + HRESULT hr = captureClient->GetNextPacketSize(&packetLength); if (FAILED(hr)) { // Note: don't call to event handler to avoid annoying the user @@ -674,7 +641,7 @@ void WASAPIAudioDevice::captureAudio_() UINT32 numFramesAvailable = 0; DWORD flags = 0; - hr = captureClient_->GetBuffer( + hr = captureClient->GetBuffer( &data, &numFramesAvailable, &flags, @@ -710,7 +677,7 @@ void WASAPIAudioDevice::captureAudio_() } // Release buffer - hr = captureClient_->ReleaseBuffer(numFramesAvailable); + hr = captureClient->ReleaseBuffer(numFramesAvailable); if (FAILED(hr)) { // Note: don't call to event handler to avoid annoying the user @@ -721,7 +688,7 @@ void WASAPIAudioDevice::captureAudio_() return; } - hr = captureClient_->GetNextPacketSize(&packetLength); + hr = captureClient->GetNextPacketSize(&packetLength); if (FAILED(hr)) { // Note: don't call to event handler to avoid annoying the user diff --git a/src/audio/WASAPIAudioDevice.h b/src/audio/WASAPIAudioDevice.h index 881d4936..51a1a4e2 100644 --- a/src/audio/WASAPIAudioDevice.h +++ b/src/audio/WASAPIAudioDevice.h @@ -34,6 +34,7 @@ #include "IAudioEngine.h" #include "IAudioDevice.h" #include "../util/Win32COMObject.h" +#include "../util/Win32COMPointer.h" class WASAPIAudioDevice : public Win32COMObject, public IAudioDevice { @@ -68,12 +69,13 @@ public: protected: friend class WASAPIAudioEngine; - WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels); + WASAPIAudioDevice(ComPtr client, ComPtr device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels); private: - IAudioClient* client_; - IAudioRenderClient* renderClient_; - IAudioCaptureClient* captureClient_; + ComPtr client_; + ComPtr device_; + ComPtr renderClient_; + ComPtr captureClient_; IAudioEngine::AudioDirection direction_; int sampleRate_; int numChannels_; @@ -91,8 +93,8 @@ private: bool isFloatingPoint_; short* tmpBuf_; - void renderAudio_(); - void captureAudio_(); + void renderAudio_(ComPtr renderClient); + void captureAudio_(ComPtr captureClient); void copyFromWindowsBuffer_(void* buf, int numFrames); void copyToWindowsBuffer_(void* buf, int numFrames); diff --git a/src/audio/WASAPIAudioEngine.cpp b/src/audio/WASAPIAudioEngine.cpp index f254f044..7dca7d17 100644 --- a/src/audio/WASAPIAudioEngine.cpp +++ b/src/audio/WASAPIAudioEngine.cpp @@ -44,6 +44,17 @@ WASAPIAudioEngine::WASAPIAudioEngine() WASAPIAudioEngine::~WASAPIAudioEngine() { stop(); + + // Release COM pointers before object fully goes away. + auto prom = std::make_shared >(); + auto fut = prom->get_future(); + enqueue_([&]() { + devEnumerator_ = nullptr; + inputDeviceList_ = nullptr; + outputDeviceList_ = nullptr; + prom->set_value(); + }); + fut.wait(); } void WASAPIAudioEngine::start() @@ -55,7 +66,7 @@ void WASAPIAudioEngine::start() HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, - (void**)&devEnumerator_); + (void**)devEnumerator_.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -70,7 +81,7 @@ void WASAPIAudioEngine::start() } // Get input and output device collections - hr = devEnumerator_->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &outputDeviceList_); + hr = devEnumerator_->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, outputDeviceList_.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -81,14 +92,12 @@ void WASAPIAudioEngine::start() onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - devEnumerator_->Release(); devEnumerator_ = nullptr; - prom->set_value(); return; } - hr = devEnumerator_->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &inputDeviceList_); + hr = devEnumerator_->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, inputDeviceList_.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -99,12 +108,8 @@ void WASAPIAudioEngine::start() onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - outputDeviceList_->Release(); outputDeviceList_ = nullptr; - - devEnumerator_->Release(); devEnumerator_ = nullptr; - prom->set_value(); return; } @@ -119,24 +124,14 @@ void WASAPIAudioEngine::stop() auto prom = std::make_shared >(); auto fut = prom->get_future(); enqueue_([&]() { - if (inputDeviceList_ != nullptr) - { - inputDeviceList_->Release(); - inputDeviceList_ = nullptr; - } - - if (outputDeviceList_ != nullptr) - { - outputDeviceList_->Release(); - outputDeviceList_ = nullptr; - } - - if (devEnumerator_ != nullptr) - { - devEnumerator_->Release(); - devEnumerator_ = nullptr; - } - + devEnumerator_ = nullptr; + inputDeviceList_ = nullptr; + outputDeviceList_ = nullptr; + + // Invalidate cached devices. + cachedInputDeviceList_.clear(); + cachedOutputDeviceList_.clear(); + prom->set_value(); }); fut.wait(); @@ -149,19 +144,30 @@ std::vector WASAPIAudioEngine::getAudioDeviceList(Audi enqueue_([&, direction]() { std::vector result; - IMMDeviceCollection* coll = + // Just used the cached results if they exist, no need to call into Windows again. + if (direction == AudioDirection::AUDIO_ENGINE_IN && cachedInputDeviceList_.size() > 0) + { + prom->set_value(cachedInputDeviceList_); + return; + } + else if (cachedOutputDeviceList_.size() > 0) + { + prom->set_value(cachedOutputDeviceList_); + return; + } + + ComPtr coll = (direction == AudioDirection::AUDIO_ENGINE_IN) ? inputDeviceList_ : outputDeviceList_; - coll->AddRef(); UINT deviceCount = 0; HRESULT hr = coll->GetCount(&deviceCount); for (UINT index = 0; index < deviceCount; index++) { - IMMDevice* device = nullptr; - hr = coll->Item(index, &device); + ComPtr device = nullptr; + hr = coll->Item(index, device.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -171,7 +177,6 @@ std::vector WASAPIAudioEngine::getAudioDeviceList(Audi { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - coll->Release(); prom->set_value(result); return; } @@ -183,10 +188,17 @@ std::vector WASAPIAudioEngine::getAudioDeviceList(Audi result.push_back(devSpec); log_debug("Found device %s (card = %s, port = %s)", (const char*)devSpec.name.ToUTF8(), (const char*)devSpec.cardName.ToUTF8(), (const char*)devSpec.portName.ToUTF8()); } - device->Release(); } - coll->Release(); + if (direction == AudioDirection::AUDIO_ENGINE_IN) + { + cachedInputDeviceList_ = result; + } + else + { + cachedOutputDeviceList_ = result; + } + prom->set_value(result); }); return fut.get(); @@ -198,11 +210,11 @@ AudioDeviceSpecification WASAPIAudioEngine::getDefaultAudioDevice(AudioDirection auto prom = std::make_shared >(); auto fut = prom->get_future(); enqueue_([&, specList, direction]() { - IMMDevice* defaultDevice = nullptr; + ComPtr defaultDevice = nullptr; HRESULT hr = devEnumerator_->GetDefaultAudioEndpoint( (direction == AudioDirection::AUDIO_ENGINE_IN) ? eCapture : eRender, eConsole, - &defaultDevice); + defaultDevice.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -221,13 +233,11 @@ AudioDeviceSpecification WASAPIAudioEngine::getDefaultAudioDevice(AudioDirection { if (defaultSpec.name == spec.name) { - defaultDevice->Release(); prom->set_value(spec); return; } } log_warn("Could not get device ID for default audio device"); - defaultDevice->Release(); prom->set_value(AudioDeviceSpecification::GetInvalidDevice()); }); return fut.get(); @@ -260,11 +270,10 @@ std::shared_ptr WASAPIAudioEngine::getAudioDevice(wxString deviceN enqueue_([&, devList, deviceName, direction, sampleRate, numChannels]() { std::shared_ptr result; int finalSampleRate = sampleRate; - IMMDeviceCollection* coll = + ComPtr coll = (direction == AudioDirection::AUDIO_ENGINE_IN) ? inputDeviceList_ : outputDeviceList_; - coll->AddRef(); for (auto& dev : devList) { @@ -272,10 +281,10 @@ std::shared_ptr WASAPIAudioEngine::getAudioDevice(wxString deviceN { log_info("Creating WASAPIAudioDevice for device %s (ID %d, direction = %d, sample rate = %d, number of channels = %d)", (const char*)deviceName.ToUTF8(), (int)dev.deviceId, (int)direction, sampleRate, numChannels); - IMMDevice* device = nullptr; - IAudioClient* client = nullptr; + ComPtr device = nullptr; + ComPtr client = nullptr; - HRESULT hr = coll->Item(dev.deviceId, &device); + HRESULT hr = coll->Item(dev.deviceId, device.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -290,7 +299,7 @@ std::shared_ptr WASAPIAudioEngine::getAudioDevice(wxString deviceN hr = device->Activate( IID_IAudioClient, CLSCTX_ALL, - nullptr, (void**)&client); + nullptr, (void**)client.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -300,8 +309,6 @@ std::shared_ptr WASAPIAudioEngine::getAudioDevice(wxString deviceN { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - - device->Release(); break; } @@ -314,14 +321,10 @@ std::shared_ptr WASAPIAudioEngine::getAudioDevice(wxString deviceN int finalNumChannels = std::max(numChannels, dev.minChannels); finalNumChannels = std::min(finalNumChannels, dev.maxChannels); - auto devPtr = new WASAPIAudioDevice(client, direction, finalSampleRate, finalNumChannels); + auto devPtr = new WASAPIAudioDevice(client, device, direction, finalSampleRate, finalNumChannels); result = std::shared_ptr(devPtr); - - client->Release(); - device->Release(); } } - coll->Release(); prom->set_value(result); }); return fut.get(); @@ -349,11 +352,11 @@ std::vector WASAPIAudioEngine::getSupportedSampleRates(wxString deviceName, return fut.get(); } -AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* device) +AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(ComPtr device) { // Get device name - IPropertyStore* propStore = nullptr; - HRESULT hr = device->OpenPropertyStore(STGM_READ, &propStore); + ComPtr propStore = nullptr; + HRESULT hr = device->OpenPropertyStore(STGM_READ, propStore.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -379,7 +382,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -387,7 +389,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d { log_warn("Device does not have a friendly name!"); PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -407,7 +408,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -415,7 +415,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d { log_warn("Device does not have a card name!"); PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -432,7 +431,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -440,7 +438,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d { log_warn("Device does not have a port name!"); PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -463,8 +460,8 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d } // Activate IAudioClient so we can obtain format info - IAudioClient* audioClient = nullptr; - hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&audioClient); + ComPtr audioClient = nullptr; + hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)audioClient.GetAddressOf()); if (FAILED(hr)) { std::stringstream ss; @@ -475,7 +472,6 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -490,9 +486,7 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d { onAudioErrorFunction(*this, ss.str(), onAudioErrorState); } - audioClient->Release(); PropVariantClear(&friendlyName); - propStore->Release(); return AudioDeviceSpecification::GetInvalidDevice(); } @@ -521,23 +515,22 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d } CoTaskMemFree(streamFormat); - audioClient->Release(); PropVariantClear(&friendlyName); - propStore->Release(); return spec; // note: deviceId needs to be filled in by caller } std::string WASAPIAudioEngine::getUTF8String_(LPWSTR str) { - std::vector buffer; std::string val = ""; int size = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); if (size > 0) { - buffer.resize(size); - WideCharToMultiByte(CP_UTF8, 0, str, -1, static_cast(&buffer[0]), buffer.size(), NULL, NULL); - val = std::string(&buffer[0]); + char* tmp = new char[size]; + assert(tmp != nullptr); + WideCharToMultiByte(CP_UTF8, 0, str, -1, tmp, size, NULL, NULL); + val = tmp; + delete[] tmp; } return val; } diff --git a/src/audio/WASAPIAudioEngine.h b/src/audio/WASAPIAudioEngine.h index 01641b9c..c6aa181e 100644 --- a/src/audio/WASAPIAudioEngine.h +++ b/src/audio/WASAPIAudioEngine.h @@ -30,6 +30,7 @@ #include #include #include "../util/Win32COMObject.h" +#include "../util/Win32COMPointer.h" #include "IAudioEngine.h" class WASAPIAudioEngine : public Win32COMObject, public IAudioEngine @@ -48,11 +49,14 @@ public: protected: private: - IMMDeviceEnumerator* devEnumerator_; - IMMDeviceCollection* inputDeviceList_; - IMMDeviceCollection* outputDeviceList_; + ComPtr devEnumerator_; + ComPtr inputDeviceList_; + ComPtr outputDeviceList_; + + std::vector cachedInputDeviceList_; + std::vector cachedOutputDeviceList_; - AudioDeviceSpecification getDeviceSpecification_(IMMDevice* device); + AudioDeviceSpecification getDeviceSpecification_(ComPtr device); std::string getUTF8String_(LPWSTR str); }; diff --git a/src/util/Win32COMPointer.h b/src/util/Win32COMPointer.h new file mode 100644 index 00000000..460bc866 --- /dev/null +++ b/src/util/Win32COMPointer.h @@ -0,0 +1,212 @@ +//========================================================================= +// Name: Win32COMPointer.h +// Purpose: Smart pointer implementation for COM objects. +// Original implementation at https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/february/windows-with-c-com-smart-pointers-revisited. +// +// Authors: Kenny Kerr with modifications by Mooneer Salem +// License: +// +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2.1, +// as published by the Free Software Foundation. This program is +// distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see . +// +//========================================================================= + +#ifndef WIN32_COM_POINTER_H +#define WIN32_COM_POINTER_H + +#include + +template +class ComPtr +{ +public: + // Hide AddRef and Release to prevent overriding ComPtr counting + class RemoveAddRefRelease : public Interface + { + private: + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + }; + + ComPtr() noexcept = default; + ComPtr(std::nullptr_t) noexcept + : m_ptr(nullptr) + { } + + ComPtr(ComPtr const & other) noexcept : + m_ptr(other.m_ptr) + { + InternalAddRef(); + } + + ComPtr(ComPtr&& other) noexcept + : m_ptr(other.m_ptr) + { + other.m_ptr = nullptr; + } + + template + ComPtr(ComPtr const & other) noexcept : + m_ptr(other.m_ptr) + { + InternalAddRef(); + } + + template + ComPtr(ComPtr && other) noexcept : + m_ptr(other.m_ptr) + { + other.m_ptr = nullptr; + } + + ~ComPtr() noexcept + { + InternalRelease(); + } + + RemoveAddRefRelease* operator->() const noexcept + { + return static_cast(m_ptr); + } + + ComPtr & operator=(ComPtr const & other) noexcept + { + InternalCopy(other.m_ptr); + return *this; + } + + template + ComPtr & operator=(ComPtr const & other) noexcept + { + InternalCopy(other.m_ptr); + return *this; + } + + template + ComPtr & operator=(ComPtr && other) noexcept + { + InternalMove(other); + return *this; + } + + void Swap(ComPtr & other) noexcept + { + Interface * temp = m_ptr; + m_ptr = other.m_ptr; + other.m_ptr = temp; + } + + template + void swap(ComPtr & left, + ComPtr & right) noexcept + { + left.Swap(right); + } + + explicit operator bool() const noexcept + { + return nullptr != m_ptr; + } + + void Reset() noexcept + { + InternalRelease(); + } + + Interface * Get() const noexcept + { + return m_ptr; + } + + Interface * Detach() noexcept + { + Interface * temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + void Copy(Interface * other) noexcept + { + InternalCopy(other); + } + + void Attach(Interface * other) noexcept + { + InternalRelease(); + m_ptr = other; + } + + Interface ** GetAddressOf() noexcept + { + assert(m_ptr == nullptr); + return &m_ptr; + } + + void CopyTo(Interface ** other) const noexcept + { + InternalAddRef(); + *other = m_ptr; + } + + template + ComPtr As() const noexcept + { + ComPtr temp; + m_ptr->QueryInterface(temp.GetAddressOf()); + return temp; + } + +private: + Interface * m_ptr = nullptr; + + void InternalAddRef() const noexcept + { + if (m_ptr) + { + m_ptr->AddRef(); + } + } + + void InternalRelease() noexcept + { + Interface * temp = m_ptr; + if (temp) + { + m_ptr = nullptr; + temp->Release(); + } + } + + void InternalCopy(Interface * other) noexcept + { + if (m_ptr != other) + { + InternalRelease(); + m_ptr = other; + InternalAddRef(); + } + } + + template + void InternalMove(ComPtr & other) noexcept + { + if (m_ptr != other.m_ptr) + { + InternalRelease(); + m_ptr = other.m_ptr; + other.m_ptr = nullptr; + } + } +}; + +#endif // WIN32_COM_POINTER_H