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 =