Compare commits

...

5 Commits

Author SHA1 Message Date
Zach Bacon f0a39ec189
Merge d458f0f29e into 215e3c5ae9 2024-03-05 22:30:56 +00:00
Zach Bacon d458f0f29e FAudio: Had to pass function pointers from the struct FAudioVoiceCallBack
In Short, audio works. Just gotta deal with the windows event system
pthreads might have what I need, winpthreads can even be used on windows

Signed-off-by: Zach Bacon <zachbacon@vba-m.com>
2024-03-05 17:30:35 -05:00
Zach Bacon 56056f1e9d FAudio fix ups
Slowly attempt to make FAudio implementation not use imme code
Investigating a cross platform query hardware method
Still a work in progress

Signed-off-by: Zach Bacon <zachbacon@vba-m.com>
2024-03-05 17:30:35 -05:00
Zach Bacon 41552e372a src/wx/faudio.cpp: fix some misconceptions
R_OK isn't a replacement for S_OK, according to https://github.com/FNA-XNA/FAudio/blob/master/tests/FAudio_compat.h
it should be defined as 0

Signed-off-by: Zach Bacon <zachbacon@vba-m.com>
2024-03-05 17:30:35 -05:00
Zach Bacon 75355eca4c faudio.cpp: Begin rework of faudio implementation
This begins the rework of the faudio code begin with removing a good
deal of the imme code that the driver used to poll audio interfaces

Removed the use of HRESULT code since that's more windowisms.

Also adapted more of the faudio stuff like WaveFormatEx is FAudioWaveFormatEx
WAVE_FORMAT_PCM is FAUDIO_FORMAT_PCM.

Also changed S_OK into R_OK but I'm pretty sure that isn't the way to go.
That said and done, this is still a work in progress, but it compiles. But
it doesn't initilize yet, I might need an extra set of eyes for that.

Signed-off-by: Zach Bacon <zachbacon@vba-m.com>
2024-03-05 17:30:35 -05:00
1 changed files with 103 additions and 152 deletions

View File

