Begin adding AUDIO_CALLBACK/FRAME_TIME_CALLBACK.

This commit is contained in:
Themaister 2013-07-14 13:09:53 +02:00
parent 1e83c59823
commit f4a23115c2
10 changed files with 340 additions and 7 deletions

View File

@ -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
View 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
View 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

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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