FAudio: Implement and have functional FAudio output

Corrected the current FAudio output code, FAudio api wasn't
a direct 1 for 1 code replacement. Adjusted the existing
code structure so that FAudioVoiceCallBack struct
was being properly called on.

Signed-off-by: Zach Bacon <zachbacon@vba-m.com>
This commit is contained in:
Zach Bacon 2023-12-23 21:33:03 -05:00 committed by Fabrice de Gans
parent 0e13cc9346
commit 311b232ee5
2 changed files with 233 additions and 221 deletions

View File

@ -325,6 +325,7 @@ endif()
# FAudio. # FAudio.
if(ENABLE_FAUDIO) if(ENABLE_FAUDIO)
find_package(FAudio REQUIRED) find_package(FAudio REQUIRED)
target_sources(visualboyadvance-m PRIVATE faudio.cpp)
target_link_libraries(visualboyadvance-m FAudio::FAudio) target_link_libraries(visualboyadvance-m FAudio::FAudio)
target_compile_definitions(visualboyadvance-m PRIVATE VBAM_ENABLE_FAUDIO) target_compile_definitions(visualboyadvance-m PRIVATE VBAM_ENABLE_FAUDIO)
endif() endif()

View File

@ -8,42 +8,44 @@
#include <vector> #include <vector>
// FAudio // FAudio
#include <faudio.h> #include <FAudio.h>
// MMDevice API
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
#include <wx/arrstr.h> #include <wx/arrstr.h>
#include <wx/log.h>
#include <wx/translation.h>
#include "core/base/sound_driver.h" #include "core/base/sound_driver.h"
#include "core/base/system.h" #include "core/base/system.h"
#include "core/gba/gbaGlobals.h" #include "core/gba/gbaGlobals.h"
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
namespace {
int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids, int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids,
const wxString* match) const wxString* match)
{ {
HRESULT hr; uint32_t hr;
UINT32 dev_count = 0; uint32_t dev_count = 0;
hr = FAudio_GetDeviceCount(fa, &dev_count); hr = FAudio_GetDeviceCount(fa, &dev_count);
if (hr != S_OK) { if (hr != 0) {
wxLogError(_("FAudio: Enumerating devices failed!")); wxLogError(_("FAudio: Enumerating devices failed!"));
return true; return -1;
} else { } else {
FAudioDeviceDetails dd; FAudioDeviceDetails dd;
for (UINT32 i = 0; i < dev_count; i++) { for (uint32_t i = 0; i < dev_count; i++) {
hr = FAudio_GetDeviceDetails(fa, i, &dd); hr = FAudio_GetDeviceDetails(fa, i, &dd);
if (hr != S_OK) { if (hr != 0) {
continue; continue;
} else { } else {
if (ids) { if (ids) {
ids->push_back((wchar_t*) dd.DeviceID); //FAudio is an interesting beast, but not that hard to adapt... once you get used to it, XAudio2 wouldn't need this, but FAudio declares FAudioDeviceDetails as int32_t ids->push_back((wchar_t*) dd.DeviceID);
names->push_back((wchar_t*) dd.DisplayName); names->push_back((wchar_t*) dd.DisplayName);
} else if (*match == dd.DeviceID) } else if (*match == wxString((wchar_t*) dd.DeviceID))
return i; return i;
} }
} }
@ -52,29 +54,7 @@ int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids,
return -1; return -1;
} }
bool GetFADevices(wxArrayString& names, wxArrayString& ids) int FAGetDev(FAudio* fa)
{
HRESULT hr;
FAudio* fa = NULL;
UINT32 flags = 0;
#ifdef _DEBUG
flags = FAUDIO_DEBUG_ENGINE;
#endif
hr = FAudioCreate(&fa, flags, FAUDIO_DEFAULT_PROCESSOR); //Apparently this needs 3 parameters, the processor.
if (hr != S_OK) {
wxLogError(_("The FAudio interface failed to initialize!"));
return false;
}
GetFADevices(fa, &names, &ids, NULL);
//fa->Release();
FAudio_Release(fa);
return true;
}
static int FAGetDev(FAudio* fa)
{ {
const wxString& audio_device = OPTION(kSoundAudioDevice); const wxString& audio_device = OPTION(kSoundAudioDevice);
if (audio_device.empty()) if (audio_device.empty())
@ -85,155 +65,50 @@ static int FAGetDev(FAudio* fa)
} }
} }
class FAudio_Output;
static void faudio_device_changed(FAudio_Output*);
class FAudio_Device_Notifier : public IMMNotificationClient {
volatile LONG registered;
IMMDeviceEnumerator* pEnumerator;
std::wstring last_device;
CRITICAL_SECTION lock;
std::vector<FAudio_Output*> instances;
public:
FAudio_Device_Notifier()
: registered(0)
{
InitializeCriticalSection(&lock);
}
~FAudio_Device_Notifier()
{
DeleteCriticalSection(&lock);
}
ULONG STDMETHODCALLTYPE AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE Release()
{
return 1;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface)
{
if (IID_IUnknown == riid) {
*ppvInterface = (IUnknown*)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
*ppvInterface = (IMMNotificationClient*)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
{
if (flow == eRender && last_device.compare(pwstrDeviceId) != 0) {
last_device = pwstrDeviceId;
EnterCriticalSection(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it) {
faudio_device_changed(*it);
}
LeaveCriticalSection(&lock);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { return S_OK; }
void do_register(FAudio_Output* p_instance)
{
if (InterlockedIncrement(&registered) == 1) {
pEnumerator = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if (SUCCEEDED(hr)) {
pEnumerator->RegisterEndpointNotificationCallback(this);
}
}
EnterCriticalSection(&lock);
instances.push_back(p_instance);
LeaveCriticalSection(&lock);
}
void do_unregister(FAudio_Output* p_instance)
{
if (InterlockedDecrement(&registered) == 0) {
if (pEnumerator) {
pEnumerator->UnregisterEndpointNotificationCallback(this);
pEnumerator->Release();
pEnumerator = NULL;
}
}
EnterCriticalSection(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it) {
if (*it == p_instance) {
instances.erase(it);
break;
}
}
LeaveCriticalSection(&lock);
}
} g_notifier;
// Synchronization Event
class FAudio_BufferNotify : public FAudioVoiceCallback { class FAudio_BufferNotify : public FAudioVoiceCallback {
public: public:
HANDLE hBufferEndEvent; bool WaitForSignal() {
return WaitForSingleObject(buffer_end_event_, 10000) != WAIT_TIMEOUT;
}
FAudio_BufferNotify() FAudio_BufferNotify()
{ {
hBufferEndEvent = NULL; buffer_end_event_ = nullptr;
hBufferEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL); buffer_end_event_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
assert(hBufferEndEvent != NULL); assert(buffer_end_event_ != nullptr);
}
OnBufferEnd = &FAudio_BufferNotify::StaticOnBufferEnd;
OnVoiceProcessingPassStart = &FAudio_BufferNotify::StaticOnVoiceProcessingPassStart;
OnVoiceProcessingPassEnd = &FAudio_BufferNotify::StaticOnVoiceProcessingPassEnd;
OnStreamEnd = &FAudio_BufferNotify::StaticOnStreamEnd;
OnBufferStart = &FAudio_BufferNotify::StaticOnBufferStart;
OnLoopEnd = &FAudio_BufferNotify::StaticOnLoopEnd;
OnVoiceError = &FAudio_BufferNotify::StaticOnVoiceError;
}
~FAudio_BufferNotify() ~FAudio_BufferNotify()
{ {
CloseHandle(hBufferEndEvent); CloseHandle(buffer_end_event_);
hBufferEndEvent = NULL; buffer_end_event_ = nullptr;
} }
STDMETHOD_(void, OnBufferEnd) private:
(void* pBufferContext) void* buffer_end_event_;
{
assert(hBufferEndEvent != NULL);
SetEvent(hBufferEndEvent);
}
// dummies: static void StaticOnBufferEnd(FAudioVoiceCallback* callback, void * pBufferContext) {
STDMETHOD_(void, OnVoiceProcessingPassStart) FAudio_BufferNotify* self = static_cast<FAudio_BufferNotify*>(callback);
(UINT32 BytesRequired) {} if (self != nullptr && self->buffer_end_event_ != NULL)
STDMETHOD_(void, OnVoiceProcessingPassEnd) {
() {} SetEvent(self->buffer_end_event_);
STDMETHOD_(void, OnStreamEnd) }
() {} }
STDMETHOD_(void, OnBufferStart) static void StaticOnVoiceProcessingPassStart(FAudioVoiceCallback* callback, uint32_t BytesRequired) {}
(void* pBufferContext) {} static void StaticOnVoiceProcessingPassEnd(FAudioVoiceCallback* callback) {}
STDMETHOD_(void, OnLoopEnd) static void StaticOnStreamEnd(FAudioVoiceCallback* callback) {}
(void* pBufferContext) {} static void StaticOnBufferStart(FAudioVoiceCallback* callback, void * pBufferContext) {}
STDMETHOD_(void, OnVoiceError) static void StaticOnLoopEnd(FAudioVoiceCallback* callback, void * pBufferContext) {}
(void* pBufferContext, HRESULT Error){}; static void StaticOnVoiceError(FAudioVoiceCallback* callback, void * pBufferContext, uint32_t Error) {}
}; };
// Class Declaration
class FAudio_Output class FAudio_Output
: public SoundDriver { : public SoundDriver {
public: public:
@ -254,15 +129,15 @@ public:
void device_change(); void device_change();
// Configuration Changes // Configuration Changes
void setThrottle(unsigned short throttle); void setThrottle(unsigned short throttle_);
private: private:
bool failed; bool failed;
bool initialized; bool initialized;
bool playing; bool playing;
UINT32 freq; uint32_t freq;
UINT32 bufferCount; uint32_t bufferCount;
BYTE* buffers; uint8_t* buffers;
int currentBuffer; int currentBuffer;
int soundBufferLen; int soundBufferLen;
@ -276,6 +151,34 @@ private:
FAudio_BufferNotify notify; // buffer end notification FAudio_BufferNotify notify; // buffer end notification
}; };
class FAudio_Device_Notifier : public IMMNotificationClient {
private:
volatile LONG registered;
IMMDeviceEnumerator* pEnumerator;
std::wstring last_device;
CRITICAL_SECTION lock;
std::vector<FAudio_Output*> instances;
public:
FAudio_Device_Notifier();
~FAudio_Device_Notifier();
ULONG STDMETHODCALLTYPE AddRef();
ULONG STDMETHODCALLTYPE Release();
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId);
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId);
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId);
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState);
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key);
void do_register(FAudio_Output* p_instance);
void do_unregister(FAudio_Output* p_instance);
};
FAudio_Device_Notifier f_notifier;
// Class Implementation // Class Implementation
FAudio_Output::FAudio_Output() FAudio_Output::FAudio_Output()
{ {
@ -290,14 +193,14 @@ FAudio_Output::FAudio_Output()
faud = NULL; faud = NULL;
mVoice = NULL; mVoice = NULL;
sVoice = NULL; sVoice = NULL;
ZeroMemory(&buf, sizeof(buf)); memset(&buf, NULL, sizeof(buf));
ZeroMemory(&vState, sizeof(vState)); memset(&vState, NULL, sizeof(vState));
g_notifier.do_register(this); f_notifier.do_register(this);
} }
FAudio_Output::~FAudio_Output() FAudio_Output::~FAudio_Output()
{ {
g_notifier.do_unregister(this); f_notifier.do_unregister(this);
close(); close();
} }
@ -307,8 +210,7 @@ void FAudio_Output::close()
if (sVoice) { if (sVoice) {
if (playing) { if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); assert(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
assert(hr == S_OK);
} }
FAudioVoice_DestroyVoice(sVoice); FAudioVoice_DestroyVoice(sVoice);
@ -341,15 +243,15 @@ bool FAudio_Output::init(long sampleRate)
if (failed || initialized) if (failed || initialized)
return false; return false;
HRESULT hr; uint32_t hr;
// Initialize FAudio // Initialize FAudio
UINT32 flags = 0; uint32_t flags = 0;
//#ifdef _DEBUG //#ifdef _DEBUG
// flags = FAUDIO_DEBUG_ENGINE; // flags = FAUDIO_DEBUG_ENGINE;
//#endif //#endif
hr = FAudioCreate(&faud, flags, FAUDIO_DEFAULT_PROCESSOR); hr = FAudioCreate(&faud, flags, FAUDIO_DEFAULT_PROCESSOR);
if (hr != S_OK) { if (hr != 0) {
wxLogError(_("The FAudio interface failed to initialize!")); wxLogError(_("The FAudio interface failed to initialize!"));
failed = true; failed = true;
return false; return false;
@ -361,27 +263,26 @@ bool FAudio_Output::init(long sampleRate)
soundBufferLen = (freq / 60) * 4; soundBufferLen = (freq / 60) * 4;
// create own buffers to store sound data because it must not be // create own buffers to store sound data because it must not be
// manipulated while the voice plays from it // manipulated while the voice plays from it
buffers = (BYTE*)malloc((bufferCount + 1) * soundBufferLen); buffers = (uint8_t*)malloc((bufferCount + 1) * soundBufferLen);
// + 1 because we need one temporary buffer when all others are in use // + 1 because we need one temporary buffer when all others are in use
WAVEFORMATEX wfx; FAudioWaveFormatEx wfx;
ZeroMemory(&wfx, sizeof(wfx)); memset(&wfx, NULL, sizeof(wfx));
wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.wFormatTag = FAUDIO_FORMAT_PCM;
wfx.nChannels = 2; wfx.nChannels = 2;
wfx.nSamplesPerSec = freq; wfx.nSamplesPerSec = freq;
wfx.wBitsPerSample = 16; wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8); wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
// create sound receiver // create sound receiver
hr = FAudio_CreateMasteringVoice( hr = FAudio_CreateMasteringVoice(faud,
faud, &mVoice,
&mVoice, FAUDIO_DEFAULT_CHANNELS,
FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE,
FAUDIO_DEFAULT_SAMPLERATE, 0,
0, FAGetDev(faud),
FAGetDev(faud), NULL);
NULL);
if (hr != S_OK) { if (hr != 0) {
wxLogError(_("FAudio: Creating mastering voice failed!")); wxLogError(_("FAudio: Creating mastering voice failed!"));
failed = true; failed = true;
return false; return false;
@ -389,10 +290,9 @@ bool FAudio_Output::init(long sampleRate)
// create sound emitter // create sound emitter
//This should be FAudio_CreateSourceVoice() //This should be FAudio_CreateSourceVoice()
//hr = faud->CreateSourceVoice(&sVoice, &wfx, 0, 4.0f, &notify); hr = FAudio_CreateSourceVoice(faud, &sVoice, &wfx, 0, 4.0f, &notify, NULL, NULL);
hr = FAudio_CreateSourceVoice(faud, &sVoice, (const FAudioWaveFormatEx*)&wfx, 0, 4.0f, &notify, NULL, NULL);
if (hr != S_OK) { if (hr != 0) {
wxLogError(_("FAudio: Creating source voice failed!")); wxLogError(_("FAudio: Creating source voice failed!"));
failed = true; failed = true;
return false; return false;
@ -400,10 +300,9 @@ bool FAudio_Output::init(long sampleRate)
if (OPTION(kSoundUpmix)) { if (OPTION(kSoundUpmix)) {
// set up stereo upmixing // set up stereo upmixing
FAudioDeviceDetails dd; FAudioDeviceDetails dd {};
ZeroMemory(&dd, sizeof(dd)); //memset(&dd, NULL, sizeof(dd));
hr = FAudio_GetDeviceDetails(faud, 0, &dd); assert(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
assert(hr == S_OK);
float* matrix = NULL; float* matrix = NULL;
matrix = (float*)malloc(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels); matrix = (float*)malloc(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels);
@ -499,8 +398,8 @@ bool FAudio_Output::init(long sampleRate)
} }
if (matrixAvailable) { if (matrixAvailable) {
hr = FAudioVoice_SetOutputMatrix(sVoice, NULL, 2, dd.OutputFormat.Format.nChannels, matrix, FAUDIO_DEFAULT_CHANNELS); //What I have here for the OperationSet maybe wrong... hr = FAudioVoice_SetOutputMatrix(sVoice, NULL, 2, dd.OutputFormat.Format.nChannels, matrix, FAUDIO_DEFAULT_CHANNELS);
assert(hr == S_OK); assert(hr == 0);
} }
free(matrix); free(matrix);
@ -508,7 +407,7 @@ bool FAudio_Output::init(long sampleRate)
} }
hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW); hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK); assert(hr == 0);
playing = true; playing = true;
currentBuffer = 0; currentBuffer = 0;
device_changed = false; device_changed = false;
@ -518,7 +417,7 @@ bool FAudio_Output::init(long sampleRate)
void FAudio_Output::write(uint16_t* finalWave, int length) void FAudio_Output::write(uint16_t* finalWave, int length)
{ {
UINT32 flags = 0; uint32_t flags = 0;
if (!initialized || failed) if (!initialized || failed)
return; return;
@ -548,7 +447,7 @@ void FAudio_Output::write(uint16_t* finalWave, int length)
// the maximum number of buffers is currently queued // the maximum number of buffers is currently queued
if (!coreOptions.speedup && coreOptions.throttle && !gba_joybus_active) { if (!coreOptions.speedup && coreOptions.throttle && !gba_joybus_active) {
// wait for one buffer to finish playing // wait for one buffer to finish playing
if (WaitForSingleObject(notify.hBufferEndEvent, 10000) == WAIT_TIMEOUT) { if (notify.WaitForSignal()) {
device_changed = true; device_changed = true;
} }
} else { } else {
@ -559,13 +458,13 @@ void FAudio_Output::write(uint16_t* finalWave, int length)
} }
// copy & protect the audio data in own memory area while playing it // copy & protect the audio data in own memory area while playing it
CopyMemory(&buffers[currentBuffer * soundBufferLen], finalWave, soundBufferLen); memcpy(&buffers[currentBuffer * soundBufferLen], finalWave, soundBufferLen);
buf.AudioBytes = soundBufferLen; buf.AudioBytes = soundBufferLen;
buf.pAudioData = &buffers[currentBuffer * soundBufferLen]; buf.pAudioData = &buffers[currentBuffer * soundBufferLen];
currentBuffer++; currentBuffer++;
currentBuffer %= (bufferCount + 1); // + 1 because we need one temporary buffer currentBuffer %= (bufferCount + 1); // + 1 because we need one temporary buffer
HRESULT hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, NULL); // send buffer to queue. uint32_t hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, NULL);
assert(hr == S_OK); assert(hr == 0);
} }
void FAudio_Output::pause() void FAudio_Output::pause()
@ -574,8 +473,8 @@ void FAudio_Output::pause()
return; return;
if (playing) { if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK); assert(hr == 0);
playing = false; playing = false;
} }
} }
@ -586,8 +485,8 @@ void FAudio_Output::resume()
return; return;
if (!playing) { if (!playing) {
HRESULT hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW); uint32_t hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK); assert(hr == 0);
playing = true; playing = true;
} }
} }
@ -598,8 +497,8 @@ void FAudio_Output::reset()
return; return;
if (playing) { if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK); assert(hr == 0);
} }
FAudioSourceVoice_FlushSourceBuffers(sVoice); FAudioSourceVoice_FlushSourceBuffers(sVoice);
@ -615,15 +514,127 @@ void FAudio_Output::setThrottle(unsigned short throttle_)
if (throttle_ == 0) if (throttle_ == 0)
throttle_ = 100; throttle_ = 100;
HRESULT hr = FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_MAX_FILTER_FREQUENCY); uint32_t hr = FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_COMMIT_NOW);
assert(hr == S_OK); assert(hr == 0);
} }
void faudio_device_changed(FAudio_Output* instance) FAudio_Device_Notifier::FAudio_Device_Notifier()
: registered(0)
{ {
instance->device_change(); InitializeCriticalSection(&lock);
}
FAudio_Device_Notifier::~FAudio_Device_Notifier()
{
DeleteCriticalSection(&lock);
} }
ULONG STDMETHODCALLTYPE FAudio_Device_Notifier::AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE FAudio_Device_Notifier::Release()
{
return 1;
}
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::QueryInterface(REFIID riid, VOID** ppvInterface)
{
if (IID_IUnknown == riid) {
*ppvInterface = (IUnknown*)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
*ppvInterface = (IMMNotificationClient*)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
{
if (flow == eRender && last_device.compare(pwstrDeviceId) != 0) {
last_device = pwstrDeviceId;
EnterCriticalSection(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it) {
(*it)->device_change();
}
LeaveCriticalSection(&lock);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; }
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; }
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { return S_OK; }
HRESULT STDMETHODCALLTYPE FAudio_Device_Notifier::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { return S_OK; }
void FAudio_Device_Notifier::do_register(FAudio_Output* p_instance)
{
if (InterlockedIncrement(&registered) == 1) {
pEnumerator = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if (SUCCEEDED(hr)) {
pEnumerator->RegisterEndpointNotificationCallback(this);
}
}
EnterCriticalSection(&lock);
instances.push_back(p_instance);
LeaveCriticalSection(&lock);
}
void FAudio_Device_Notifier::do_unregister(FAudio_Output* p_instance)
{
if (InterlockedDecrement(&registered) == 0) {
if (pEnumerator) {
pEnumerator->UnregisterEndpointNotificationCallback(this);
pEnumerator->Release();
pEnumerator = NULL;
}
}
EnterCriticalSection(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it) {
if (*it == p_instance) {
instances.erase(it);
break;
}
}
LeaveCriticalSection(&lock);
}
} // namespace
bool GetFADevices(wxArrayString& names, wxArrayString& ids)
{
uint32_t hr;
FAudio* fa = NULL;
uint32_t flags = 0;
#ifdef _DEBUG
flags = FAUDIO_DEBUG_ENGINE;
#endif
hr = FAudioCreate(&fa, flags, FAUDIO_DEFAULT_PROCESSOR);
if (hr != 0) {
wxLogError(_("The FAudio interface failed to initialize!"));
return false;
}
GetFADevices(fa, &names, &ids, NULL);
FAudio_Release(fa);
return true;
}
// Class Declaration
SoundDriver* newFAudio_Output() SoundDriver* newFAudio_Output()
{ {
return new FAudio_Output(); return new FAudio_Output();