Audio: Size the granular queue from the classic resampler's target

The queue cap was four output callbacks plus a frame of audio. Bluetooth and
other outputs without a fast path call back with large chunks, so that came
to about 100 ms there. Now it's the classic target (which also honors Extra
audio buffering), raised for slow games' per-frame bursts and to keep half
the queue above one callback's read, plus a granule for the overlap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-10 10:29:23 +01:00
1 parent 997cc92072
commit 5f349fd603
3 files changed
+28 -17

No files matched your search

+12 -7
View File
@@ -14,6 +14,7 @@
#include "Common/Swap.h"
#include "Core/Config.h"
#include "Core/HW/Display.h"
#include "Core/HW/StereoResampler.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Core/Util/AudioFormat.h" // for clamp_u16
@@ -102,13 +103,17 @@ void GranularMixer::Mix(s16 *samples, u32 num_samples, int outSampleRate, float
const float fade_in_mul = -std::expm1(-1.0 / (out_sample_rate * FADE_IN_RC));
const float fade_out_mul = -std::expm1(-1.0 / (out_sample_rate * FADE_OUT_RC));
// Calculate the ideal length of the granule queue.
// NOTE: We must have enough room here for 20fps games, generating all their audio
// in a burst each frame (since we can't force real clock sync). That means 16*3 = 48 or rather 50ms.
// However, in case of faster framerates, we should apply some pressure to reduce this. And if real clock sync
// is on, we should also be able to get away with a shorter buffer here.
// const u32 buffer_size_ms = frameTimeEstimate_ * 44100.0f;
const u32 buffer_size_samples = smoothedReadSize_ * 4 + std::llround(frameTimeEstimate_ * inSampleRate);
// Calculate the length of the granule queue. Dequeue() cuts the queue back to half when it
// goes over, so this is a cap rather than a fill level.
// We aim for the same latency as StereoResampler, but must also have room for slow (like 20fps)
// games, which generate all their audio in a burst each frame (since we can't force real
// clock sync), and keep half the queue above what one output callback reads, or each callback
// runs dry. Bluetooth and other non-fast outputs read in large chunks.
// One more granule on top, for the two half-overlapping granules being played.
const int targetSamples = StereoResampler::TargetBufferSize();
const int frameSamples = (int)std::llround(frameTimeEstimate_ * inSampleRate);
const int readSamples = (int)(smoothedReadSize_ * inSampleRate / out_sample_rate);
const u32 buffer_size_samples = std::max({ targetSamples, frameSamples + targetSamples / 2, readSamples * 2 }) + GRANULE_SIZE;
queuedSamplesTarget_ = buffer_size_samples;
// Limit the possible queue sizes to any number between 4 and 64.
+13 -10
View File
@@ -74,20 +74,23 @@ StereoResampler::~StereoResampler() {
buffer_ = nullptr;
}
void StereoResampler::UpdateBufferSize() {
int StereoResampler::TargetBufferSize() {
if (g_Config.bExtraAudioBuffering) {
return TARGET_BUFSIZE_EXTRA;
}
int systemBufsize = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER);
if (systemBufsize > 0 && TARGET_BUFSIZE_DEFAULT < systemBufsize + TARGET_BUFSIZE_MARGIN) {
return std::min(4096, systemBufsize + TARGET_BUFSIZE_MARGIN);
}
return TARGET_BUFSIZE_DEFAULT;
}
void StereoResampler::UpdateBufferSize() {
targetBufsize_ = TargetBufferSize();
if (g_Config.bExtraAudioBuffering || targetBufsize_ * 2 > MAX_BUFSIZE_DEFAULT) {
maxBufsize_ = MAX_BUFSIZE_EXTRA;
targetBufsize_ = TARGET_BUFSIZE_EXTRA;
} else {
maxBufsize_ = MAX_BUFSIZE_DEFAULT;
targetBufsize_ = TARGET_BUFSIZE_DEFAULT;
int systemBufsize = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER);
if (systemBufsize > 0 && targetBufsize_ < systemBufsize + TARGET_BUFSIZE_MARGIN) {
targetBufsize_ = std::min(4096, systemBufsize + TARGET_BUFSIZE_MARGIN);
if (targetBufsize_ * 2 > MAX_BUFSIZE_DEFAULT)
maxBufsize_ = MAX_BUFSIZE_EXTRA;
}
}
}
+3
View File
@@ -44,6 +44,9 @@ public:
void GetAudioDebugStats(char *buf, size_t bufSize);
void ResetStatCounters();
// The buffer fill to aim for, in 44.1 kHz stereo frames. GranularMixer sizes its queue from this too.
static int TargetBufferSize();
private:
void UpdateBufferSize();