@ -8,10 +8,8 @@
#include "../common/SoundDriver.h"
// FAudio
#include <faudio.h>
// MMDevice API
#include <mmdeviceapi.h>
#include <FAudio.h>
#include <pthread.h>
#include <string>
#include <vector>
@ -22,27 +20,26 @@
int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids,
const wxString* match)
{
HRESULT hr;
UINT32 dev_count = 0;
uint32_t hr;
uint32_t dev_count = 0;
hr = FAudio_GetDeviceCount(fa, &dev_count);
if (hr != S_OK) {
if (hr != 0) {
wxLogError(_("FAudio: Enumerating devices failed!"));
return true;
} else {
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);
if (hr != S_OK) {
if (hr != 0) {
continue;
} else {
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);
} else if (*match == dd.DeviceID)
} else if (*match == wxString((wchar_t*) dd.DeviceID))
return i;
}
}
@ -53,22 +50,20 @@ int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids,
bool GetFADevices(wxArrayString& names, wxArrayString& ids)
{
HRESULT hr;
uint32_t hr;
FAudio* fa = NULL;
UINT32 flags = 0;
uint32_t flags = 0;
#ifdef _DEBUG
flags = FAUDIO_DEBUG_ENGINE;
#endif
hr = FAudioCreate(&fa, flags, FAUDIO_DEFAULT_PROCESSOR);
hr = FAudioCreate(&fa, flags, FAUDIO_DEFAULT_PROCESSOR); //Apparently this needs 3 parameters, the processor.
if (hr != S_OK) {
if (hr != 0) {
wxLogError(_("The FAudio interface failed to initialize!"));
return false;
}
GetFADevices(fa, &names, &ids, NULL);
//fa->Release();
FAudio_Release(fa);
return true;
}
@ -87,98 +82,56 @@ class FAudio_Output;
static void faudio_device_changed(FAudio_Output*);
class FAudio_Device_Notifier : public IMMNotificationClient {
volatile LONG registered;
IMMDeviceEnumerator* pEnumerator;
class FAudio_Device_Notifier {
std::wstring last_device;
CRITICAL_SECTION lock;
pthread_mutex_t lock;
std::vector<FAudio_Output*> instances;
public:
FAudio_Device_Notifier()
: registered(0)
{
InitializeCriticalSection(&lock);
pthread_mutex_init(&lock, NULL);
}
~FAudio_Device_Notifier()
{
DeleteCriticalSection(&lock);
pthread_mutex_destroy(&lock);
}
ULONG STDMETHODCALLTYPE AddRef()
ULONG FAUDIOAPI AddRef()
{
return 1;
}
ULONG STDMETHODCALLTYPE Release()
ULONG FAUDIOAPI Release()
{
return 1;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface)
//uint32_t FAUDIOAPI QueryInterface or it should be, I still need to find a cross platform way to query devices.
void OnDefaultDeviceChanged() // This is the callback that is called when the default device is changed. The XAudio2 driver leverages mmdeviceapi, but here we can't rely on that, so we need to find a method that work on all platforms.
{
if (IID_IUnknown == riid) {
*ppvInterface = (IUnknown*)this;
} else if (__uuidof(IMMNotificationClient) == riid) {
*ppvInterface = (IMMNotificationClient*)this;
} else {
*ppvInterface = NULL;
return E_NOINTERFACE;
pthread_mutex_lock(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it)
{
faudio_device_changed(*it);
}
return S_OK;
pthread_mutex_unlock(&lock);
}
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);
pthread_mutex_lock(&lock);
instances.push_back(p_instance);
LeaveCriticalSection(&lock);
pthread_mutex_unlock(&lock);
}
void do_unregister(FAudio_Output* p_instance)
{
if (InterlockedDecrement(&registered) == 0) {
if (pEnumerator) {
pEnumerator->UnregisterEndpointNotificationCallback(this);
pEnumerator->Release();
pEnumerator = NULL;
}
}
EnterCriticalSection(&lock);
pthread_mutex_lock(&lock);
for (auto it = instances.begin(); it < instances.end(); ++it) {
if (*it == p_instance) {
@ -186,51 +139,52 @@ public:
break;
}
}
LeaveCriticalSection(&lock);
pthread_mutex_unlock(&lock);
}
} g_notifier;
// Synchronization Event
class FAudio_BufferNotify : public FAudioVoiceCallback {
public:
HANDLE hBufferEndEvent;
void *hBufferEndEvent;
FAudio_BufferNotify()
{
hBufferEndEvent = NULL;
hBufferEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
assert(hBufferEndEvent != NULL);
//I'm still figuring out how to deal with pthreads, so I'm going to leave this empty for now.
//hBufferEndEvent = pthread_cond_init();
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()
{
CloseHandle(hBufferEndEvent);
hBufferEndEvent = NULL;
//I'm still figuring out how to deal with pthreads, so I'm going to leave this empty for now.
//pthread_cond_destroy(hBufferEndEvent);
}
STDMETHOD_(void, OnBufferEnd)
(void* pBufferContext)
{
assert(hBufferEndEvent != NULL);
SetEvent(hBufferEndEvent);
static void StaticOnBufferEnd(FAudioVoiceCallback* callback, void * pBufferContext) {
FAudio_BufferNotify* self = static_cast<FAudio_BufferNotify*>(callback);
if (self != nullptr && self->hBufferEndEvent != NULL)
{
SetEvent(self->hBufferEndEvent);
}
}
// dummies:
STDMETHOD_(void, OnVoiceProcessingPassStart)
(UINT32 BytesRequired) {}
STDMETHOD_(void, OnVoiceProcessingPassEnd)
() {}
STDMETHOD_(void, OnStreamEnd)
() {}
STDMETHOD_(void, OnBufferStart)
(void* pBufferContext) {}
STDMETHOD_(void, OnLoopEnd)
(void* pBufferContext) {}
STDMETHOD_(void, OnVoiceError)
(void* pBufferContext, HRESULT Error){};
static void StaticOnVoiceProcessingPassStart(FAudioVoiceCallback* callback, uint32_t BytesRequired) {}
static void StaticOnVoiceProcessingPassEnd(FAudioVoiceCallback* callback) {}
static void StaticOnStreamEnd(FAudioVoiceCallback* callback) {}
static void StaticOnBufferStart(FAudioVoiceCallback* callback, void * pBufferContext) {}
static void StaticOnLoopEnd(FAudioVoiceCallback* callback, void * pBufferContext) {}
static void StaticOnVoiceError(FAudioVoiceCallback* callback, void * pBufferContext, uint32_t Error) {}
};
// Class Declaration
class FAudio_Output
: public SoundDriver {
@ -258,9 +212,9 @@ private:
bool failed;
bool initialized;
bool playing;
UINT32 freq;
UINT32 bufferCount;
BYTE* buffers;
uint32_t freq;
uint32_t bufferCount;
uint8_t* buffers;
int currentBuffer;
int soundBufferLen;
@ -288,8 +242,8 @@ FAudio_Output::FAudio_Output()
faud = NULL;
mVoice = NULL;
sVoice = NULL;
ZeroMemory(&buf, sizeof(buf));
ZeroMemory(&vState, sizeof(vState));
memset(&buf, NULL, sizeof(buf));
memset(&vState, NULL, sizeof(vState));
g_notifier.do_register(this);
}
@ -305,8 +259,7 @@ void FAudio_Output::close()
if (sVoice) {
if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK);
assert(FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW) == 0);
}
FAudioVoice_DestroyVoice(sVoice);
@ -339,15 +292,15 @@ bool FAudio_Output::init(long sampleRate)
if (failed || initialized)
return false;
HRESULT hr;
uint32_t hr;
// Initialize FAudio
UINT32 flags = 0;
uint32_t flags = 0;
//#ifdef _DEBUG
// flags = FAUDIO_DEBUG_ENGINE;
//#endif
hr = FAudioCreate(&faud, flags, FAUDIO_DEFAULT_PROCESSOR);
if (hr != S_OK) {
if (hr != 0) {
wxLogError(_("The FAudio interface failed to initialize!"));
failed = true;
return false;
@ -359,27 +312,26 @@ bool FAudio_Output::init(long sampleRate)
soundBufferLen = (freq / 60) * 4;
// create own buffers to store sound data because it must not be
// 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
WAVEFORMATEX wfx;
ZeroMemory(&wfx, sizeof(wfx));
wfx.wFormatTag = WAVE_FORMAT_PCM;
FAudioWaveFormatEx wfx;
memset(&wfx, NULL, sizeof(wfx));
wfx.wFormatTag = FAUDIO_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = freq;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
// create sound receiver
hr = FAudio_CreateMasteringVoice(
faud,
&mVoice,
FAUDIO_DEFAULT_CHANNELS,
FAUDIO_DEFAULT_SAMPLERATE,
0,
FAGetDev(faud),
NULL);
hr = FAudio_CreateMasteringVoice(faud,
&mVoice,
FAUDIO_DEFAULT_CHANNELS,
FAUDIO_DEFAULT_SAMPLERATE,
0,
FAGetDev(faud),
NULL);
if (hr != S_OK) {
if (hr != 0) {
wxLogError(_("FAudio: Creating mastering voice failed!"));
failed = true;
return false;
@ -388,9 +340,9 @@ bool FAudio_Output::init(long sampleRate)
// create sound emitter
//This should be FAudio_CreateSourceVoice()
//hr = faud->CreateSourceVoice(&sVoice, &wfx, 0, 4.0f, &notify);
hr = FAudio_CreateSourceVoice(faud, &sVoice, (const FAudioWaveFormatEx*)&wfx, 0, 4.0f, &notify, NULL, NULL);
hr = FAudio_CreateSourceVoice(faud, &sVoice, &wfx, 0, 4.0f, &notify, NULL, NULL);
if (hr != S_OK) {
if (hr != 0) {
wxLogError(_("FAudio: Creating source voice failed!"));
failed = true;
return false;
@ -399,9 +351,8 @@ bool FAudio_Output::init(long sampleRate)
if (gopts.upmix) {
// set up stereo upmixing
FAudioDeviceDetails dd;
ZeroMemory(&dd, sizeof(dd));
hr = FAudio_GetDeviceDetails(faud, 0, &dd);
assert(hr == S_OK);
memset(&dd, NULL, sizeof(dd));
assert(FAudio_GetDeviceDetails(faud, 0, &dd) == 0);
float* matrix = NULL;
matrix = (float*)malloc(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels);
@ -497,8 +448,8 @@ bool FAudio_Output::init(long sampleRate)
}
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...
assert(hr == S_OK);
hr = FAudioVoice_SetOutputMatrix(sVoice, NULL, 2, dd.OutputFormat.Format.nChannels, matrix, FAUDIO_DEFAULT_CHANNELS);
assert(hr == 0);
}
free(matrix);
@ -506,7 +457,7 @@ bool FAudio_Output::init(long sampleRate)
}
hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK);
assert(hr == 0);
playing = true;
currentBuffer = 0;
device_changed = false;
@ -516,7 +467,7 @@ bool FAudio_Output::init(long sampleRate)
void FAudio_Output::write(uint16_t* finalWave, int length)
{
UINT32 flags = 0;
uint32_t flags = 0;
if (!initialized || failed)
return;
@ -547,7 +498,7 @@ void FAudio_Output::write(uint16_t* finalWave, int length)
if (!coreOptions.speedup && coreOptions.throttle && !gba_joybus_active) {
// wait for one buffer to finish playing
if (WaitForSingleObject(notify.hBufferEndEvent, 10000) == WAIT_TIMEOUT) {
device_changed = true;
device_changed = true;
}
} else {
// drop current audio frame
@ -557,13 +508,13 @@ void FAudio_Output::write(uint16_t* finalWave, int length)
}
// 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.pAudioData = &buffers[currentBuffer * soundBufferLen];
currentBuffer++;
currentBuffer %= (bufferCount + 1); // + 1 because we need one temporary buffer
HRESULT hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, NULL); // send buffer to queue.
assert(hr == S_OK);
uint32_t hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, NULL);
assert(hr == 0);
}
void FAudio_Output::pause()
@ -572,8 +523,8 @@ void FAudio_Output::pause()
return;
if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK);
uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == 0);
playing = false;
}
}
@ -584,8 +535,8 @@ void FAudio_Output::resume()
return;
if (!playing) {
HRESULT hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK);
uint32_t hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == 0);
playing = true;
}
}
@ -596,8 +547,8 @@ void FAudio_Output::reset()
return;
if (playing) {
HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == S_OK);
uint32_t hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW);
assert(hr == 0);
}
FAudioSourceVoice_FlushSourceBuffers(sVoice);
@ -613,8 +564,8 @@ void FAudio_Output::setThrottle(unsigned short throttle_)
if (throttle_ == 0)
throttle_ = 100;
HRESULT hr = FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_MAX_FILTER_FREQUENCY);
assert(hr == S_OK);
uint32_t hr = FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_MAX_FILTER_FREQUENCY);
assert(hr == 0);
}
void faudio_device_changed(FAudio_Output* instance)