From 4961252a7ab6d9d27dafe67721f25b2df2fa4a1d Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Sat, 17 Oct 2020 15:04:18 +0100 Subject: [PATCH] Add API extension for cores to monitor frontend audio buffer occupancy --- libretro-common/include/libretro.h | 33 +++++++++++++++++ retroarch.c | 59 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index e03c5e40c8..8b579edcb4 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1335,6 +1335,15 @@ enum retro_mod * should be considered active. */ +#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62 + /* const struct retro_audio_buffer_status_callback * -- + * Lets the core know the occupancy level of the frontend + * audio buffer. Can be used by a core to attempt frame + * skipping in order to avoid buffer under-runs. + * A core may pass NULL to disable buffer status reporting + * in the frontend. + */ + /* VFS functionality */ /* File paths: @@ -2224,6 +2233,30 @@ struct retro_frame_time_callback retro_usec_t reference; }; +/* Notifies a libretro core of the current occupancy + * level of the frontend audio buffer. + * + * - active: 'true' if audio buffer is currently + * in use. Will be 'false' if audio is + * disabled in the frontend + * + * - occupancy: Given as a value in the range [0,100], + * corresponding to the occupancy percentage + * of the audio buffer + * + * - underrun_likely: 'true' if the frontend expects an + * audio buffer underrun during the + * next frame (indicates that a core + * should attempt frame skipping) + * + * It will be called right before retro_run() every frame. */ +typedef void (RETRO_CALLCONV *retro_audio_buffer_status_callback_t)( + bool active, unsigned occupancy, bool underrun_likely); +struct retro_audio_buffer_status_callback +{ + retro_audio_buffer_status_callback_t callback; +}; + /* Pass this to retro_video_refresh_t if rendering to hardware. * Passing NULL to retro_video_refresh_t is still a frame dupe as normal. * */ diff --git a/retroarch.c b/retroarch.c index c2bbe7899f..95430401af 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2156,6 +2156,7 @@ struct rarch_state gfx_display_t dispgfx; /* ptr alignment */ input_keyboard_press_t keyboard_press_cb; /* ptr alignment */ struct retro_frame_time_callback runloop_frame_time; /* ptr alignment */ + struct retro_audio_buffer_status_callback runloop_audio_buffer_status; /* ptr alignment */ retro_input_state_t input_state_callback_original; /* ptr alignment */ struct retro_audio_callback audio_callback; /* ptr alignment */ retro_keyboard_event_t runloop_key_event; /* ptr alignment */ @@ -19891,6 +19892,21 @@ static bool rarch_environment_cb(unsigned cmd, void *data) break; } + case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: + { + const struct retro_audio_buffer_status_callback *info = + (const struct retro_audio_buffer_status_callback*)data; + + RARCH_LOG("[Environ]: SET_AUDIO_BUFFER_STATUS_CALLBACK.\n"); + + if (info) + p_rarch->runloop_audio_buffer_status.callback = info->callback; + else + p_rarch->runloop_audio_buffer_status.callback = NULL; + + break; + } + case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE: { struct retro_rumble_interface *iface = @@ -20880,6 +20896,8 @@ static void uninit_libretro_symbols( retroarch_deinit_core_options(p_rarch); retroarch_system_info_free(p_rarch); retroarch_frame_time_free(p_rarch); + memset(&p_rarch->runloop_audio_buffer_status, 0, + sizeof(struct retro_audio_buffer_status_callback)); p_rarch->camera_driver_active = false; p_rarch->location_driver_active = false; @@ -38049,6 +38067,8 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data) #endif p_rarch->runloop_autosave = false; retroarch_frame_time_free(p_rarch); + memset(&p_rarch->runloop_audio_buffer_status, 0, + sizeof(struct retro_audio_buffer_status_callback)); break; case RARCH_CTL_IS_IDLE: return p_rarch->runloop_idle; @@ -40079,6 +40099,45 @@ int runloop_iterate(void) p_rarch->runloop_frame_time.callback(delta); } + /* Update audio buffer occupancy if buffer status + * callback is in use by the core */ + if (p_rarch->runloop_audio_buffer_status.callback) + { + bool audio_buf_active = false; + unsigned audio_buf_occupancy = 0; + bool audio_buf_underrun = false; + + if (!(p_rarch->runloop_paused || + !p_rarch->audio_driver_active || + !p_rarch->audio_driver_output_samples_buf) && + p_rarch->current_audio->write_avail && + p_rarch->audio_driver_context_audio_data && + p_rarch->audio_driver_buffer_size) + { + size_t audio_buf_avail = p_rarch->current_audio->write_avail( + p_rarch->audio_driver_context_audio_data); + + audio_buf_avail = (audio_buf_avail > p_rarch->audio_driver_buffer_size) ? + p_rarch->audio_driver_buffer_size : audio_buf_avail; + + audio_buf_occupancy = (unsigned)(100 - (audio_buf_avail * 100) / + p_rarch->audio_driver_buffer_size); + + /* Elsewhere, we standardise on a 'low water mark' + * of 25% of the total audio buffer size - use + * the same metric here (can be made more sophisticated + * if required - i.e. determine buffer occupancy in + * terms of usec, and weigh this against the expected + * frame time) */ + audio_buf_underrun = audio_buf_occupancy < 25; + + audio_buf_active = true; + } + + p_rarch->runloop_audio_buffer_status.callback( + audio_buf_active, audio_buf_occupancy, audio_buf_underrun); + } + switch ((enum runloop_state)runloop_check_state(p_rarch, settings, current_time)) {