mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-23 16:09:47 +00:00
Begin adding AUDIO_CALLBACK/FRAME_TIME_CALLBACK.
This commit is contained in:
parent
1e83c59823
commit
f4a23115c2
2
Makefile
2
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
|
||||
|
180
audio/thread_wrapper.c
Normal file
180
audio/thread_wrapper.c
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "thread_wrapper.h"
|
||||
#include "../thread.h"
|
||||
#include "../general.h"
|
||||
#include "../performance.h"
|
||||
#include "../fifo_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
30
audio/thread_wrapper.h
Normal file
30
audio/thread_wrapper.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
||||
|
33
driver.c
33
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)
|
||||
|
21
dynamic.c
21
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;
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,9 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
|
@ -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
|
||||
|
41
libretro.h
41
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)
|
||||
|
29
retroarch.c
29
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
|
||||
|
Loading…
Reference in New Issue
Block a user