diff --git a/Makefile b/Makefile index 7d82355959..372f909f96 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ ifeq ($(HAVE_XML), 1) endif ifeq ($(HAVE_DYLIB), 1) - OBJ += gfx/ext.o + OBJ += gfx/ext.o audio/ext.o LIBS += -ldl endif diff --git a/Makefile.win32 b/Makefile.win32 index 216e3f6c17..e0f417fb4a 100644 --- a/Makefile.win32 +++ b/Makefile.win32 @@ -82,7 +82,7 @@ ifeq ($(HAVE_FBO), 1) endif ifeq ($(HAVE_DYLIB), 1) - OBJ += gfx/ext.o + OBJ += gfx/ext.o audio/ext.o endif ifneq ($(V),1) diff --git a/Makefile.win64 b/Makefile.win64 index 414dd440cd..76f6f75a3c 100644 --- a/Makefile.win64 +++ b/Makefile.win64 @@ -82,7 +82,7 @@ ifeq ($(HAVE_FBO), 1) endif ifeq ($(HAVE_DYLIB), 1) - OBJ += gfx/ext.o + OBJ += gfx/ext.o audio/ext.o endif diff --git a/audio/ext.c b/audio/ext.c new file mode 100644 index 0000000000..0b064c2a68 --- /dev/null +++ b/audio/ext.c @@ -0,0 +1,149 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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. + * + * SSNES 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 SSNES. + * If not, see . + */ + +#include "ext/ssnes_audio.h" +#include +#include +#include +#include "driver.h" +#include "dynamic.h" +#include "general.h" +#include + +typedef struct audio_ext +{ + dylib_t lib; + const ssnes_audio_driver_t *driver; + void *handle; + bool is_float; +} audio_ext_t; + +static void audio_ext_free(void *data) +{ + audio_ext_t *ext = data; + if (ext) + { + if (ext->driver && ext->handle) + ext->driver->free(ext->handle); + if (ext->lib) + dylib_close(ext->lib); + free(ext); + } +} + +static void* audio_ext_init(const char *device, int rate, int latency) +{ + if (!(*g_settings.audio.external_driver)) + { + SSNES_ERR("Please define an external audio driver.\n"); + return NULL; + } + + audio_ext_t *ext = calloc(1, sizeof(*ext)); + if (!ext) + return NULL; + + ext->lib = dylib_load(g_settings.audio.external_driver); + if (!ext->lib) + { + SSNES_ERR("Failed to load external library \"%s\"\n", g_settings.audio.external_driver); + goto error; + } + + const ssnes_audio_driver_t* (*plugin_load)(void) = dylib_proc(ext->lib, "ssnes_audio_driver_init"); + + if (!plugin_load) + { + SSNES_ERR("Failed to find symbol \"ssnes_audio_driver_init\" in plugin.\n"); + goto error; + } + + ext->driver = plugin_load(); + if (!ext->driver) + { + SSNES_ERR("Received invalid driver from plugin.\n"); + goto error; + } + + ssnes_audio_driver_info_t info = { + .device = device, + .sample_rate = rate, + .latency = latency + }; + + ext->handle = ext->driver->init(&info); + if (!ext->handle) + { + SSNES_ERR("Failed to init audio driver.\n"); + goto error; + } + + return ext; + +error: + audio_ext_free(ext); + return NULL; +} + +static ssize_t audio_ext_write(void *data, const void *buf, size_t size) +{ + audio_ext_t *ext = data; + unsigned frame_size = ext->is_float ? (2 * sizeof(float)) : (2 * sizeof(int16_t)); + size /= frame_size; + + int ret = ext->driver->write(ext->handle, buf, size); + if (ret < 0) + return -1; + return ret * frame_size; +} + +static bool audio_ext_start(void *data) +{ + audio_ext_t *ext = data; + return ext->driver->start(ext->handle); +} + +static bool audio_ext_stop(void *data) +{ + audio_ext_t *ext = data; + return ext->driver->stop(ext->handle); +} + +static void audio_ext_set_nonblock_state(void *data, bool toggle) +{ + audio_ext_t *ext = data; + ext->driver->set_nonblock_state(ext->handle, toggle); +} + +static bool audio_ext_use_float(void *data) +{ + audio_ext_t *ext = data; + ext->is_float = ext->driver->use_float(ext->handle); + return ext->is_float; +} + + +const audio_driver_t audio_ext = { + .init = audio_ext_init, + .write = audio_ext_write, + .stop = audio_ext_stop, + .start = audio_ext_start, + .set_nonblock_state = audio_ext_set_nonblock_state, + .use_float = audio_ext_use_float, + .free = audio_ext_free, + .ident = "ext" +}; diff --git a/audio/ext/ssnes_audio.h b/audio/ext/ssnes_audio.h new file mode 100644 index 0000000000..f1abe1daef --- /dev/null +++ b/audio/ext/ssnes_audio.h @@ -0,0 +1,110 @@ +///// +// API header for external SSNES audio driver plugins. +// +// + +#ifndef __SSNES_AUDIO_DRIVER_PLUGIN_H +#define __SSNES_AUDIO_DRIVER_PLUGIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifdef SSNES_DLL_IMPORT +#define SSNES_API_EXPORT __declspec(dllimport) +#else +#define SSNES_API_EXPORT __declspec(dllexport) +#endif +#define SSNES_API_CALLTYPE __cdecl +#else +#define SSNES_API_EXPORT +#define SSNES_API_CALLTYPE +#endif + +#define SSNES_TRUE 1 +#define SSNES_FALSE 0 +#define SSNES_OK 1 +#define SSNES_ERROR 0 + +#define SSNES_API_VERSION 1 + +typedef struct ssnes_audio_driver_info +{ + // A hint for a subdevice of the audio driver. + // This is driver independent, and not relevant for all + // audio drivers. I.e. ALSA driver might use "hw:0", + // OSS "/dev/audio", etc. + const char *device; + + // Audio sample rate. + unsigned sample_rate; + + // Maximum audio latency requested for output, + // measured in milliseconds. + // If driver is not able to provide this latency, it can + // be disregarded. + unsigned latency; +} ssnes_audio_driver_info_t; + +typedef struct ssnes_audio_driver +{ + // Initializes the device. + void* (*init)(const ssnes_audio_driver_info_t *info); + + // Write data in buffer to audio driver. + // A frame here is defined as one combined sample of left and right + // channels. (I.e. 44.1kHz, 16-bit stereo has 88.2k samples/s, and + // 44.1k frames/s.) + // + // Samples are interleaved in format LRLRLRLRLR ... + // If the driver returns true in use_float(), a floating point + // format will be used, with range [-1.0, 1.0]. + // If not, signed 16-bit samples in native byte ordering will be used. + // + // This function returns the number of frames successfully written. + // If an error occurs, -1 should be returned. + // Note that non-blocking behavior that cannot write at this time + // should return 0 as returning -1 will terminate the driver. + // + // Unless said otherwise with set_nonblock_state(), all writes + // are blocking, and it should block till it has written all frames. + int (*write)(void *data, const void *buffer, unsigned frames); + + // Temporarily pauses the audio driver. + int (*stop)(void *data); + + // Resumes audio driver from the paused state. + int (*start)(void *data); + + // If state is true, nonblocking operation is assumed. + // This is typically used for fast-forwarding. If driver cannot + // implement nonblocking writes, this can be disregarded, but should + // log a message to stderr. + void (*set_nonblock_state)(void *data, int state); + + // Stops and frees the audio driver. + void (*free)(void *data); + + // If true is returned, the audio driver is capable of using + // floating point data. This will likely increase performance as the + // resampler unit uses floating point. The sample range is + // [-1.0, 1.0]. + int (*use_float)(void *data); + + // Human readable identification string for the driver. + const char *ident; + + // Must be set to SSNES_API_VERSION. + // Used for detecting API mismatch. + int api_version; +} ssnes_audio_driver_t; + +SSNES_API_EXPORT const ssnes_audio_driver_t* SSNES_API_CALLTYPE + ssnes_audio_driver_init(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/config.def.h b/config.def.h index 65db3a1c98..763705ec66 100644 --- a/config.def.h +++ b/config.def.h @@ -56,6 +56,7 @@ #define AUDIO_SDL 8 #define AUDIO_XAUDIO 9 #define AUDIO_PULSE 10 +#define AUDIO_EXT 15 //////////////////////// #define INPUT_SDL 7 #define INPUT_X 12 @@ -91,6 +92,8 @@ #define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND #elif defined(HAVE_ROAR) #define AUDIO_DEFAULT_DRIVER AUDIO_ROAR +#elif defined(HAVE_DYLIB) +#define AUDIO_DEFAULT_DRIVER AUDIO_EXT #else #error "Need at least one audio driver!" #endif diff --git a/driver.c b/driver.c index c2e157fa3b..0932cfaf1c 100644 --- a/driver.c +++ b/driver.c @@ -56,6 +56,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_PULSE &audio_pulse, #endif +#ifdef HAVE_DYLIB + &audio_ext, +#endif }; static const video_driver_t *video_drivers[] = { diff --git a/driver.h b/driver.h index 2cd8e9969f..10d708778a 100644 --- a/driver.h +++ b/driver.h @@ -154,6 +154,7 @@ extern const audio_driver_t audio_jack; extern const audio_driver_t audio_sdl; extern const audio_driver_t audio_xa; extern const audio_driver_t audio_pulse; +extern const audio_driver_t audio_ext; extern const video_driver_t video_gl; extern const video_driver_t video_xvideo; extern const video_driver_t video_sdl; diff --git a/general.h b/general.h index acdaabc77f..18d00c2024 100644 --- a/general.h +++ b/general.h @@ -106,6 +106,7 @@ struct settings int src_quality; char dsp_plugin[256]; + char external_driver[256]; } audio; struct diff --git a/settings.c b/settings.c index 8570fb5b3f..0266842752 100644 --- a/settings.c +++ b/settings.c @@ -83,6 +83,9 @@ static void set_defaults(void) case AUDIO_PULSE: def_audio = "pulse"; break; + case AUDIO_EXT: + def_audio = "ext"; + break; default: break; } @@ -315,6 +318,7 @@ static void parse_config_file(void) #ifdef HAVE_DYLIB CONFIG_GET_STRING(video.filter_path, "video_filter"); CONFIG_GET_STRING(video.external_driver, "video_external_driver"); + CONFIG_GET_STRING(audio.external_driver, "audio_external_driver"); #endif #if defined(HAVE_CG) || defined(HAVE_XML) diff --git a/ssnes.cfg b/ssnes.cfg index 17c6de4162..4adfbe3398 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -99,9 +99,12 @@ # When altering audio_in_rate on-the-fly, define by how much each time. # audio_rate_step = 0.25 -# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl and xaudio +# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl, xaudio and ext (external driver). # audio_driver = +# Path to external audio driver using the SSNES audio driver API. +# audio_external_driver = + # Override the default audio device the audio_driver uses. This is driver dependant. E.g. ALSA wants a PCM device, OSS wants a path (e.g. /dev/dsp), Jack wants portnames (e.g. system:playback1,system:playback_2), and so on ... # audio_device =