From b47f58c33814cf5361cd15709b035abdd8a57a7a Mon Sep 17 00:00:00 2001 From: Themaister Date: Sat, 1 Jan 2011 03:53:30 +0100 Subject: [PATCH] Add Jack audio driver. --- Makefile | 4 + audio/jack.c | 292 ++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 1 + driver.c | 3 + driver.h | 1 + dynamic.c | 2 + qb/config.libs.sh | 3 +- 7 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 audio/jack.c diff --git a/Makefile b/Makefile index 6f3e3a5372..3972d52e75 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ ifeq ($(HAVE_AL), 1) OBJ += audio/openal.o LIBS += -lopenal endif +ifeq ($(HAVE_JACK),1) + OBJ += audio/jack.o + LIBS += -ljack +endif ifeq ($(HAVE_GLFW), 1) OBJ += gfx/gl.o diff --git a/audio/jack.c b/audio/jack.c new file mode 100644 index 0000000000..cb8266118c --- /dev/null +++ b/audio/jack.c @@ -0,0 +1,292 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 "driver.h" +#include +#include "general.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FRAMES(x) (x / (sizeof(int16_t) * 2)) +#define SAMPLES(x) (x / sizeof(int16_t)) + +typedef struct jack +{ + jack_client_t *client; + jack_port_t *ports[2]; + jack_ringbuffer_t *buffer[2]; + volatile bool shutdown; + bool nonblock; + + pthread_cond_t cond; + pthread_mutex_t cond_lock; +} jack_t; + +static int process_cb(jack_nframes_t nframes, void *data) +{ + jack_t *jd = data; + if (nframes <= 0) + { + pthread_cond_signal(&jd->cond); + return 0; + } + + jack_nframes_t avail[2]; + avail[0] = jack_ringbuffer_read_space(jd->buffer[0]); + avail[1] = jack_ringbuffer_read_space(jd->buffer[1]); + jack_nframes_t min_avail = FRAMES((avail[0] < avail[1]) ? avail[0] : avail[1]); + + if (min_avail > nframes) + min_avail = nframes; + + //static int underrun = 0; + //if (min_avail < nframes) + //{ + // fprintf(stderr, "underrun count: %d\n", underrun++); + // fprintf(stderr, "required %d frames, got %d.\n", (int)nframes, (int)min_avail); + //} + + for (int i = 0; i < 2; i++) + { + jack_default_audio_sample_t *out = jack_port_get_buffer(jd->ports[i], nframes); + assert(out); + jack_ringbuffer_read(jd->buffer[i], (char*)out, min_avail * sizeof(jack_default_audio_sample_t)); + + for (jack_nframes_t f = min_avail; f < nframes; f++) + { + out[f] = 0.0f; + } + } + pthread_cond_signal(&jd->cond); + return 0; +} + +static void shutdown_cb(void *data) +{ + jack_t *jd = data; + jd->shutdown = true; + pthread_cond_signal(&jd->cond); +} + +static inline void s16_to_float(jack_default_audio_sample_t * restrict out, const int16_t * restrict in, size_t samples) +{ + for (int i = 0; i < samples; i++) + out[i] = (float)in[i] / 0x8000; +} + +static void parse_ports(const char **dest_ports, const char **jports) +{ + int parsed = 0; + + const char *con = strtok(g_settings.audio.device, ","); + if (con) + dest_ports[parsed++] = con; + con = strtok(NULL, ","); + if (con) + dest_ports[parsed++] = con; + + for (int i = parsed; i < 2; i++) + dest_ports[i] = jports[i]; +} + +#define JACK_BUFFER_SIZE_MIN_FRAMES 128 +static void* __jack_init(const char* device, int rate, int latency) +{ + jack_t *jd = calloc(1, sizeof(jack_t)); + if ( jd == NULL ) + return NULL; + + const char **jports = NULL; + + jd->client = jack_client_open("SSNES", JackNullOption, NULL); + if (jd->client == NULL) + goto error; + + g_settings.audio.out_rate = jack_get_sample_rate(jd->client); + + jack_set_process_callback(jd->client, process_cb, jd); + jack_on_shutdown(jd->client, shutdown_cb, jd); + + jd->ports[0] = jack_port_register(jd->client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + jd->ports[1] = jack_port_register(jd->client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (jd->ports[0] == NULL || jd->ports[1] == NULL) + { + SSNES_ERR("Failed to register ports.\n"); + goto error; + } + + jack_nframes_t bufsize; + jack_nframes_t jack_bufsize = jack_get_buffer_size(jd->client); + + bufsize = (latency * g_settings.audio.out_rate / 1000 + 1) > jack_bufsize ? (latency * g_settings.audio.out_rate / 1000 + 1) : jack_bufsize; + bufsize *= sizeof(jack_default_audio_sample_t); + + //fprintf(stderr, "jack buffer size: %d\n", (int)bufsize); + for (int i = 0; i < 2; i++) + { + jd->buffer[i] = jack_ringbuffer_create(bufsize); + if (jd->buffer[i] == NULL) + { + SSNES_ERR("Failed to create buffers.\n"); + goto error; + } + } + + const char *dest_ports[2]; + jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput); + if (jports == NULL) + { + SSNES_ERR("Failed to get ports.\n"); + goto error; + } + + parse_ports(dest_ports, jports); + + if (jack_activate(jd->client) < 0) + { + SSNES_ERR("Failed to activate Jack...\n"); + goto error; + } + + for (int i = 0; i < 2; i++) + { + if (jack_connect(jd->client, jack_port_name(jd->ports[i]), dest_ports[i])) + { + SSNES_ERR("Failed to connect to Jack port.\n"); + goto error; + } + } + + pthread_cond_init(&jd->cond, NULL); + pthread_mutex_init(&jd->cond_lock, NULL); + + + jack_free(jports); + return jd; + +error: + if (jports != NULL) + jack_free(jports); + return NULL; +} + +static size_t write_buffer(jack_t *jd, const void *buf, size_t size) +{ + //fprintf(stderr, "write_buffer: size: %zu\n", size); + // Convert our data to float, deinterleave and write. + jack_default_audio_sample_t out_buffer[size / sizeof(int16_t)]; + jack_default_audio_sample_t out_deinterleaved_buffer[2][FRAMES(size)]; + s16_to_float(out_buffer, buf, SAMPLES(size)); + + for (int i = 0; i < 2; i++) + for (size_t j = 0; j < FRAMES(size); j++) + out_deinterleaved_buffer[i][j] = out_buffer[j * 2 + i]; + + for(;;) + { + if (jd->shutdown) + return 0; + + size_t avail[2]; + avail[0] = jack_ringbuffer_write_space(jd->buffer[0]); + avail[1] = jack_ringbuffer_write_space(jd->buffer[1]); + size_t min_avail = avail[0] < avail[1] ? avail[0] : avail[1]; + + if (jd->nonblock && min_avail < FRAMES(size) * sizeof(jack_default_audio_sample_t)) + return 0; + + //fprintf(stderr, "Write avail is: %d\n", (int)min_avail); + if (min_avail >= FRAMES(size) * sizeof(jack_default_audio_sample_t)) + break; + + pthread_mutex_lock(&jd->cond_lock); + pthread_cond_wait(&jd->cond, &jd->cond_lock); + pthread_mutex_unlock(&jd->cond_lock); + } + + for (int i = 0; i < 2; i++) + jack_ringbuffer_write(jd->buffer[i], (const char*)out_deinterleaved_buffer[i], FRAMES(size) * sizeof(jack_default_audio_sample_t)); + return size; +} + +static ssize_t __jack_write(void* data, const void* buf, size_t size) +{ + jack_t *jd = data; + + return write_buffer(jd, buf, size); +} + +static bool __jack_stop(void *data) +{ + (void)data; + return true; +} + +static void __jack_set_nonblock_state(void *data, bool state) +{ + jack_t *jd = data; + jd->nonblock = state; +} + +static bool __jack_start(void *data) +{ + (void)data; + return true; +} + +static void __jack_free(void *data) +{ + jack_t *jd = data; + + if (jd->client != NULL) + { + jack_deactivate(jd->client); + jack_client_close(jd->client); + } + + for (int i = 0; i < 2; i++) + if (jd->buffer[i] != NULL) + jack_ringbuffer_free(jd->buffer[i]); + + pthread_mutex_destroy(&jd->cond_lock); + pthread_cond_destroy(&jd->cond); + free(jd); +} + +const audio_driver_t audio_jack = { + .init = __jack_init, + .write = __jack_write, + .stop = __jack_stop, + .start = __jack_start, + .set_nonblock_state = __jack_set_nonblock_state, + .free = __jack_free, + .ident = "jack" +}; + + + + + + diff --git a/config.def.h b/config.def.h index 8db9455dcd..d2ce444381 100644 --- a/config.def.h +++ b/config.def.h @@ -37,6 +37,7 @@ #define AUDIO_ALSA 3 #define AUDIO_ROAR 4 #define AUDIO_AL 5 +#define AUDIO_JACK 6 //////////////////////// #define VIDEO_DEFAULT_DRIVER VIDEO_GL diff --git a/driver.c b/driver.c index c1d451906f..4e694af930 100644 --- a/driver.c +++ b/driver.c @@ -39,6 +39,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_ROAR &audio_roar, #endif +#ifdef HAVE_JACK + &audio_jack, +#endif }; static const video_driver_t *video_drivers[] = { diff --git a/driver.h b/driver.h index 651dd3cf9b..f695746cd3 100644 --- a/driver.h +++ b/driver.h @@ -106,6 +106,7 @@ extern const audio_driver_t audio_oss; extern const audio_driver_t audio_alsa; extern const audio_driver_t audio_roar; extern const audio_driver_t audio_openal; +extern const audio_driver_t audio_jack; extern const video_driver_t video_gl; //////////////////////////////////////////////// diff --git a/dynamic.c b/dynamic.c index af87e0d027..5c8ff14ceb 100644 --- a/dynamic.c +++ b/dynamic.c @@ -30,7 +30,9 @@ #endif +#ifdef HAVE_DYNAMIC static void *lib_handle = NULL; +#endif void (*psnes_init)(void); diff --git a/qb/config.libs.sh b/qb/config.libs.sh index a94db0fc9c..a01eaf03d9 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -14,6 +14,7 @@ check_header OSS sys/soundcard.h check_lib AL -lopenal alcOpenDevice check_lib RSOUND -lrsound rsd_init check_lib ROAR -lroar roar_vs_new +check_lib JACK -ljack jack_client_open check_lib GLFW -lglfw glfwInit check_critical GLFW "Cannot find GLFW library." @@ -25,7 +26,7 @@ check_lib SRC -lsamplerate src_callback_new check_lib DYNAMIC -ldl dlopen # Creates config.mk. -VARS="ALSA OSS AL RSOUND ROAR GLFW FILTER CG DYNAMIC" +VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC" create_config_make config.mk $VARS create_config_header config.h $VARS