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