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

Fix UWP audio and a hang bug #13792

Merged
merged 3 commits into from
Dec 19, 2020
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
5 changes: 3 additions & 2 deletions UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,8 @@ void GameSettingsScreen::CreateViews() {
altVolume->SetZeroLabel(a->T("Mute"));
altVolume->SetNegativeDisable(a->T("Use global volume"));

#ifdef _WIN32
// Hide the backend selector in UWP builds (we only support XAudio2 there).
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
if (IsVistaOrHigher()) {
static const char *backend[] = { "Auto", "DSound (compatible)", "WASAPI (fast)" };
PopupMultiChoice *audioBackend = audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (restart req.)"), backend, 0, ARRAY_SIZE(backend), a->GetName(), screenManager()));
Expand All @@ -625,7 +626,7 @@ void GameSettingsScreen::CreateViews() {
#endif

std::vector<std::string> micList = Microphone::getDeviceList();
if (micList.size() >= 1) {
if (!micList.empty()) {
audioSettings->Add(new ItemHeader(a->T("Microphone")));
PopupMultiChoiceDynamic *MicChoice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sMicDevice, a->T("Microphone Device"), micList, nullptr, screenManager()));
MicChoice->OnChoice.Handle(this, &GameSettingsScreen::OnMicDeviceChange);
Expand Down
11 changes: 6 additions & 5 deletions UWP/StorageFileLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ StorageFileLoader::StorageFileLoader(Windows::Storage::StorageFile ^file) {
}

StorageFileLoader::~StorageFileLoader() {
initMutex.lock();
active_ = false;
operationRequested_ = false;
cond_.notify_all();
{
std::unique_lock<std::mutex> lock(mutex_);
active_ = false;
operationRequested_ = false;
cond_.notify_all();
}
thread_->join();
initMutex.unlock();
}

void StorageFileLoader::threadfunc() {
Expand Down
259 changes: 126 additions & 133 deletions UWP/XAudioSoundStream.cpp
Original file line number Diff line number Diff line change
@@ -1,192 +1,185 @@
#include "pch.h"

#include <algorithm>
#include <XAudio2.h>

#include <algorithm>
#include <cstdint>

#include "Common/Log.h"
#include "Common/Thread/ThreadUtil.h"
#include "XAudioSoundStream.h"

#include <process.h>

#define BUFSIZE 0x80000U
const size_t BUFSIZE = 32 * 1024;

class XAudioBackend : public WindowsAudioBackend {
public:
XAudioBackend();
~XAudioBackend() override;
XAudioBackend();
~XAudioBackend() 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_; }
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 + BUFSIZE ) % BUFSIZE; }
bool RunSound();
bool CreateBuffer();
bool WriteDataToBuffer( char* soundData, DWORD soundBytes );
void PollLoop();
bool RunSound();
bool CreateBuffer();
void PollLoop();

StreamCallback callback_;
StreamCallback callback_ = nullptr;

IXAudio2* xaudioDevice;
IXAudio2MasteringVoice* xaudioMaster;
IXAudio2SourceVoice* xaudioVoice;
IXAudio2 *xaudioDevice = nullptr;
IXAudio2MasteringVoice *xaudioMaster = nullptr;
IXAudio2SourceVoice *xaudioVoice = nullptr;

int sampleRate_;
int sampleRate_ = 0;

char realtimeBuffer_[ BUFSIZE ];
unsigned cursor_;
char realtimeBuffer_[BUFSIZE]{};
uint32_t cursor_ = 0;

HANDLE thread_;
HANDLE exitEvent_;
HANDLE thread_ = 0;
HANDLE exitEvent_ = 0;

bool exit = false;
bool exit = false;
};

// TODO: Get rid of this
static XAudioBackend *g_dsound;

inline int RoundDown128( int x ) {
return x & ( ~127 );
XAudioBackend::XAudioBackend() {
exitEvent_ = CreateEvent(nullptr, true, true, L"");
}

inline int RoundDown128(int x) {
return x & (~127);
}

bool XAudioBackend::CreateBuffer() {
if FAILED( xaudioDevice->CreateMasteringVoice( &xaudioMaster, 2, sampleRate_, 0, 0, NULL ) )
return false;

WAVEFORMATEX waveFormat;
waveFormat.cbSize = sizeof( waveFormat );
waveFormat.nAvgBytesPerSec = sampleRate_ * 4;
waveFormat.nBlockAlign = 4;
waveFormat.nChannels = 2;
waveFormat.nSamplesPerSec = sampleRate_;
waveFormat.wBitsPerSample = 16;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;

if FAILED( xaudioDevice->CreateSourceVoice( &xaudioVoice, &waveFormat, 0, XAUDIO2_DEFAULT_FREQ_RATIO, nullptr, nullptr, nullptr ) )
return false;

return true;
if FAILED(xaudioDevice->CreateMasteringVoice(&xaudioMaster, 2, sampleRate_, 0, 0, NULL))
return false;

WAVEFORMATEX waveFormat;
waveFormat.cbSize = sizeof(waveFormat);
waveFormat.nAvgBytesPerSec = sampleRate_ * 4;
waveFormat.nBlockAlign = 4;
waveFormat.nChannels = 2;
waveFormat.nSamplesPerSec = sampleRate_;
waveFormat.wBitsPerSample = 16;
waveFormat.wFormatTag = WAVE_FORMAT_PCM;

if FAILED(xaudioDevice->CreateSourceVoice(&xaudioVoice, &waveFormat, 0, 1.0, nullptr, nullptr, nullptr))
return false;

xaudioVoice->SetFrequencyRatio(1.0);
return true;
}

bool XAudioBackend::RunSound() {
if FAILED( XAudio2Create( &xaudioDevice, 0, XAUDIO2_DEFAULT_PROCESSOR ) ) {
xaudioDevice = NULL;
return false;
}

XAUDIO2_DEBUG_CONFIGURATION dbgCfg;
ZeroMemory( &dbgCfg, sizeof( dbgCfg ) );
dbgCfg.TraceMask = XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL;
//dbgCfg.BreakMask = XAUDIO2_LOG_ERRORS;
xaudioDevice->SetDebugConfiguration( &dbgCfg );

if ( !CreateBuffer() ) {
xaudioDevice->Release();
xaudioDevice = NULL;
return false;
}

cursor_ = 0;

if FAILED( xaudioVoice->Start( 0, XAUDIO2_COMMIT_NOW ) ) {
xaudioDevice->Release();
xaudioDevice = NULL;
return false;
}

thread_ = (HANDLE)_beginthreadex( 0, 0, []( void* param )
{
if FAILED(XAudio2Create(&xaudioDevice, 0, XAUDIO2_DEFAULT_PROCESSOR)) {
xaudioDevice = NULL;
return false;
}

XAUDIO2_DEBUG_CONFIGURATION dbgCfg;
ZeroMemory(&dbgCfg, sizeof(dbgCfg));
dbgCfg.TraceMask = XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL;
//dbgCfg.BreakMask = XAUDIO2_LOG_ERRORS;
xaudioDevice->SetDebugConfiguration(&dbgCfg);

if (!CreateBuffer()) {
xaudioDevice->Release();
xaudioDevice = NULL;
return false;
}

cursor_ = 0;

if FAILED(xaudioVoice->Start(0, XAUDIO2_COMMIT_NOW)) {
xaudioDevice->Release();
xaudioDevice = NULL;
return false;
}

thread_ = (HANDLE)_beginthreadex(0, 0, [](void* param)
{
setCurrentThreadName("XAudio2");
XAudioBackend *backend = (XAudioBackend *)param;
backend->PollLoop();
return 0U;
}, ( void * )this, 0, 0 );
SetThreadPriority( thread_, THREAD_PRIORITY_ABOVE_NORMAL );
XAudioBackend *backend = (XAudioBackend *)param;
backend->PollLoop();
return 0U;
}, (void *)this, 0, 0);
SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);

return true;
}

XAudioBackend::XAudioBackend() : xaudioDevice( nullptr ) {
exitEvent_ = CreateEvent( nullptr, true, true, L"" );
return true;
}

XAudioBackend::~XAudioBackend() {
if ( !xaudioDevice )
return;
if (!xaudioDevice)
return;

if ( !xaudioVoice )
return;
if (!xaudioVoice)
return;

exit = true;
WaitForSingleObject( exitEvent_, INFINITE );
CloseHandle( exitEvent_ );
exit = true;
WaitForSingleObject(exitEvent_, INFINITE);
CloseHandle(exitEvent_);

xaudioDevice->Release();
xaudioDevice->Release();
}

bool XAudioBackend::Init(HWND window, StreamCallback _callback, int sampleRate ) {
callback_ = _callback;
sampleRate_ = sampleRate;
return RunSound();
bool XAudioBackend::Init(HWND window, StreamCallback _callback, int sampleRate) {
callback_ = _callback;
sampleRate_ = sampleRate;
return RunSound();
}

void XAudioBackend::Update() {
}

bool XAudioBackend::WriteDataToBuffer( char* soundData, DWORD soundBytes ) {
XAUDIO2_BUFFER xaudioBuffer;
ZeroMemory( &xaudioBuffer, sizeof( xaudioBuffer ) );
xaudioBuffer.pAudioData = (const BYTE*)soundData;
xaudioBuffer.AudioBytes = soundBytes;

if FAILED( xaudioVoice->SubmitSourceBuffer( &xaudioBuffer, NULL ) )
return false;

return true;
}

void XAudioBackend::PollLoop()
{
ResetEvent( exitEvent_ );
void XAudioBackend::PollLoop() {
ResetEvent(exitEvent_);

while ( !exit )
{
XAUDIO2_VOICE_STATE state;
xaudioVoice->GetState( &state );
while (!exit) {
XAUDIO2_VOICE_STATE state;
xaudioVoice->GetState(&state);

if ( state.BuffersQueued < 1 )
{
int a = 0;
a++;
}
// TODO: Still plenty of tuning to do here.
// 4 seems to work fine.
if (state.BuffersQueued > 4) {
Sleep(1);
continue;
}

unsigned bytesRequired = ( sampleRate_ * 4 ) / 100;
uint32_t bytesRequired = (sampleRate_ * 4) / 100;

while ( bytesRequired )
{
unsigned bytesLeftInBuffer = BUFSIZE - cursor_;
unsigned readCount = std::min( bytesRequired, bytesLeftInBuffer );
uint32_t bytesLeftInBuffer = BUFSIZE - cursor_;
uint32_t readCount = std::min(bytesRequired, bytesLeftInBuffer);

int numBytesRendered = 4 * ( *callback_ )( (short*)&realtimeBuffer_[ cursor_ ], readCount / 4, 16, sampleRate_, 2 );
// realtimeBuffer_ is just used as a ring of scratch space to be submitted, since SubmitSourceBuffer doesn't
// take ownership of the data. It needs to be big enough to fit the max number of buffers we check for
// above, which it is, easily.

WriteDataToBuffer( &realtimeBuffer_[ cursor_ ], numBytesRendered );
cursor_ += numBytesRendered;
if ( cursor_ >= BUFSIZE )
{
cursor_ = 0;
bytesLeftInBuffer = BUFSIZE;
}
int stereoSamplesRendered = (*callback_)((short*)&realtimeBuffer_[cursor_], readCount / 4, 16, sampleRate_, 2);
int numBytesRendered = 2 * sizeof(short) * stereoSamplesRendered;

bytesRequired -= numBytesRendered;
}
XAUDIO2_BUFFER xaudioBuffer{};
xaudioBuffer.pAudioData = (const BYTE*)&realtimeBuffer_[cursor_];
xaudioBuffer.AudioBytes = numBytesRendered;

Sleep( 2 );
}
if FAILED(xaudioVoice->SubmitSourceBuffer(&xaudioBuffer, NULL)) {
WARN_LOG(AUDIO, "XAudioBackend: Failed writing bytes");
}
cursor_ += numBytesRendered;
if (cursor_ >= BUFSIZE) {
cursor_ = 0;
bytesLeftInBuffer = BUFSIZE;
}
}

SetEvent( exitEvent_ );
SetEvent(exitEvent_);
}

WindowsAudioBackend *CreateAudioBackend( AudioBackendType type ) {
return new XAudioBackend();
WindowsAudioBackend *CreateAudioBackend(AudioBackendType type) {
// Only one type available on UWP.
return new XAudioBackend();
}