Revert self-pumping audio thread (#7284)

* Revert "Fix audio regression playing audio too slowly (#6894)"

This reverts commit db41e1b5f8.

* Revert "fix(audio): Fix audio stutter when gfx hitches (#6704)"

This reverts commit 4415537deb.
This commit is contained in:
Chris
2026-09-30 22:39:23 -04:00
committed by GitHub
parent ad7d0a66d0
commit f4e698f4cc
2 changed files with 34 additions and 89 deletions
+3 -3
View File
@@ -4,8 +4,8 @@
static struct {
std::thread thread;
std::condition_variable cv_to_thread;
std::condition_variable cv_to_thread, cv_from_thread;
std::mutex mutex;
std::atomic_bool running;
std::atomic_bool processing;
bool running;
bool processing;
} audio;
+31 -86
View File
@@ -849,11 +849,7 @@ void OTRGlobals::Initialize() {
CVarGetInteger(CVAR_SETTING("AutoCaptureMouse"), 1));
context->GetWindow()->SetForceCursorVisibility(CVarGetInteger(CVAR_SETTING("CursorVisibility"), 0));
context->InitAudio({ .SampleRate = 32000,
.SampleLength = 1024,
// 4096 frames at 32 kHz (~128 ms) gives enough reservoir for frame
// jitter and slow-frame spikes without perceptible audio latency.
.DesiredBuffered = 4096 });
context->InitAudio({ .SampleRate = 32000, .SampleLength = 1024, .DesiredBuffered = 1680 });
// The menu is set up before audio is initialized, so its list of available audio backends has to be
// populated here rather than in Menu::InitElement (where the window backends are handled).
@@ -1042,100 +1038,42 @@ extern "C" int AudioPlayer_GetDesiredBuffered(void);
std::unordered_map<std::string, ExtensionEntry> ExtensionCache;
void OTRAudio_Thread() {
#define SAMPLES_HIGH 560
#define SAMPLES_MID 544
#define SAMPLES_LOW 528
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1)
#define NUM_AUDIO_CHANNELS 2
// The sequencer advances a fixed slice of musical time per engine update
// (tempoInternalToExternal in audio_heap.c assumes 60 updates/sec), so with
// production paced by backend buffer fill the sample count must average
// exactly 32000/60 = 533.33 per update or tempo drifts.
// Two thirds 528 one third 544 gives 533.33.
int32_t sample_debt_thirds = 0;
// Single producer routine used by both wake-driven and pre-buffer loops.
// Picks per-iteration sample count itself, then produces and plays it.
auto produce_next_batch = [&]() {
u32 num_audio_samples = sample_debt_thirds > 0 ? SAMPLES_MID : SAMPLES_LOW;
sample_debt_thirds += (1600 - 3 * (int32_t)num_audio_samples) * AUDIO_FRAMES_PER_UPDATE;
const u32 total_frames = num_audio_samples * AUDIO_FRAMES_PER_UPDATE;
const u32 total_samples = total_frames * NUM_AUDIO_CHANNELS;
// 3 is the maximum authentic frame divisor.
static thread_local s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS),
num_audio_samples);
}
AudioPlayer_Play(reinterpret_cast<u8*>(audio_buffer), total_samples * sizeof(int16_t));
};
// Self-pump cadence. The gfx thread wakes us once per rendered frame
// (Graph_ProcessGfxCommands sets audio.processing), but a single long
// frame leave us asleep while the backend's queue drains to silence.
// So we also wake on a short timeout, independent of the gfx frame rate.
// Doing so is in fact closer to the console, where the audio task ran
// off the scheduler rather than gated on rendering..
constexpr auto kSelfPumpInterval = std::chrono::milliseconds(5);
// The self-pump timeout must wait that the game has reached its render
// loop, to avoid accessing uninitialized variables.
bool primed = false;
while (audio.running) {
{
std::unique_lock<std::mutex> Lock(audio.mutex);
if (!primed) {
// Pre-init: block until the gfx thread drives the first buffer
// (engine guaranteed ready by then), exactly as before.
while (!audio.processing && audio.running) {
audio.cv_to_thread.wait(Lock);
}
primed = true;
} else if (!audio.processing && audio.running) {
// Primed: wait for the next gfx wake, but no longer than
// kSelfPumpInterval so a stalled gfx thread can't starve the
// backend queue. A pending wake falls straight through.
audio.cv_to_thread.wait_for(Lock, kSelfPumpInterval);
while (!audio.processing && audio.running) {
audio.cv_to_thread.wait(Lock);
}
if (!audio.running) {
break;
}
}
std::unique_lock<std::mutex> Lock(audio.mutex);
// AudioMgr_ThreadEntry(&gAudioMgr);
// 528 and 544 relate to 60 fps at 32 kHz 32000/60 = 533.333..
// in an ideal world, one third of the calls should use num_samples=544 and two thirds num_samples=528
#define SAMPLES_HIGH 560
#define SAMPLES_LOW 528
{
std::unique_lock<std::mutex> Lock(audio.mutex);
#define AUDIO_FRAMES_PER_UPDATE (R_UPDATE_RATE > 0 ? R_UPDATE_RATE : 1)
#define NUM_AUDIO_CHANNELS 2
// Producer guard (banteg/Shipwright#6594): skip advancing the audio
// engine if the backend ring cannot accept the largest next burst.
// Generating PCM that DoPlay() would refuse creates a discontinuity
// audible as a click. The pre-buffer loop below will catch up once
// the backend drains enough.
if (AudioPlayer_Buffered() + SAMPLES_MID * AUDIO_FRAMES_PER_UPDATE > AudioPlayer_GetDesiredBuffered()) {
audio.processing = false;
} else {
produce_next_batch();
audio.processing = false;
}
int samples_left = AudioPlayer_Buffered();
u32 num_audio_samples = samples_left < AudioPlayer_GetDesiredBuffered() ? SAMPLES_HIGH : SAMPLES_LOW;
// 3 is the maximum authentic frame divisor.
s16 audio_buffer[SAMPLES_HIGH * NUM_AUDIO_CHANNELS * 3];
for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) {
AudioMgr_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * NUM_AUDIO_CHANNELS),
num_audio_samples);
}
// Pre-buffer: fill the reservoir while the backend can accept more,
// without waiting for the next frame signal. This absorbs load spikes.
// Safe for BGM — the N64 sequencer advances independently of gameplay.
// The producer guard (same as above) prevents advancing the audio engine
// when the backend ring is already at capacity.
while (audio.running && AudioPlayer_Buffered() < AudioPlayer_GetDesiredBuffered()) {
if (AudioPlayer_Buffered() + SAMPLES_MID * AUDIO_FRAMES_PER_UPDATE > AudioPlayer_GetDesiredBuffered()) {
break;
}
produce_next_batch();
}
AudioPlayer_Play((u8*)audio_buffer,
num_audio_samples * (sizeof(int16_t) * NUM_AUDIO_CHANNELS * AUDIO_FRAMES_PER_UPDATE));
audio.processing = false;
audio.cv_from_thread.notify_one();
}
}
@@ -1906,6 +1844,13 @@ extern "C" void Graph_ProcessGfxCommands(Gfx* commands) {
last_fps = fps;
last_update_rate = R_UPDATE_RATE;
{
std::unique_lock<std::mutex> Lock(audio.mutex);
while (audio.processing) {
audio.cv_from_thread.wait(Lock);
}
}
bool curAltAssets = CVarGetInteger(CVAR_SETTING("AltAssets"), 1);
if (prevAltAssets != curAltAssets) {
prevAltAssets = curAltAssets;