From 7855c980cb85cb916d47ad2e74ea14a311252e23 Mon Sep 17 00:00:00 2001 From: 6d4m <103454927+6d4m@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:33:01 +0300 Subject: [PATCH] audio: keep blocking AudioOut writes on an even cadence (#822) * audio: keep blocking AudioOut writes on an even cadence The blocking queue check only enforced a minimum cushion, so the guest could queue a burst of buffers and then be blocked for the whole cushion. Measured on PPSA03647, intervals between guest writes alternated between 0 ms and ~44 ms instead of one 5.33 ms buffer period, while the average rate stayed correct at 48000 frames/s. Mixers that run on a timer cannot track that cadence and the music turns slow and warbling. Keep the cushion as-is, but also hold writes to the buffer period, and skip that wait when the stream is running low so it cannot underrun. Intervals become ~5.4 ms with microsecond-level deviation and the menu music plays correctly. * audio: reuse output clock after SDL queue priming --------- Co-authored-by: nmzik --- src/libs/audio.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/libs/audio.cpp b/src/libs/audio.cpp index 14e784944..428e7992c 100644 --- a/src/libs/audio.cpp +++ b/src/libs/audio.cpp @@ -101,6 +101,7 @@ private: uint32_t freq = 0; Format format = Format::Unknown; uint64_t last_output_time = 0; + bool queue_primed = false; int channels_num = 0; int volume[12] = {}; @@ -360,20 +361,34 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) { const auto prepared_size = BytesPerSample(port->format) * output_channels * port->samples_num; + uint32_t min_queued_size = 0; if (blocking) { constexpr uint64_t target_latency_us = 40000; const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0; const auto buffers = buffer_us != 0 ? static_cast((target_latency_us + buffer_us - 1) / buffer_us) : 2u; - const auto min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u); + min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u); const auto wait_start = LibKernel::KernelGetProcessTime(); - while (SDL_GetAudioStreamQueued(port->stream) > static_cast(min_queued_size)) { + auto queued = SDL_GetAudioStreamQueued(port->stream); + if (queued < static_cast(prepared_size)) { + port->queue_primed = false; + } + while (queued > static_cast(min_queued_size)) { if (LibKernel::KernelGetProcessTime() - wait_start > 200000) { SDL_ClearAudioStream(port->stream); + port->queue_primed = false; break; } Common::Thread::SleepMicro(1000); + queued = SDL_GetAudioStreamQueued(port->stream); + } + if (port->queue_primed) { + const auto next_time = port->last_output_time + buffer_us; + const auto now = LibKernel::KernelGetProcessTime(); + if (next_time > now) { + Common::Thread::SleepMicro(next_time - now); + } } } @@ -381,6 +396,10 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) { LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError()); return false; } + if (blocking && !port->queue_primed && + SDL_GetAudioStreamQueued(port->stream) >= static_cast(min_queued_size)) { + port->queue_primed = true; + } return true; }