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.
pull/977/head
Mooneer Salem 2025-07-18 23:59:26 -07:00 committed by GitHub
parent 2bcf190dd9
commit 45de4762fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 327 additions and 148 deletions

View File

@ -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)

View File

@ -36,8 +36,9 @@
thread_local HANDLE WASAPIAudioDevice::HelperTask_ = nullptr;
WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels)
WASAPIAudioDevice::WASAPIAudioDevice(ComPtr<IAudioClient> client, ComPtr<IMMDevice> 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<std::promise<void> >();
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<IAudioRenderClient> renderClientRef = renderClient_;
ComPtr<IAudioCaptureClient> captureClientRef = captureClient_;
ComPtr<IAudioClient> 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<IAudioRenderClient> 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<IAudioCaptureClient> 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

View File

@ -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<IAudioClient> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels);
private:
IAudioClient* client_;
IAudioRenderClient* renderClient_;
IAudioCaptureClient* captureClient_;
ComPtr<IAudioClient> client_;
ComPtr<IMMDevice> device_;
ComPtr<IAudioRenderClient> renderClient_;
ComPtr<IAudioCaptureClient> captureClient_;
IAudioEngine::AudioDirection direction_;
int sampleRate_;
int numChannels_;
@ -91,8 +93,8 @@ private:
bool isFloatingPoint_;
short* tmpBuf_;
void renderAudio_();
void captureAudio_();
void renderAudio_(ComPtr<IAudioRenderClient> renderClient);
void captureAudio_(ComPtr<IAudioCaptureClient> captureClient);
void copyFromWindowsBuffer_(void* buf, int numFrames);
void copyToWindowsBuffer_(void* buf, int numFrames);

View File

@ -44,6 +44,17 @@ WASAPIAudioEngine::WASAPIAudioEngine()
WASAPIAudioEngine::~WASAPIAudioEngine()
{
stop();
// Release COM pointers before object fully goes away.
auto prom = std::make_shared<std::promise<void> >();
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<std::promise<void> >();
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<AudioDeviceSpecification> WASAPIAudioEngine::getAudioDeviceList(Audi
enqueue_([&, direction]() {
std::vector<AudioDeviceSpecification> 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<IMMDeviceCollection> 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<IMMDevice> device = nullptr;
hr = coll->Item(index, device.GetAddressOf());
if (FAILED(hr))
{
std::stringstream ss;
@ -171,7 +177,6 @@ std::vector<AudioDeviceSpecification> WASAPIAudioEngine::getAudioDeviceList(Audi
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
coll->Release();
prom->set_value(result);
return;
}
@ -183,10 +188,17 @@ std::vector<AudioDeviceSpecification> 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<std::promise<AudioDeviceSpecification> >();
auto fut = prom->get_future();
enqueue_([&, specList, direction]() {
IMMDevice* defaultDevice = nullptr;
ComPtr<IMMDevice> 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<IAudioDevice> WASAPIAudioEngine::getAudioDevice(wxString deviceN
enqueue_([&, devList, deviceName, direction, sampleRate, numChannels]() {
std::shared_ptr<IAudioDevice> result;
int finalSampleRate = sampleRate;
IMMDeviceCollection* coll =
ComPtr<IMMDeviceCollection> coll =
(direction == AudioDirection::AUDIO_ENGINE_IN) ?
inputDeviceList_ :
outputDeviceList_;
coll->AddRef();
for (auto& dev : devList)
{
@ -272,10 +281,10 @@ std::shared_ptr<IAudioDevice> 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<IMMDevice> device = nullptr;
ComPtr<IAudioClient> 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<IAudioDevice> 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<IAudioDevice> WASAPIAudioEngine::getAudioDevice(wxString deviceN
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
device->Release();
break;
}
@ -314,14 +321,10 @@ std::shared_ptr<IAudioDevice> 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<IAudioDevice>(devPtr);
client->Release();
device->Release();
}
}
coll->Release();
prom->set_value(result);
});
return fut.get();
@ -349,11 +352,11 @@ std::vector<int> WASAPIAudioEngine::getSupportedSampleRates(wxString deviceName,
return fut.get();
}
AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* device)
AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(ComPtr<IMMDevice> device)
{
// Get device name
IPropertyStore* propStore = nullptr;
HRESULT hr = device->OpenPropertyStore(STGM_READ, &propStore);
ComPtr<IPropertyStore> 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<IAudioClient> 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<char> 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<LPSTR>(&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;
}

View File

@ -30,6 +30,7 @@
#include <initguid.h>
#include <mmdeviceapi.h>
#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<IMMDeviceEnumerator> devEnumerator_;
ComPtr<IMMDeviceCollection> inputDeviceList_;
ComPtr<IMMDeviceCollection> outputDeviceList_;
std::vector<AudioDeviceSpecification> cachedInputDeviceList_;
std::vector<AudioDeviceSpecification> cachedOutputDeviceList_;
AudioDeviceSpecification getDeviceSpecification_(IMMDevice* device);
AudioDeviceSpecification getDeviceSpecification_(ComPtr<IMMDevice> device);
std::string getUTF8String_(LPWSTR str);
};

View File

@ -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 <http://www.gnu.org/licenses/>.
//
//=========================================================================
#ifndef WIN32_COM_POINTER_H
#define WIN32_COM_POINTER_H
#include <cassert>
template <typename Interface>
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 <typename T>
ComPtr(ComPtr<T> const & other) noexcept :
m_ptr(other.m_ptr)
{
InternalAddRef();
}
template <typename T>
ComPtr(ComPtr<T> && other) noexcept :
m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
~ComPtr() noexcept
{
InternalRelease();
}
RemoveAddRefRelease* operator->() const noexcept
{
return static_cast<RemoveAddRefRelease*>(m_ptr);
}
ComPtr & operator=(ComPtr const & other) noexcept
{
InternalCopy(other.m_ptr);
return *this;
}
template <typename T>
ComPtr & operator=(ComPtr<T> const & other) noexcept
{
InternalCopy(other.m_ptr);
return *this;
}
template <typename T>
ComPtr & operator=(ComPtr<T> && 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 <typename Interface2>
void swap(ComPtr<Interface> & left,
ComPtr<Interface2> & 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 <typename T>
ComPtr<T> As() const noexcept
{
ComPtr<T> 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 <typename T>
void InternalMove(ComPtr<T> & other) noexcept
{
if (m_ptr != other.m_ptr)
{
InternalRelease();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
}
};
#endif // WIN32_COM_POINTER_H