From f4a23115c24215271022d44dfc315b718131dad5 Mon Sep 17 00:00:00 2001 From: Themaister Date: Sun, 14 Jul 2013 13:09:53 +0200 Subject: [PATCH] Begin adding AUDIO_CALLBACK/FRAME_TIME_CALLBACK. --- Makefile | 2 +- audio/thread_wrapper.c | 180 +++++++++++++++++++++++++++++++++++++++++ audio/thread_wrapper.h | 30 +++++++ driver.c | 33 +++++++- dynamic.c | 21 +++++ general.h | 5 +- gfx/thread_wrapper.h | 5 ++ griffin/griffin.c | 1 + libretro.h | 41 ++++++++++ retroarch.c | 29 ++++++- 10 files changed, 340 insertions(+), 7 deletions(-) create mode 100644 audio/thread_wrapper.c create mode 100644 audio/thread_wrapper.h diff --git a/Makefile b/Makefile index 46c49f529b..acf4054dbf 100644 --- a/Makefile +++ b/Makefile @@ -93,7 +93,7 @@ ifeq ($(HAVE_RGUI), 1) endif ifeq ($(HAVE_THREADS), 1) - OBJ += autosave.o thread.o gfx/thread_wrapper.o + OBJ += autosave.o thread.o gfx/thread_wrapper.o audio/thread_wrapper.o ifeq ($(findstring Haiku,$(OS)),) LIBS += -lpthread endif diff --git a/audio/thread_wrapper.c b/audio/thread_wrapper.c new file mode 100644 index 0000000000..f3f173b7b9 --- /dev/null +++ b/audio/thread_wrapper.c @@ -0,0 +1,180 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "thread_wrapper.h" +#include "../thread.h" +#include "../general.h" +#include "../performance.h" +#include "../fifo_buffer.h" +#include +#include + +typedef struct audio_thread +{ + const audio_driver_t *driver; + void *driver_data; + + sthread_t *thread; + slock_t *lock; + scond_t *cond; + bool alive; + bool stopped; +} audio_thread_t; + +static void audio_thread_loop(void *data) +{ + audio_thread_t *thr = (audio_thread_t*)data; + + for (;;) + { + slock_lock(thr->lock); + + if (!thr->alive) + { + scond_signal(thr->cond); + slock_unlock(thr->lock); + break; + } + + if (thr->stopped) + { + thr->driver->stop(thr->driver_data); + while (thr->stopped) + scond_wait(thr->cond, thr->lock); + thr->driver->start(thr->driver_data); + } + + slock_unlock(thr->lock); + g_extern.system.audio_callback(); + } +} + +static void audio_thread_block(audio_thread_t *thr) +{ + slock_lock(thr->lock); + thr->stopped = true; + scond_signal(thr->cond); + slock_unlock(thr->lock); +} + +static void audio_thread_unblock(audio_thread_t *thr) +{ + slock_lock(thr->lock); + thr->stopped = false; + scond_signal(thr->cond); + slock_unlock(thr->lock); +} + +static void audio_thread_free(void *data) +{ + audio_thread_t *thr = (audio_thread_t*)data; + slock_lock(thr->lock); + thr->alive = false; + scond_signal(thr->cond); + slock_unlock(thr->lock); + + sthread_join(thr->thread); + + thr->driver->free(thr->driver_data); + slock_free(thr->lock); + scond_free(thr->cond); + free(thr); +} + +static bool audio_thread_stop(void *data) +{ + audio_thread_t *thr = (audio_thread_t*)data; + audio_thread_block(thr); + return true; +} + +static bool audio_thread_start(void *data) +{ + audio_thread_t *thr = (audio_thread_t*)data; + audio_thread_unblock(thr); + return true; +} + +static void audio_thread_set_nonblock_state(void *data, bool state) +{ + audio_thread_t *thr = (audio_thread_t*)data; + (void)state; +} + +static bool audio_thread_use_float(void *data) +{ + audio_thread_t *thr = (audio_thread_t*)data; + return thr->driver->use_float(thr->driver_data); +} + +static ssize_t audio_thread_write(void *data, const void *buf, size_t size) +{ + audio_thread_t *thr = (audio_thread_t*)data; + ssize_t ret = thr->driver->write(thr->driver_data, buf, size); + if (ret < 0) + { + slock_lock(thr->lock); + thr->alive = false; + scond_signal(thr->cond); + slock_unlock(thr->lock); + return ret; + } + + return ret; +} + +static const audio_driver_t audio_thread = { + NULL, + audio_thread_write, + audio_thread_stop, + audio_thread_start, + audio_thread_set_nonblock_state, + audio_thread_free, + audio_thread_use_float, + "audio-thread", + NULL, // No point in using rate control with threaded audio. + NULL, +}; + +bool rarch_threaded_audio_init(const audio_driver_t **out_driver, void **out_data, + const char *device, unsigned out_rate, unsigned latency, + const audio_driver_t *driver) +{ + void *audio_handle = driver->init(device, out_rate, latency); + if (!audio_handle) + return false; + + audio_thread_t *thr = (audio_thread_t*)calloc(1, sizeof(*thr)); + if (!thr) + { + driver->free(audio_handle); + return false; + } + + thr->driver = driver; + thr->driver_data = audio_handle; + + thr->cond = scond_new(); + thr->lock = slock_new(); + thr->alive = true; + thr->stopped = true; + + thr->thread = sthread_create(audio_thread_loop, thr); + + *out_driver = &audio_thread; + *out_data = thr; + return true; +} + diff --git a/audio/thread_wrapper.h b/audio/thread_wrapper.h new file mode 100644 index 0000000000..87e6f88fdb --- /dev/null +++ b/audio/thread_wrapper.h @@ -0,0 +1,30 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef RARCH_AUDIO_THREAD_H__ +#define RARCH_AUDIO_THREAD_H__ + +#include "../driver.h" +#include "../boolean.h" + +// Starts a audio driver in a new thread. +// Access to audio driver will be mediated through this driver. +// This driver interfaces with audio callback and is only used in that case. +bool rarch_threaded_audio_init(const audio_driver_t **out_driver, void **out_data, + const char *device, unsigned out_rate, unsigned latency, + const audio_driver_t *driver); + +#endif + diff --git a/driver.c b/driver.c index 42d31b1928..0ee6dac1ae 100644 --- a/driver.c +++ b/driver.c @@ -24,6 +24,7 @@ #include "audio/utils.h" #include "audio/resampler.h" #include "gfx/thread_wrapper.h" +#include "audio/thread_wrapper.h" #include "gfx/gfx_common.h" #ifdef HAVE_X11 @@ -357,7 +358,7 @@ void global_uninit_drivers(void) if (driver.input_data) { - driver.input->free(NULL); + driver.input->free(driver.input_data); driver.input_data = NULL; } } @@ -382,6 +383,8 @@ void init_drivers(void) // Keep non-throttled state as good as possible. if (driver.nonblock_state) driver_set_nonblock_state(driver.nonblock_state); + + g_extern.system.frame_time_last = 0; } void uninit_drivers(void) @@ -501,8 +504,26 @@ void init_audio(void) return; } - driver.audio_data = audio_init_func(*g_settings.audio.device ? g_settings.audio.device : NULL, - g_settings.audio.out_rate, g_settings.audio.latency); +#ifdef HAVE_THREADS + find_audio_driver(); + if (g_extern.system.audio_callback) + { + RARCH_LOG("Starting threaded audio driver ...\n"); + if (!rarch_threaded_audio_init(&driver.audio, &driver.audio_data, + *g_settings.audio.device ? g_settings.audio.device : NULL, + g_settings.audio.out_rate, g_settings.audio.latency, + driver.audio)) + { + RARCH_ERR("Cannot open threaded audio driver ... Exiting ...\n"); + rarch_fail(1, "init_audio()"); + } + } + else +#endif + { + driver.audio_data = audio_init_func(*g_settings.audio.device ? g_settings.audio.device : NULL, + g_settings.audio.out_rate, g_settings.audio.latency); + } if (!driver.audio_data) { @@ -538,7 +559,8 @@ void init_audio(void) rarch_assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO); rarch_assert(g_extern.audio_data.outsamples = (float*)malloc(outsamples_max * sizeof(float))); - if (g_extern.audio_active && g_settings.audio.rate_control) + g_extern.audio_data.rate_control = false; + if (!g_extern.system.audio_callback && g_extern.audio_active && g_settings.audio.rate_control) { if (driver.audio->buffer_size && driver.audio->write_avail) { @@ -557,6 +579,9 @@ void init_audio(void) #endif g_extern.measure_data.buffer_free_samples_count = 0; + + if (g_extern.audio_active && g_extern.system.audio_callback) // Threaded driver is initially stopped. + audio_start_func(); } static void compute_audio_buffer_statistics(void) diff --git a/dynamic.c b/dynamic.c index 984f8e0ea1..7e50ab976e 100644 --- a/dynamic.c +++ b/dynamic.c @@ -723,6 +723,27 @@ static bool environment_cb(unsigned cmd, void *data) break; } +#ifdef HAVE_THREADS + case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK: + { + RARCH_LOG("Environ SET_AUDIO_CALLBACK.\n"); + const struct retro_audio_callback *info = (const struct retro_audio_callback*)data; + + if (g_extern.recording || g_extern.netplay_enable) // A/V sync is a must. + return false; + g_extern.system.audio_callback = info->callback; + break; + } +#endif + + case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK: + RARCH_LOG("Environ SET_FRAME_TIME_CALLBACK.\n"); + if (g_extern.netplay_enable) // retro_run() will be called in very strange and mysterious ways, have to disable it. + return false; + const struct retro_frame_time_callback *info = (const struct retro_frame_time_callback*)data; + g_extern.system.frame_time_callback = info->callback; + break; + default: RARCH_LOG("Environ UNSUPPORTED (#%u).\n", cmd); return false; diff --git a/general.h b/general.h index 89834a87ba..55dab63154 100644 --- a/general.h +++ b/general.h @@ -386,6 +386,10 @@ struct global char valid_extensions[PATH_MAX]; retro_keyboard_event_t key_event; + retro_audio_callback_t audio_callback; + + retro_usec_t frame_time_last; + retro_frame_time_callback_t frame_time_callback; struct retro_disk_control_callback disk_control; struct retro_hw_render_callback hw_render_callback; @@ -427,7 +431,6 @@ struct global float volume_db; float volume_gain; - } audio_data; struct diff --git a/gfx/thread_wrapper.h b/gfx/thread_wrapper.h index 6019803525..5858ac1e4f 100644 --- a/gfx/thread_wrapper.h +++ b/gfx/thread_wrapper.h @@ -13,6 +13,9 @@ * If not, see . */ +#ifndef RARCH_VIDEO_THREAD_H__ +#define RARCH_VIDEO_THREAD_H__ + #include "../driver.h" #include "../boolean.h" @@ -22,3 +25,5 @@ bool rarch_threaded_video_init(const video_driver_t **out_driver, void **out_dat const input_driver_t **input, void **input_data, const video_driver_t *driver, const video_info_t *info); +#endif + diff --git a/griffin/griffin.c b/griffin/griffin.c index cd152d142b..6865685aba 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -494,6 +494,7 @@ THREAD #elif defined(HAVE_THREADS) #include "../thread.c" #include "../gfx/thread_wrapper.c" +#include "../audio/thread_wrapper.c" #ifndef RARCH_CONSOLE #include "../autosave.c" #endif diff --git a/libretro.h b/libretro.h index 54873fd551..f2d7d1568b 100755 --- a/libretro.h +++ b/libretro.h @@ -486,8 +486,49 @@ enum retro_mod // Retrieves the absolute path from where this libretro implementation was loaded. // NULL is returned if the libretro was loaded statically (i.e. linked statically to frontend), or if the path cannot be determined. // Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can be loaded without ugly hacks. + // +#define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 20 + // const struct retro_audio_callback * -- + // Sets an interface which is used to notify a libretro core about audio being available for writing. + // The callback can be called from any thread, so a core using this must have a thread safe audio implementation. + // It is intended for games where audio and video are completely asynchronous and audio can be generated on the fly. + // This interface is not recommended for use with emulators which have highly synchronous audio. + // + // The callback only notifies about writability; the libretro core still has to call the normal audio callbacks + // to write audio. The audio callbacks must be called from within the notification callback. + // The amount of audio data to write is up to the implementation. + // Generally, the audio callback will be called continously in a loop. + // + // Due to thread safety guarantees and lack of sync between audio and video, a frontend + // can selectively disallow this interface based on internal configuration. A core using + // this interface must also implement the "normal" audio interface. + // + // A libretro core using SET_AUDIO_CALLBACK should also make use of SET_FRAME_TIME_CALLBACK. +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 + // const struct retro_frame_time_callback * -- + // Lets the core know how much time has passed since last invocation of retro_run(). + // The frontend can tamper with the timing to fake fast-forward, slow-motion, frame stepping, etc. + // In this case the delta time will use the FPS value reported in get_av_info(). +// Notifies libretro that audio data should be written. +typedef void (*retro_audio_callback_t)(void); +struct retro_audio_callback +{ + retro_audio_callback_t callback; +}; + +// Notifies a libretro core of time spent since last invocation of retro_run() in microseconds. +// It will be called right before retro_run() every frame. +// The frontend can tamper with timing to support cases like fast-forward, slow-motion and framestepping. +// In those scenarios the FPS value in av_info will be used to fake the frame time. +typedef int64_t retro_usec_t; +typedef void (*retro_frame_time_callback_t)(retro_usec_t usec); +struct retro_frame_time_callback +{ + retro_frame_time_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. #define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) diff --git a/retroarch.c b/retroarch.c index e9f224a3a5..5e28a3033d 100644 --- a/retroarch.c +++ b/retroarch.c @@ -1424,6 +1424,12 @@ void rarch_init_rewind(void) if (!g_settings.rewind_enable || g_extern.state_manager) return; + if (g_extern.system.audio_callback) + { + RARCH_ERR("Implementation uses threaded audio. Cannot use rewind.\n"); + return; + } + g_extern.state_size = pretro_serialize_size(); if (!g_extern.state_size) { @@ -2903,6 +2909,7 @@ int rarch_main_init(int argc, char *argv[]) #endif } + init_libretro_cbs(); init_system_av_info(); init_drivers(); @@ -2915,7 +2922,6 @@ int rarch_main_init(int argc, char *argv[]) #endif rarch_init_rewind(); - init_libretro_cbs(); init_controllers(); #ifdef HAVE_FFMPEG @@ -2978,6 +2984,26 @@ static inline bool check_enter_rgui(void) } } +static inline void update_frame_time(void) +{ + if (!g_extern.system.frame_time_callback) + return; + + rarch_time_t time = rarch_get_time_usec(); + rarch_time_t delta = 0; + + if (!g_extern.system.frame_time_last || g_extern.is_paused || driver.nonblock_state || g_extern.recording) + { + rarch_time_t reference_delta = (rarch_time_t)roundf(1000000LL / g_extern.system.av_info.timing.fps); + delta = reference_delta; + } + else + delta = time - g_extern.system.frame_time_last; + + g_extern.system.frame_time_last = time; + g_extern.system.frame_time_callback(delta); +} + bool rarch_main_iterate(void) { #ifdef HAVE_DYLIB @@ -3020,6 +3046,7 @@ bool rarch_main_iterate(void) bsv_movie_set_frame_start(g_extern.bsv.movie); #endif + update_frame_time(); pretro_run(); #ifdef HAVE_BSV_MOVIE