[Scummvm-git-logs] scummvm master -> 606f9dace5ffe9ae133cf9b9a1b40b9a50a9fef9

spleen1981 noreply at scummvm.org
Sun Jul 26 23:53:45 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
606f9dace5 LIBRETRO: Pace audio generation against wall time, not frame count (#7742)


Commit: 606f9dace5ffe9ae133cf9b9a1b40b9a50a9fef9
    https://github.com/scummvm/scummvm/commit/606f9dace5ffe9ae133cf9b9a1b40b9a50a9fef9
Author: AlbertoVelazquez (50219414+AlbertoVelazquez at users.noreply.github.com)
Date: 2026-07-27T01:53:41+02:00

Commit Message:
LIBRETRO: Pace audio generation against wall time, not frame count (#7742)

audio_run() emits a fixed sample_rate/frame_rate samples per call. As
it runs once per retro_run(), the output rate follows the achieved
call rate rather than real time: a frontend running below frame_rate
underproduces and the audio buffer underruns (crackle), while one
running above overproduces. Video is already frame-locked; audio was
not.

Derive the per-call count from the wall-clock time elapsed since the
previous call (cpu_features_get_time_usec()), so average production
stays at sample_rate regardless of the retro_run() cadence. Bound the
batch to the mix buffer size so a long stall (content load,
save-state) cannot request a multi-second batch. The fractional-sample
remainder is preserved, and a vsync-locked frontend gets the same
per-frame count as before.

Changed paths:
    backends/platform/libretro/src/libretro-core.cpp


diff --git a/backends/platform/libretro/src/libretro-core.cpp b/backends/platform/libretro/src/libretro-core.cpp
index 57ceb5ba30f..9627e4870e7 100644
--- a/backends/platform/libretro/src/libretro-core.cpp
+++ b/backends/platform/libretro/src/libretro-core.cpp
@@ -38,6 +38,8 @@
 #include <libgen.h>
 #endif
 
+#include <features/features_cpu.h> // cpu_features_get_time_usec()
+
 /**
  * Include base/internal_version.h to allow access to SCUMMVM_VERSION.
  * @see retro_get_system_info()
@@ -97,6 +99,7 @@ static float frame_rate = 0;
 static uint16 sample_rate = 0;
 static float audio_samples_per_frame   = 0.0f; // length in samples per frame
 static float audio_samples_accumulator = 0.0f;
+static retro_time_t audio_last_time_usec = 0; // timestamp of the previous audio_run()
 
 static int16 *audio_sample_buffer = NULL; // pointer to output buffer
 
@@ -191,6 +194,7 @@ static void log_scummvm_exit_code(void) {
 
 static void audio_buffer_init(uint16 sample_rate, uint16 frame_rate) {
 	audio_samples_accumulator = 0.0f;
+	audio_last_time_usec      = cpu_features_get_time_usec();
 	audio_samples_per_frame   = (float)sample_rate / (float)frame_rate;
 	uint32 audio_sample_buffer_size  = ((uint32)retro_setting_get_audio_samples_buffer_size()) * 2 * sizeof(int16);
 	audio_sample_buffer       = audio_sample_buffer ? (int16 *)realloc(audio_sample_buffer, audio_sample_buffer_size) : (int16 *)malloc(audio_sample_buffer_size);
@@ -205,19 +209,33 @@ static void audio_run(void) {
 	int16 *audio_buffer_ptr;
 	uint32 samples_to_read;
 	uint32 samples_produced;
+	uint16 samples_buffer_size = retro_setting_get_audio_samples_buffer_size();
+
+	/* Pace audio against elapsed wall time instead of a fixed sample_rate/frame_rate
+	 * batch, so output stays at sample_rate even when the frontend runs retro_run()
+	 * above or below frame_rate (a slow frontend would otherwise underrun the audio
+	 * buffer). Video stays frame-locked. */
+	retro_time_t now_usec = cpu_features_get_time_usec();
+	float samples_target = (float)sample_rate * (float)(now_usec - audio_last_time_usec) / 1000000.0f;
+	audio_last_time_usec = now_usec;
 
-	/* Audio_samples_per_frame is decimal;
-	 * get integer component */
-	samples_to_read = (uint32)audio_samples_per_frame;
+	/* Samples_target is decimal; get integer component */
+	samples_to_read = (uint32)samples_target;
 
 	/* Account for fractional component */
-	audio_samples_accumulator += audio_samples_per_frame - (float)samples_to_read;
+	audio_samples_accumulator += samples_target - (float)samples_to_read;
 
 	if (audio_samples_accumulator >= 1.0f) {
 		samples_to_read++;
 		audio_samples_accumulator -= 1.0f;
 	}
 
+	/* Bound the batch to the mix buffer: after a long stall (content load,
+	 * save-state) the elapsed interval would otherwise request a multi-second
+	 * batch and overrun audio_sample_buffer. */
+	if (samples_to_read > samples_buffer_size)
+		samples_to_read = samples_buffer_size;
+
 	samples_produced = ((Audio::MixerImpl *)g_system->getMixer())->mixCallback((byte *) audio_sample_buffer, samples_to_read * 2 * sizeof(int16));
 
 	/* Workaround as currently there's no way to detect silence-only buffers from the mixer */




More information about the Scummvm-git-logs mailing list