From 3263ab11c268dd1802959ccb6ac5a82ef845fa48 Mon Sep 17 00:00:00 2001 From: Sparronator <86388887+Sparronator9999@users.noreply.github.com> Date: Wed, 9 Jul 2025 03:52:36 +1000 Subject: [PATCH] Make SPU audio single-buffered (audio latency improvement) (#2286) * SPU audio latency improvements Basically reverts audio buffer handling to what it was before commit 05b94ef, but with the mutexes kept for thread safety (which the referenced commit was trying to do). The SPU audio buffer should still be thread-safe in theory... right? * Audio output improvements This commit changes the audio output buffer to be configured by a variable, and fixes the case where the sound driver may change the buffer size after calling SDL_OpenAudioDevice (e.g. if the buffer size is set too low for the driver to handle). --- src/NDS.cpp | 1 - src/SPU.cpp | 90 +++++++++--------------- src/SPU.h | 12 ++-- src/frontend/qt_sdl/EmuInstance.h | 1 + src/frontend/qt_sdl/EmuInstanceAudio.cpp | 15 ++-- 5 files changed, 48 insertions(+), 71 deletions(-) diff --git a/src/NDS.cpp b/src/NDS.cpp index 340cd020..5fd65bea 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -1045,7 +1045,6 @@ u32 NDS::RunFrame() ARM7Timestamp-SysTimestamp, GPU.GPU3D.Timestamp-SysTimestamp); #endif - SPU.TransferOutput(); break; } diff --git a/src/SPU.cpp b/src/SPU.cpp index 0f0c286d..b3688793 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -207,11 +207,10 @@ SPU::SPU(melonDS::NDS& nds, AudioBitDepth bitdepth, AudioInterpolation interpola ApplyBias = true; Degrade10Bit = false; - memset(OutputFrontBuffer, 0, 2*OutputBufferSize*2); + memset(OutputBuffer, 0, 2*OutputBufferSize*2); - OutputBackbufferWritePosition = 0; - OutputFrontBufferReadPosition = 0; - OutputFrontBufferWritePosition = 0; + OutputBufferReadPos = 0; + OutputBufferWritePos = 0; } SPU::~SPU() @@ -242,11 +241,10 @@ void SPU::Reset() void SPU::Stop() { Platform::Mutex_Lock(AudioLock); - memset(OutputFrontBuffer, 0, 2*OutputBufferSize*2); + memset(OutputBuffer, 0, 2*OutputBufferSize*2); - OutputBackbufferWritePosition = 0; - OutputFrontBufferReadPosition = 0; - OutputFrontBufferWritePosition = 0; + OutputBufferReadPos = 0; + OutputBufferWritePos = 0; Platform::Mutex_Unlock(AudioLock); } @@ -942,67 +940,49 @@ void SPU::Mix(u32 dummy) rightoutput &= 0xFFFFFFC0; } - // OutputBufferFrame can never get full because it's - // transfered to OutputBuffer at the end of the frame - // FIXME: apparently this does happen!!! - if (OutputBackbufferWritePosition * 2 < OutputBufferSize - 1) + Platform::Mutex_Lock(AudioLock); + OutputBuffer[OutputBufferWritePos++] = leftoutput >> 1; + OutputBuffer[OutputBufferWritePos++] = rightoutput >> 1; + + OutputBufferWritePos &= ((2*OutputBufferSize)-1); + + if (OutputBufferWritePos == OutputBufferReadPos) { - OutputBackbuffer[OutputBackbufferWritePosition ] = leftoutput >> 1; - OutputBackbuffer[OutputBackbufferWritePosition + 1] = rightoutput >> 1; - OutputBackbufferWritePosition += 2; + // advance the read position too, to avoid losing the entire FIFO + OutputBufferReadPos += 2; + OutputBufferReadPos &= ((2*OutputBufferSize)-1); } + Platform::Mutex_Unlock(AudioLock); NDS.ScheduleEvent(Event_SPU, true, 1024, 0, 0); } -void SPU::TransferOutput() -{ - Platform::Mutex_Lock(AudioLock); - for (u32 i = 0; i < OutputBackbufferWritePosition; i += 2) - { - OutputFrontBuffer[OutputFrontBufferWritePosition ] = OutputBackbuffer[i ]; - OutputFrontBuffer[OutputFrontBufferWritePosition + 1] = OutputBackbuffer[i + 1]; - - OutputFrontBufferWritePosition += 2; - OutputFrontBufferWritePosition &= OutputBufferSize*2-1; - if (OutputFrontBufferWritePosition == OutputFrontBufferReadPosition) - { - // advance the read position too, to avoid losing the entire FIFO - OutputFrontBufferReadPosition += 2; - OutputFrontBufferReadPosition &= OutputBufferSize*2-1; - } - } - OutputBackbufferWritePosition = 0; - Platform::Mutex_Unlock(AudioLock);; -} - void SPU::TrimOutput() { Platform::Mutex_Lock(AudioLock); const int halflimit = (OutputBufferSize / 2); - int readpos = OutputFrontBufferWritePosition - (halflimit*2); + int readpos = OutputBufferWritePos - (halflimit*2); if (readpos < 0) readpos += (OutputBufferSize*2); - OutputFrontBufferReadPosition = readpos; + OutputBufferReadPos = readpos; Platform::Mutex_Unlock(AudioLock); } void SPU::DrainOutput() { Platform::Mutex_Lock(AudioLock); - OutputFrontBufferWritePosition = 0; - OutputFrontBufferReadPosition = 0; + OutputBufferReadPos = 0; + OutputBufferWritePos = 0; Platform::Mutex_Unlock(AudioLock); } void SPU::InitOutput() { Platform::Mutex_Lock(AudioLock); - memset(OutputBackbuffer, 0, 2*OutputBufferSize*2); - memset(OutputFrontBuffer, 0, 2*OutputBufferSize*2); - OutputFrontBufferReadPosition = 0; - OutputFrontBufferWritePosition = 0; + memset(OutputBuffer, 0, 2*OutputBufferSize*2); + OutputBufferReadPos = 0; + OutputBufferWritePos = 0; Platform::Mutex_Unlock(AudioLock); } @@ -1011,10 +991,10 @@ int SPU::GetOutputSize() const Platform::Mutex_Lock(AudioLock); int ret; - if (OutputFrontBufferWritePosition >= OutputFrontBufferReadPosition) - ret = OutputFrontBufferWritePosition - OutputFrontBufferReadPosition; + if (OutputBufferWritePos >= OutputBufferReadPos) + ret = OutputBufferWritePos - OutputBufferReadPos; else - ret = (OutputBufferSize*2) - OutputFrontBufferReadPosition + OutputFrontBufferWritePosition; + ret = (OutputBufferSize*2) - OutputBufferReadPos + OutputBufferWritePos; ret >>= 1; @@ -1043,10 +1023,10 @@ void SPU::Sync(bool wait) { Platform::Mutex_Lock(AudioLock); - int readpos = OutputFrontBufferWritePosition - (halflimit*2); + int readpos = OutputBufferWritePos - (halflimit*2); if (readpos < 0) readpos += (OutputBufferSize*2); - OutputFrontBufferReadPosition = readpos; + OutputBufferReadPos = readpos; Platform::Mutex_Unlock(AudioLock); } @@ -1055,7 +1035,7 @@ void SPU::Sync(bool wait) int SPU::ReadOutput(s16* data, int samples) { Platform::Mutex_Lock(AudioLock); - if (OutputFrontBufferReadPosition == OutputFrontBufferWritePosition) + if (OutputBufferReadPos == OutputBufferWritePos) { Platform::Mutex_Unlock(AudioLock); return 0; @@ -1063,13 +1043,11 @@ int SPU::ReadOutput(s16* data, int samples) for (int i = 0; i < samples; i++) { - *data++ = OutputFrontBuffer[OutputFrontBufferReadPosition]; - *data++ = OutputFrontBuffer[OutputFrontBufferReadPosition + 1]; + *data++ = OutputBuffer[OutputBufferReadPos++]; + *data++ = OutputBuffer[OutputBufferReadPos++]; + OutputBufferReadPos &= ((2*OutputBufferSize)-1); - OutputFrontBufferReadPosition += 2; - OutputFrontBufferReadPosition &= ((2*OutputBufferSize)-1); - - if (OutputFrontBufferWritePosition == OutputFrontBufferReadPosition) + if (OutputBufferWritePos == OutputBufferReadPos) { Platform::Mutex_Unlock(AudioLock); return i+1; diff --git a/src/SPU.h b/src/SPU.h index fa93273d..85921a5b 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -241,7 +241,6 @@ public: int GetOutputSize() const; void Sync(bool wait); int ReadOutput(s16* data, int samples); - void TransferOutput(); u8 Read8(u32 addr); u16 Read16(u32 addr); @@ -251,14 +250,11 @@ public: void Write32(u32 addr, u32 val); private: - static const u32 OutputBufferSize = 2*2048; + static const u32 OutputBufferSize = 2*1024; // TODO: configurable audio buffer sizes? melonDS::NDS& NDS; - s16 OutputBackbuffer[2 * OutputBufferSize] {}; - u32 OutputBackbufferWritePosition = 0; - - s16 OutputFrontBuffer[2 * OutputBufferSize] {}; - u32 OutputFrontBufferWritePosition = 0; - u32 OutputFrontBufferReadPosition = 0; + s16 OutputBuffer[2 * OutputBufferSize] {}; + u32 OutputBufferWritePos = 0; + u32 OutputBufferReadPos = 0; Platform::Mutex* AudioLock; diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 295e9bf6..b77ee917 100755 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -305,6 +305,7 @@ private: SDL_AudioDeviceID audioDevice; int audioFreq; + int audioBufSize; float audioSampleFrac; bool audioMuted; SDL_cond* audioSyncCond; diff --git a/src/frontend/qt_sdl/EmuInstanceAudio.cpp b/src/frontend/qt_sdl/EmuInstanceAudio.cpp index 2c925801..c8bb8913 100644 --- a/src/frontend/qt_sdl/EmuInstanceAudio.cpp +++ b/src/frontend/qt_sdl/EmuInstanceAudio.cpp @@ -73,8 +73,8 @@ void EmuInstance::audioCallback(void* data, Uint8* stream, int len) // resample incoming audio to match the output sample rate int len_in = inst->audioGetNumSamplesOut(len); - if (len_in > 1024) len_in = 1024; - s16 buf_in[1024*2]; + if (len_in > inst->audioBufSize) len_in = inst->audioBufSize; + s16 buf_in[inst->audioBufSize*2]; int num_in; SDL_LockMutex(inst->audioSyncLock); @@ -416,16 +416,17 @@ void EmuInstance::audioInit() audioSyncCond = SDL_CreateCond(); audioSyncLock = SDL_CreateMutex(); - audioFreq = 48000; // TODO: make configurable? + audioFreq = 48000; // TODO: make both of these configurable? + audioBufSize = 1024; SDL_AudioSpec whatIwant, whatIget; memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); whatIwant.freq = audioFreq; whatIwant.format = AUDIO_S16LSB; whatIwant.channels = 2; - whatIwant.samples = 1024; + whatIwant.samples = audioBufSize; whatIwant.callback = audioCallback; whatIwant.userdata = this; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE); if (!audioDevice) { Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); @@ -433,7 +434,9 @@ void EmuInstance::audioInit() else { audioFreq = whatIget.freq; + audioBufSize = whatIget.samples; Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); + Platform::Log(Platform::LogLevel::Info, "Audio output buffer size: %d samples\n", audioBufSize); SDL_PauseAudioDevice(audioDevice, 1); } @@ -479,7 +482,7 @@ void EmuInstance::audioSync() if (audioDevice) { SDL_LockMutex(audioSyncLock); - while (nds->SPU.GetOutputSize() > 1024) + while (nds->SPU.GetOutputSize() > audioBufSize) { int ret = SDL_CondWaitTimeout(audioSyncCond, audioSyncLock, 500); if (ret == SDL_MUTEX_TIMEDOUT) break;