Initial work on switching to faudio, WIP
This commit is contained in:
parent
0a40ca7ab0
commit
e00aca18df
|
@ -0,0 +1,625 @@
|
|||
#ifndef NO_XAUDIO2
|
||||
|
||||
// Application
|
||||
#include "wxvbam.h"
|
||||
#include <stdio.h>
|
||||
|
||||
// Interface
|
||||
#include "../common/ConfigManager.h"
|
||||
#include "../common/SoundDriver.h"
|
||||
|
||||
// XAudio2
|
||||
#include <faudio.h>
|
||||
#endif
|
||||
|
||||
// MMDevice API
|
||||
#include <mmdeviceapi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Internals
|
||||
#include "../System.h" // for systemMessage()
|
||||
#include "../gba/Globals.h"
|
||||
|
||||
int GetXA2Devices(IXAudio2* xa, wxArrayString* names, wxArrayString* ids,
|
||||
const wxString* match)
|
||||
{
|
||||
HRESULT hr;
|
||||
UINT32 dev_count = 0;
|
||||
hr = xa->FAudio_GetDeviceCount(&dev_count);
|
||||
|
||||
if (hr != S_OK) {
|
||||
wxLogError(_("XAudio2: Enumerating devices failed!"));
|
||||
return true;
|
||||
} else {
|
||||
XAUDIO2_DEVICE_DETAILS dd;
|
||||
|
||||
for (UINT32 i = 0; i < dev_count; i++) {
|
||||
hr = xa->GetDeviceDetails(i, &dd);
|
||||
|
||||
if (hr != S_OK) {
|
||||
continue;
|
||||
} else {
|
||||
if (ids) {
|
||||
ids->push_back(dd.DeviceID);
|
||||
names->push_back(dd.DisplayName);
|
||||
} else if (*match == dd.DeviceID)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool GetXA2Devices(wxArrayString& names, wxArrayString& ids)
|
||||
{
|
||||
HRESULT hr;
|
||||
IXAudio2* xa = NULL;
|
||||
UINT32 flags = 0;
|
||||
#ifdef _DEBUG
|
||||
flags = FAUDIO_DEBUG_ENGINE;
|
||||
#endif
|
||||
hr = XAudio2Create(&xa, flags);
|
||||
|
||||
if (hr != S_OK) {
|
||||
wxLogError(_("The XAudio2 interface failed to initialize!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
GetXA2Devices(xa, &names, &ids, NULL);
|
||||
xa->Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
static int XA2GetDev(IXAudio2* xa)
|
||||
{
|
||||
if (gopts.audio_dev.empty())
|
||||
return 0;
|
||||
else {
|
||||
int ret = GetXA2Devices(xa, NULL, NULL, &gopts.audio_dev);
|
||||
return ret < 0 ? 0 : ret;
|
||||
}
|
||||
}
|
||||
|
||||
class XAudio2_Output;
|
||||
|
||||
static void xaudio2_device_changed(XAudio2_Output*);
|
||||
|
||||
class XAudio2_Device_Notifier : public IMMNotificationClient {
|
||||
volatile LONG registered;
|
||||
IMMDeviceEnumerator* pEnumerator;
|
||||
|
||||
std::wstring last_device;
|
||||
|
||||
CRITICAL_SECTION lock;
|
||||
std::vector<XAudio2_Output*> instances;
|
||||
|
||||
public:
|
||||
XAudio2_Device_Notifier()
|
||||
: registered(0)
|
||||
{
|
||||
InitializeCriticalSection(&lock);
|
||||
}
|
||||
~XAudio2_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) {
|
||||
xaudio2_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(XAudio2_Output* p_instance)
|
||||
{
|
||||
if (InterlockedIncrement(®istered) == 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(XAudio2_Output* p_instance)
|
||||
{
|
||||
if (InterlockedDecrement(®istered) == 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 XAudio2_BufferNotify : public IXAudio2VoiceCallback {
|
||||
public:
|
||||
HANDLE hBufferEndEvent;
|
||||
|
||||
XAudio2_BufferNotify()
|
||||
{
|
||||
hBufferEndEvent = NULL;
|
||||
hBufferEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
assert(hBufferEndEvent != NULL);
|
||||
}
|
||||
|
||||
~XAudio2_BufferNotify()
|
||||
{
|
||||
CloseHandle(hBufferEndEvent);
|
||||
hBufferEndEvent = NULL;
|
||||
}
|
||||
|
||||
STDMETHOD_(void, OnBufferEnd)
|
||||
(void* pBufferContext)
|
||||
{
|
||||
assert(hBufferEndEvent != NULL);
|
||||
SetEvent(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){};
|
||||
};
|
||||
|
||||
// Class Declaration
|
||||
class XAudio2_Output
|
||||
: public SoundDriver {
|
||||
public:
|
||||
XAudio2_Output();
|
||||
~XAudio2_Output();
|
||||
|
||||
// Initialization
|
||||
bool init(long sampleRate);
|
||||
|
||||
// Sound Data Feed
|
||||
void write(uint16_t* finalWave, int length);
|
||||
|
||||
// Play Control
|
||||
void pause();
|
||||
void resume();
|
||||
void reset();
|
||||
void close();
|
||||
void device_change();
|
||||
|
||||
// Configuration Changes
|
||||
void setThrottle(unsigned short throttle);
|
||||
|
||||
private:
|
||||
bool failed;
|
||||
bool initialized;
|
||||
bool playing;
|
||||
UINT32 freq;
|
||||
UINT32 bufferCount;
|
||||
BYTE* buffers;
|
||||
int currentBuffer;
|
||||
int soundBufferLen;
|
||||
|
||||
volatile bool device_changed;
|
||||
|
||||
IXAudio2* xaud;
|
||||
IXAudio2MasteringVoice* mVoice; // listener
|
||||
IXAudio2SourceVoice* sVoice; // sound source
|
||||
XAUDIO2_BUFFER buf;
|
||||
XAUDIO2_VOICE_STATE vState;
|
||||
XAudio2_BufferNotify notify; // buffer end notification
|
||||
};
|
||||
|
||||
// Class Implementation
|
||||
XAudio2_Output::XAudio2_Output()
|
||||
{
|
||||
failed = false;
|
||||
initialized = false;
|
||||
playing = false;
|
||||
freq = 0;
|
||||
bufferCount = gopts.audio_buffers;
|
||||
buffers = NULL;
|
||||
currentBuffer = 0;
|
||||
device_changed = false;
|
||||
xaud = NULL;
|
||||
mVoice = NULL;
|
||||
sVoice = NULL;
|
||||
ZeroMemory(&buf, sizeof(buf));
|
||||
ZeroMemory(&vState, sizeof(vState));
|
||||
g_notifier.do_register(this);
|
||||
}
|
||||
|
||||
XAudio2_Output::~XAudio2_Output()
|
||||
{
|
||||
g_notifier.do_unregister(this);
|
||||
close();
|
||||
}
|
||||
|
||||
void XAudio2_Output::close()
|
||||
{
|
||||
initialized = false;
|
||||
|
||||
if (sVoice) {
|
||||
if (playing) {
|
||||
HRESULT hr = sVoice->Stop(0);
|
||||
assert(hr == S_OK);
|
||||
}
|
||||
|
||||
sVoice->DestroyVoice();
|
||||
sVoice = NULL;
|
||||
}
|
||||
|
||||
if (buffers) {
|
||||
free(buffers);
|
||||
buffers = NULL;
|
||||
}
|
||||
|
||||
if (mVoice) {
|
||||
mVoice->DestroyVoice();
|
||||
mVoice = NULL;
|
||||
}
|
||||
|
||||
if (xaud) {
|
||||
xaud->Release();
|
||||
xaud = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2_Output::device_change()
|
||||
{
|
||||
device_changed = true;
|
||||
}
|
||||
|
||||
bool XAudio2_Output::init(long sampleRate)
|
||||
{
|
||||
if (failed || initialized)
|
||||
return false;
|
||||
|
||||
HRESULT hr;
|
||||
// Initialize XAudio2
|
||||
UINT32 flags = 0;
|
||||
//#ifdef _DEBUG
|
||||
// flags = XAUDIO2_DEBUG_ENGINE;
|
||||
//#endif
|
||||
hr = XAudio2Create(&xaud, flags);
|
||||
|
||||
if (hr != S_OK) {
|
||||
wxLogError(_("The XAudio2 interface failed to initialize!"));
|
||||
failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
freq = sampleRate;
|
||||
// calculate the number of samples per frame first
|
||||
// then multiply it with the size of a sample frame (16 bit * stereo)
|
||||
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);
|
||||
// + 1 because we need one temporary buffer when all others are in use
|
||||
WAVEFORMATEX wfx;
|
||||
ZeroMemory(&wfx, sizeof(wfx));
|
||||
wfx.wFormatTag = WAVE_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 = xaud->CreateMasteringVoice(
|
||||
&mVoice,
|
||||
XAUDIO2_DEFAULT_CHANNELS,
|
||||
XAUDIO2_DEFAULT_SAMPLERATE,
|
||||
0,
|
||||
XA2GetDev(xaud),
|
||||
NULL);
|
||||
|
||||
if (hr != S_OK) {
|
||||
wxLogError(_("XAudio2: Creating mastering voice failed!"));
|
||||
failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// create sound emitter
|
||||
hr = xaud->CreateSourceVoice(&sVoice, &wfx, 0, 4.0f, ¬ify);
|
||||
|
||||
if (hr != S_OK) {
|
||||
wxLogError(_("XAudio2: Creating source voice failed!"));
|
||||
failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gopts.upmix) {
|
||||
// set up stereo upmixing
|
||||
XAUDIO2_DEVICE_DETAILS dd;
|
||||
ZeroMemory(&dd, sizeof(dd));
|
||||
hr = xaud->FAudio_GetDeviceDetails(0, &dd);
|
||||
assert(hr == S_OK);
|
||||
float* matrix = NULL;
|
||||
matrix = (float*)malloc(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels);
|
||||
|
||||
if (matrix == NULL)
|
||||
return false;
|
||||
|
||||
bool matrixAvailable = true;
|
||||
|
||||
switch (dd.OutputFormat.Format.nChannels) {
|
||||
case 4: // 4.0
|
||||
//Speaker \ Left Source Right Source
|
||||
/*Front L*/ matrix[0] = 1.0000f;
|
||||
matrix[1] = 0.0000f;
|
||||
/*Front R*/ matrix[2] = 0.0000f;
|
||||
matrix[3] = 1.0000f;
|
||||
/*Back L*/ matrix[4] = 1.0000f;
|
||||
matrix[5] = 0.0000f;
|
||||
/*Back R*/ matrix[6] = 0.0000f;
|
||||
matrix[7] = 1.0000f;
|
||||
break;
|
||||
|
||||
case 5: // 5.0
|
||||
//Speaker \ Left Source Right Source
|
||||
/*Front L*/ matrix[0] = 1.0000f;
|
||||
matrix[1] = 0.0000f;
|
||||
/*Front R*/ matrix[2] = 0.0000f;
|
||||
matrix[3] = 1.0000f;
|
||||
/*Front C*/ matrix[4] = 0.7071f;
|
||||
matrix[5] = 0.7071f;
|
||||
/*Side L*/ matrix[6] = 1.0000f;
|
||||
matrix[7] = 0.0000f;
|
||||
/*Side R*/ matrix[8] = 0.0000f;
|
||||
matrix[9] = 1.0000f;
|
||||
break;
|
||||
|
||||
case 6: // 5.1
|
||||
//Speaker \ Left Source Right Source
|
||||
/*Front L*/ matrix[0] = 1.0000f;
|
||||
matrix[1] = 0.0000f;
|
||||
/*Front R*/ matrix[2] = 0.0000f;
|
||||
matrix[3] = 1.0000f;
|
||||
/*Front C*/ matrix[4] = 0.7071f;
|
||||
matrix[5] = 0.7071f;
|
||||
/*LFE */ matrix[6] = 0.0000f;
|
||||
matrix[7] = 0.0000f;
|
||||
/*Side L*/ matrix[8] = 1.0000f;
|
||||
matrix[9] = 0.0000f;
|
||||
/*Side R*/ matrix[10] = 0.0000f;
|
||||
matrix[11] = 1.0000f;
|
||||
break;
|
||||
|
||||
case 7: // 6.1
|
||||
//Speaker \ Left Source Right Source
|
||||
/*Front L*/ matrix[0] = 1.0000f;
|
||||
matrix[1] = 0.0000f;
|
||||
/*Front R*/ matrix[2] = 0.0000f;
|
||||
matrix[3] = 1.0000f;
|
||||
/*Front C*/ matrix[4] = 0.7071f;
|
||||
matrix[5] = 0.7071f;
|
||||
/*LFE */ matrix[6] = 0.0000f;
|
||||
matrix[7] = 0.0000f;
|
||||
/*Side L*/ matrix[8] = 1.0000f;
|
||||
matrix[9] = 0.0000f;
|
||||
/*Side R*/ matrix[10] = 0.0000f;
|
||||
matrix[11] = 1.0000f;
|
||||
/*Back C*/ matrix[12] = 0.7071f;
|
||||
matrix[13] = 0.7071f;
|
||||
break;
|
||||
|
||||
case 8: // 7.1
|
||||
//Speaker \ Left Source Right Source
|
||||
/*Front L*/ matrix[0] = 1.0000f;
|
||||
matrix[1] = 0.0000f;
|
||||
/*Front R*/ matrix[2] = 0.0000f;
|
||||
matrix[3] = 1.0000f;
|
||||
/*Front C*/ matrix[4] = 0.7071f;
|
||||
matrix[5] = 0.7071f;
|
||||
/*LFE */ matrix[6] = 0.0000f;
|
||||
matrix[7] = 0.0000f;
|
||||
/*Back L*/ matrix[8] = 1.0000f;
|
||||
matrix[9] = 0.0000f;
|
||||
/*Back R*/ matrix[10] = 0.0000f;
|
||||
matrix[11] = 1.0000f;
|
||||
/*Side L*/ matrix[12] = 1.0000f;
|
||||
matrix[13] = 0.0000f;
|
||||
/*Side R*/ matrix[14] = 0.0000f;
|
||||
matrix[15] = 1.0000f;
|
||||
break;
|
||||
|
||||
default:
|
||||
matrixAvailable = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (matrixAvailable) {
|
||||
hr = sVoice->SetOutputMatrix(NULL, 2, dd.OutputFormat.Format.nChannels, matrix);
|
||||
assert(hr == S_OK);
|
||||
}
|
||||
|
||||
free(matrix);
|
||||
matrix = NULL;
|
||||
}
|
||||
|
||||
hr = sVoice->Start(0);
|
||||
assert(hr == S_OK);
|
||||
playing = true;
|
||||
currentBuffer = 0;
|
||||
device_changed = false;
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2_Output::write(uint16_t* finalWave, int length)
|
||||
{
|
||||
if (!initialized || failed)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
if (device_changed) {
|
||||
close();
|
||||
|
||||
if (!init(freq))
|
||||
return;
|
||||
}
|
||||
|
||||
sVoice->GetState(&vState);
|
||||
assert(vState.BuffersQueued <= bufferCount);
|
||||
|
||||
if (vState.BuffersQueued < bufferCount) {
|
||||
if (vState.BuffersQueued == 0) {
|
||||
// buffers ran dry
|
||||
if (systemVerbose & VERBOSE_SOUNDOUTPUT) {
|
||||
static unsigned int i = 0;
|
||||
log("XAudio2: Buffers were not refilled fast enough (i=%i)\n", i++);
|
||||
}
|
||||
}
|
||||
|
||||
// there is at least one free buffer
|
||||
break;
|
||||
} else {
|
||||
// the maximum number of buffers is currently queued
|
||||
if (!speedup && throttle && !gba_joybus_active) {
|
||||
// wait for one buffer to finish playing
|
||||
if (WaitForSingleObject(notify.hBufferEndEvent, 10000) == WAIT_TIMEOUT) {
|
||||
device_changed = true;
|
||||
}
|
||||
} else {
|
||||
// drop current audio frame
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy & protect the audio data in own memory area while playing it
|
||||
CopyMemory(&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 = sVoice->SubmitSourceBuffer(&buf); // send buffer to queue
|
||||
assert(hr == S_OK);
|
||||
}
|
||||
|
||||
void XAudio2_Output::pause()
|
||||
{
|
||||
if (!initialized || failed)
|
||||
return;
|
||||
|
||||
if (playing) {
|
||||
HRESULT hr = sVoice->Stop(0);
|
||||
assert(hr == S_OK);
|
||||
playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2_Output::resume()
|
||||
{
|
||||
if (!initialized || failed)
|
||||
return;
|
||||
|
||||
if (!playing) {
|
||||
HRESULT hr = sVoice->Start(0);
|
||||
assert(hr == S_OK);
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2_Output::reset()
|
||||
{
|
||||
if (!initialized || failed)
|
||||
return;
|
||||
|
||||
if (playing) {
|
||||
HRESULT hr = sVoice->Stop(0);
|
||||
assert(hr == S_OK);
|
||||
}
|
||||
|
||||
sVoice->FlushSourceBuffers();
|
||||
sVoice->Start(0);
|
||||
playing = true;
|
||||
}
|
||||
|
||||
void XAudio2_Output::setThrottle(unsigned short throttle_)
|
||||
{
|
||||
if (!initialized || failed)
|
||||
return;
|
||||
|
||||
if (throttle_ == 0)
|
||||
throttle_ = 100;
|
||||
|
||||
HRESULT hr = sVoice->SetFrequencyRatio((float)throttle_ / 100.0f);
|
||||
assert(hr == S_OK);
|
||||
}
|
||||
|
||||
void xaudio2_device_changed(XAudio2_Output* instance)
|
||||
{
|
||||
instance->device_change();
|
||||
}
|
||||
|
||||
SoundDriver* newXAudio2_Output()
|
||||
{
|
||||
return new XAudio2_Output();
|
||||
}
|
||||
|
||||
#endif // #ifndef NO_XAUDIO2
|
Loading…
Reference in New Issue