diff --git a/shell/libretro/audiostream.cpp b/shell/libretro/audiostream.cpp index ea20c170b..1aab5904b 100644 --- a/shell/libretro/audiostream.cpp +++ b/shell/libretro/audiostream.cpp @@ -21,24 +21,124 @@ #include -#define SAMPLE_COUNT 512 -struct SoundFrame { s16 l; s16 r; }; +#include +#include + +#define AUDIO_BUFFER_SIZE_DEFAULT (1 << 11) +#define AUDIO_BUFFER_SIZE_MAX (1 << 20) /* 1 MiB */ extern retro_audio_sample_batch_t audio_batch_cb; +static std::mutex audio_buffer_mutex; +static std::vector audio_buffer; +static size_t audio_buffer_idx; +static size_t audio_batch_frames_max; + +static int16_t *audio_out_buffer = nullptr; +static size_t audio_out_buffer_size; + +void retro_audio_init(void) +{ + const std::lock_guard lock(audio_buffer_mutex); + + audio_buffer.resize(AUDIO_BUFFER_SIZE_DEFAULT); + audio_buffer_idx = 0; + audio_batch_frames_max = std::numeric_limits::max(); + + audio_out_buffer_size = AUDIO_BUFFER_SIZE_DEFAULT; + audio_out_buffer = (int16_t*)malloc(audio_out_buffer_size * sizeof(int16_t)); +} + +void retro_audio_deinit(void) +{ + const std::lock_guard lock(audio_buffer_mutex); + + audio_buffer.clear(); + audio_buffer_idx = 0; + + if (audio_out_buffer != nullptr) + free(audio_out_buffer); + + audio_out_buffer = nullptr; + audio_out_buffer_size = 0; +} + +void retro_audio_flush_buffer(void) +{ + const std::lock_guard lock(audio_buffer_mutex); + audio_buffer_idx = 0; +} + +void retro_audio_upload(void) +{ + audio_buffer_mutex.lock(); + + if (audio_out_buffer_size < audio_buffer_idx) + { + int16_t *tmp = (int16_t *)realloc(audio_out_buffer, + audio_buffer_idx * sizeof(int16_t)); + + if (!tmp) + { + audio_buffer_idx = 0; + audio_buffer_mutex.unlock(); + return; + } + + audio_out_buffer_size = audio_buffer_idx; + audio_out_buffer = tmp; + } + + for (size_t i = 0; i < audio_buffer_idx; i++) + audio_out_buffer[i] = audio_buffer[i]; + + size_t num_frames = audio_buffer_idx >> 1; + audio_buffer_idx = 0; + + audio_buffer_mutex.unlock(); + + int16_t *audio_out_buffer_ptr = audio_out_buffer; + while (num_frames > 0) + { + size_t frames_to_write = (num_frames > audio_batch_frames_max) ? + audio_batch_frames_max : num_frames; + size_t frames_written = audio_batch_cb(audio_out_buffer_ptr, + frames_to_write); + + if ((frames_written < frames_to_write) && + (frames_written > 0)) + audio_batch_frames_max = frames_written; + + num_frames -= frames_to_write; + audio_out_buffer_ptr += frames_to_write << 1; + } +} + void WriteSample(s16 r, s16 l) { - static SoundFrame Buffer[SAMPLE_COUNT]; - static u32 writePtr; // next sample index - Buffer[writePtr].r = r; - Buffer[writePtr].l = l; + const std::lock_guard lock(audio_buffer_mutex); - if (++writePtr == SAMPLE_COUNT) - { - if (dc_is_running() && (!config::ThreadedRendering || config::LimitFPS)) - audio_batch_cb((const int16_t*)Buffer, SAMPLE_COUNT); - writePtr = 0; - } + if (audio_buffer.size() < audio_buffer_idx + 2) + { + if (audio_buffer_idx + 2 > AUDIO_BUFFER_SIZE_MAX) + { + audio_buffer_idx = 0; + return; + } + + try + { + audio_buffer.resize(audio_buffer_idx + 2 + AUDIO_BUFFER_SIZE_DEFAULT); + } + catch (std::bad_alloc &) + { + audio_buffer_idx = 0; + return; + } + } + + audio_buffer[audio_buffer_idx++] = l; + audio_buffer[audio_buffer_idx++] = r; } void InitAudio() diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index 6a4ae0d97..e13dc60e2 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -73,13 +73,6 @@ constexpr char slash = path_default_slash_c(); * background or not. */ -#define RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB (3 | RETRO_ENVIRONMENT_RETROARCH_START_BLOCK) - /* retro_environment_t * -- - * Provides the callback to the frontend method which will cancel - * all currently waiting threads. Used when coordination is needed - * between the core and the frontend to gracefully stop all threads. - */ - #define RETRO_ENVIRONMENT_POLL_TYPE_OVERRIDE (4 | RETRO_ENVIRONMENT_RETROARCH_START_BLOCK) /* unsigned * -- * Tells the frontend to override the poll type behavior. @@ -98,6 +91,11 @@ constexpr char slash = path_default_slash_c(); #include "libretro_core_options.h" #include "vmu_xhair.h" +extern void retro_audio_init(void); +extern void retro_audio_deinit(void); +extern void retro_audio_flush_buffer(void); +extern void retro_audio_upload(void); + std::string arcadeFlashPath; static bool boot_to_bios; @@ -145,7 +143,6 @@ static retro_input_poll_t poll_cb; static retro_input_state_t input_cb; retro_audio_sample_batch_t audio_batch_cb; static retro_environment_t environ_cb; -static retro_environment_t frontend_clear_thread_waits_cb; static retro_rumble_interface rumble; @@ -264,8 +261,6 @@ void retro_init() unsigned color_mode = RETRO_PIXEL_FORMAT_XRGB8888; environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &color_mode); - environ_cb(RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB, &frontend_clear_thread_waits_cb); - init_kb_map(); struct retro_keyboard_callback kb_callback = { &retro_keyboard_event }; environ_cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &kb_callback); @@ -274,6 +269,7 @@ void retro_init() libretro_supports_bitmasks = true; init_disk_control_interface(); + retro_audio_init(); if (!_vmem_reserve()) ERROR_LOG(VMEM, "Cannot reserve memory space"); @@ -294,6 +290,8 @@ void retro_deinit() os_UninstallFaultHandler(); libretro_supports_bitmasks = false; LogManager::Shutdown(); + + retro_audio_deinit(); } static void set_variable_visibility() @@ -891,6 +889,11 @@ void retro_run() if (config::RendererType.isOpenGL()) glsm_ctl(GLSM_CTL_STATE_UNBIND, nullptr); + if (!config::ThreadedRendering || config::LimitFPS) + retro_audio_upload(); + else + retro_audio_flush_buffer(); + video_cb(is_dupe ? 0 : RETRO_HW_FRAME_BUFFER_VALID, framebufferWidth, framebufferHeight, 0); if (!config::ThreadedRendering) @@ -931,6 +934,7 @@ void retro_reset() setGameGeometry(geometry); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geometry); blankVmus(); + retro_audio_flush_buffer(); if (config::ThreadedRendering) dc_resume(); @@ -1722,11 +1726,7 @@ bool retro_load_game_special(unsigned game_type, const struct retro_game_info *i void retro_unload_game() { INFO_LOG(COMMON, "Flycast unloading game"); - if (frontend_clear_thread_waits_cb != nullptr) - frontend_clear_thread_waits_cb(1, nullptr); dc_stop(); - if (frontend_clear_thread_waits_cb != nullptr) - frontend_clear_thread_waits_cb(0, nullptr); free(game_data); game_data = nullptr; disk_paths.clear(); @@ -1825,6 +1825,7 @@ bool retro_unserialize(const void * data, size_t size) } bool result = dc_loadstate(&data, size); + retro_audio_flush_buffer(); if (config::ThreadedRendering) {