Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow WASAPI device switching #11438

Merged
merged 4 commits into from
Oct 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,10 @@ add_dependencies(${CoreLibName} GitVersion)
set(WindowsFiles
Windows/DSoundStream.cpp
Windows/DSoundStream.h
Windows/WindowsAudio.cpp
Windows/WindowsAudio.h
Windows/WASAPIStream.cpp
Windows/WASAPIStream.h
Windows/Debugger/CPURegsInterface.h
Windows/Debugger/BreakpointWindow.cpp
Windows/Debugger/BreakpointWindow.h
Expand Down
2 changes: 1 addition & 1 deletion UI/NativeApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
#include <thread>

#if defined(_WIN32)
#include "Windows/DSoundStream.h"
#include "Windows/WindowsAudio.h"
#include "Windows/MainWindow.h"
#endif

Expand Down
302 changes: 5 additions & 297 deletions Windows/DSoundStream.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "Common/CommonWindows.h"
#include <mmreg.h>
#include <MMReg.h>
#include <process.h>

#ifdef __MINGW32__
Expand All @@ -11,70 +11,14 @@
#endif

#include "thread/threadutil.h"
#include "Common/Log.h"
#include "Common/OSVersion.h"
#include "Core/ConfigValues.h"
#include "Core/Reporting.h"
#include "Core/Util/AudioFormat.h"
#include "Windows/W32Util/Misc.h"

#include "dsoundstream.h"

// WASAPI begin
#include <Objbase.h>
#include <Mmreg.h>
#include <MMDeviceAPI.h>
#include <AudioClient.h>
#include <AudioPolicy.h>

#pragma comment(lib, "ole32.lib")

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
// WASAPI end

#define BUFSIZE 0x4000
#define MAXWAIT 20 //ms

class DSoundAudioBackend : public WindowsAudioBackend {
public:
DSoundAudioBackend();
~DSoundAudioBackend() override;

bool Init(HWND window, StreamCallback callback, int sampleRate) override; // If fails, can safely delete the object
void Update() override;
int GetSampleRate() override { return sampleRate_; }

private:
inline int ModBufferSize(int x) { return (x + bufferSize_) % bufferSize_; }
int RunThread();
static unsigned int WINAPI soundThread(void *param);
bool CreateBuffer();
bool WriteDataToBuffer(DWORD offset, // Our own write cursor.
char* soundData, // Start of our data.
DWORD soundBytes); // Size of block to copy.

CRITICAL_SECTION soundCriticalSection;
HWND window_;
HANDLE soundSyncEvent_ = NULL;
HANDLE hThread_ = NULL;

StreamCallback callback_;

IDirectSound8 *ds_ = NULL;
IDirectSoundBuffer *dsBuffer_ = NULL;

int bufferSize_; // bytes
int totalRenderedBytes_;
int sampleRate_;

volatile int threadData_;

int currentPos_;
int lastPos_;
short realtimeBuffer_[BUFSIZE * 2];
};
#include "DSoundStream.h"

// TODO: Get rid of this
static DSoundAudioBackend *g_dsound;
Expand Down Expand Up @@ -161,7 +105,7 @@ int DSoundAudioBackend::RunThread() {
InitializeCriticalSection(&soundCriticalSection);

DWORD num1;
short *p1;
int16_t *p1;

dsBuffer_->Lock(0, bufferSize_, (void **)&p1, &num1, 0, 0, 0);

Expand All @@ -173,7 +117,7 @@ int DSoundAudioBackend::RunThread() {
currentPos_ = 0;
lastPos_ = 0;

dsBuffer_->Play(0,0,DSBPLAY_LOOPING);
dsBuffer_->Play(0, 0, DSBPLAY_LOOPING);

while (!threadData_) {
EnterCriticalSection(&soundCriticalSection);
Expand Down Expand Up @@ -250,239 +194,3 @@ void DSoundAudioBackend::Update() {
if (soundSyncEvent_ != NULL)
SetEvent(soundSyncEvent_);
}

class WASAPIAudioBackend : public WindowsAudioBackend {
public:
WASAPIAudioBackend();
~WASAPIAudioBackend() override;

bool Init(HWND window, StreamCallback callback, int sampleRate) override; // If fails, can safely delete the object
void Update() override {}
int GetSampleRate() override { return sampleRate_; }

private:
int RunThread();
static unsigned int WINAPI soundThread(void *param);

HANDLE hThread_;

StreamCallback callback_;
int sampleRate_;

volatile int threadData_;
};

// TODO: Make these adjustable. This is from the example in MSDN.
// 200 times/sec = 5ms, pretty good :) Wonder if all computers can handle it though.
#define REFTIMES_PER_SEC (10000000/200)
#define REFTIMES_PER_MILLISEC (REFTIMES_PER_SEC / 1000)

WASAPIAudioBackend::WASAPIAudioBackend() : hThread_(NULL), sampleRate_(0), callback_(nullptr), threadData_(0) {
}

WASAPIAudioBackend::~WASAPIAudioBackend() {
if (threadData_ == 0) {
threadData_ = 1;
}

if (hThread_ != NULL) {
WaitForSingleObject(hThread_, 1000);
CloseHandle(hThread_);
hThread_ = NULL;
}

if (threadData_ == 2) {
// blah.
}
}

unsigned int WINAPI WASAPIAudioBackend::soundThread(void *param) {
WASAPIAudioBackend *backend = (WASAPIAudioBackend *)param;
return backend->RunThread();
}

bool WASAPIAudioBackend::Init(HWND window, StreamCallback callback, int sampleRate) {
threadData_ = 0;
callback_ = callback;
sampleRate_ = sampleRate;
hThread_ = (HANDLE)_beginthreadex(0, 0, soundThread, (void *)this, 0, 0);
SetThreadPriority(hThread_, THREAD_PRIORITY_ABOVE_NORMAL);
return true;
}

int WASAPIAudioBackend::RunThread() {
// Adapted from http://msdn.microsoft.com/en-us/library/windows/desktop/dd316756(v=vs.85).aspx

CoInitializeEx(NULL, COINIT_MULTITHREADED);
setCurrentThreadName("WASAPI_audio");

IMMDeviceEnumerator *pDeviceEnumerator;
IMMDevice *pDevice;
IAudioClient *pAudioInterface;
IAudioRenderClient *pAudioRenderClient;
WAVEFORMATEXTENSIBLE *pDeviceFormat;
DWORD flags = 0;
REFERENCE_TIME hnsBufferDuration, hnsActualDuration;
UINT32 pNumBufferFrames;
UINT32 pNumPaddingFrames, pNumAvFrames;
short *shortBuf = nullptr;
int numSamples;
hnsBufferDuration = REFTIMES_PER_SEC;

enum {
UNKNOWN_FORMAT = 0,
IEEE_FLOAT = 1,
PCM16 = 2,
} format = UNKNOWN_FORMAT;

HRESULT hresult;
hresult = CoCreateInstance(CLSID_MMDeviceEnumerator,
NULL, /*Object is not created as the part of the aggregate */
CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator);
if (FAILED(hresult)) goto bail;

hresult = pDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
if (FAILED(hresult)) {
pDeviceEnumerator->Release();
goto bail;
}

hresult = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioInterface);
if (FAILED(hresult)) {
pDevice->Release();
pDeviceEnumerator->Release();
goto bail;
}

hresult = pAudioInterface->GetMixFormat((WAVEFORMATEX**)&pDeviceFormat);
hresult = pAudioInterface->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsBufferDuration, 0, &pDeviceFormat->Format, NULL);
hresult = pAudioInterface->GetService(IID_IAudioRenderClient, (void**)&pAudioRenderClient);
if (FAILED(hresult)) {
pDevice->Release();
pDeviceEnumerator->Release();
pAudioInterface->Release();
goto bail;
}
hresult = pAudioInterface->GetBufferSize(&pNumBufferFrames);
if (FAILED(hresult)) {
pDevice->Release();
pDeviceEnumerator->Release();
pAudioInterface->Release();
goto bail;
}

sampleRate_ = pDeviceFormat->Format.nSamplesPerSec;

// Don't know if PCM16 ever shows up here, the documentation only talks about float... but let's blindly
// try to support it :P

if (pDeviceFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
if (!memcmp(&pDeviceFormat->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(pDeviceFormat->SubFormat))) {
format = IEEE_FLOAT;
// printf("float format\n");
} else {
ERROR_LOG_REPORT_ONCE(unexpectedformat, SCEAUDIO, "Got unexpected WASAPI 0xFFFE stream format, expected float!");
if (pDeviceFormat->Format.wBitsPerSample == 16 && pDeviceFormat->Format.nChannels == 2) {
format = PCM16;
}
}
} else {
ERROR_LOG_REPORT_ONCE(unexpectedformat2, SCEAUDIO, "Got unexpected non-extensible WASAPI stream format, expected extensible float!");
if (pDeviceFormat->Format.wBitsPerSample == 16 && pDeviceFormat->Format.nChannels == 2) {
format = PCM16;
}
}


BYTE *pData;
hresult = pAudioRenderClient->GetBuffer(pNumBufferFrames, &pData);
numSamples = pNumBufferFrames * pDeviceFormat->Format.nChannels;
if (format == IEEE_FLOAT) {
memset(pData, 0, sizeof(float) * numSamples);
shortBuf = new short[pNumBufferFrames * pDeviceFormat->Format.nChannels];
} else if (format == PCM16) {
memset(pData, 0, sizeof(short) * numSamples);
}

hresult = pAudioRenderClient->ReleaseBuffer(pNumBufferFrames, flags);
hnsActualDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC * pNumBufferFrames / pDeviceFormat->Format.nSamplesPerSec);

hresult = pAudioInterface->Start();

while (flags != AUDCLNT_BUFFERFLAGS_SILENT) {
Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2));

hresult = pAudioInterface->GetCurrentPadding(&pNumPaddingFrames);
if (FAILED(hresult)) {
// What to do?
pNumPaddingFrames = 0;
}
pNumAvFrames = pNumBufferFrames - pNumPaddingFrames;

hresult = pAudioRenderClient->GetBuffer(pNumAvFrames, &pData);
if (FAILED(hresult)) {
// What to do?
} else if (pNumAvFrames) {
switch (format) {
case IEEE_FLOAT:
callback_(shortBuf, pNumAvFrames, 16, sampleRate_, 2);
if (pDeviceFormat->Format.nChannels == 2) {
ConvertS16ToF32((float *)pData, shortBuf, pNumAvFrames * pDeviceFormat->Format.nChannels);
} else {
float *ptr = (float *)pData;
int chans = pDeviceFormat->Format.nChannels;
memset(ptr, 0, pNumAvFrames * chans * sizeof(float));
for (UINT32 i = 0; i < pNumAvFrames; i++) {
ptr[i * chans + 0] = (float)shortBuf[i * 2] * (1.0f / 32768.0f);
ptr[i * chans + 1] = (float)shortBuf[i * 2 + 1] * (1.0f / 32768.0f);
}
}
break;
case PCM16:
callback_((short *)pData, pNumAvFrames, 16, sampleRate_, 2);
break;
}
}

if (threadData_ != 0) {
flags = AUDCLNT_BUFFERFLAGS_SILENT;
}

hresult = pAudioRenderClient->ReleaseBuffer(pNumAvFrames, flags);
if (FAILED(hresult)) {
// Not much to do here either...
}
}

// Wait for last data in buffer to play before stopping.
Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2));

delete[] shortBuf;
hresult = pAudioInterface->Stop();

CoTaskMemFree(pDeviceFormat);
pDeviceEnumerator->Release();
pDevice->Release();
pAudioInterface->Release();
pAudioRenderClient->Release();

bail:
threadData_ = 2;
CoUninitialize();
return 0;
}

WindowsAudioBackend *CreateAudioBackend(AudioBackendType type) {
if (IsVistaOrHigher()) {
switch (type) {
case AUDIO_BACKEND_WASAPI:
case AUDIO_BACKEND_AUTO:
return new WASAPIAudioBackend();
case AUDIO_BACKEND_DSOUND:
default:
return new DSoundAudioBackend();
}
} else {
return new DSoundAudioBackend();
}
}
Loading