diff --git a/Makefile b/Makefile index 511e40341d..31cfd447da 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ ifeq ($(HAVE_JACK),1) endif ifeq ($(HAVE_SDL), 1) - OBJ += gfx/gl.o input/sdl.o + OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o LIBS += $(SDL_LIBS) -lGL DEFINES += $(SDL_CFLAGS) endif diff --git a/audio/buffer.c b/audio/buffer.c new file mode 100644 index 0000000000..f3df435acb --- /dev/null +++ b/audio/buffer.c @@ -0,0 +1,121 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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. + * + * RSound 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 RSound. + * If not, see . + */ + +#include "buffer.h" + +struct rsound_fifo_buffer +{ + char *buffer; + size_t bufsize; + size_t first; + size_t end; +}; + +rsound_fifo_buffer_t* rsnd_fifo_new(size_t size) +{ + rsound_fifo_buffer_t *buf = calloc(1, sizeof(*buf)); + if (buf == NULL) + return NULL; + + buf->buffer = calloc(1, size + 1); + if (buf->buffer == NULL) + { + free(buf); + return NULL; + } + buf->bufsize = size + 1; + + return buf; +} + +void rsnd_fifo_free(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + free(buffer->buffer); + free(buffer); +} + +size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + return end - first; +} + +size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer) +{ + assert(buffer); + assert(buffer->buffer); + + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + + return (buffer->bufsize - 1) - (end - first); +} + +void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size) +{ + assert(buffer); + assert(buffer->buffer); + assert(in_buf); + assert(rsnd_fifo_write_avail(buffer) >= size); + + size_t first_write = size; + size_t rest_write = 0; + if (buffer->end + size > buffer->bufsize) + { + first_write = buffer->bufsize - buffer->end; + rest_write = size - first_write; + } + + memcpy(buffer->buffer + buffer->end, in_buf, first_write); + if (rest_write > 0) + memcpy(buffer->buffer, (const char*)in_buf + first_write, rest_write); + + buffer->end = (buffer->end + size) % buffer->bufsize; +} + + +void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size) +{ + assert(buffer); + assert(buffer->buffer); + assert(in_buf); + assert(rsnd_fifo_read_avail(buffer) >= size); + + size_t first_read = size; + size_t rest_read = 0; + if (buffer->first + size > buffer->bufsize) + { + first_read = buffer->bufsize - buffer->first; + rest_read = size - first_read; + } + + memcpy(in_buf, (const char*)buffer->buffer + buffer->first, first_read); + if (rest_read > 0) + memcpy((char*)in_buf + first_read, buffer->buffer, rest_read); + + buffer->first = (buffer->first + size) % buffer->bufsize; +} + diff --git a/audio/buffer.h b/audio/buffer.h new file mode 100644 index 0000000000..63678d2223 --- /dev/null +++ b/audio/buffer.h @@ -0,0 +1,36 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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. + * + * RSound 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 RSound. + * If not, see . + */ + +#ifndef __BUFFER_H +#define __BUFFER_H + +#include +#include +#include +#include + +#ifndef RSD_FIFO_BUF_TYPEDEF +#define RSD_FIFO_BUF_TYPEDEF +typedef struct rsound_fifo_buffer rsound_fifo_buffer_t; +#endif + +rsound_fifo_buffer_t* rsnd_fifo_new(size_t size); +void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size); +void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size); +void rsnd_fifo_free(rsound_fifo_buffer_t* buffer); +size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer); +size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer); + +#endif diff --git a/audio/sdl.c b/audio/sdl.c new file mode 100644 index 0000000000..f24836f570 --- /dev/null +++ b/audio/sdl.c @@ -0,0 +1,191 @@ +/* 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 +#include +#include +#include + +#include +#include +#include +#include "general.h" +#include "buffer.h" + +typedef struct sdl_audio +{ + bool nonblock; + + SDL_mutex *lock; + SDL_cond *cond; + rsound_fifo_buffer_t *buffer; +} sdl_audio_t; + +static void sdl_audio_cb(void *data, Uint8 *stream, int len) +{ + sdl_audio_t *sdl = data; + + size_t avail = rsnd_fifo_read_avail(sdl->buffer); + size_t write_size = len > avail ? avail : len; + rsnd_fifo_read(sdl->buffer, stream, write_size); + SDL_CondSignal(sdl->cond); + + // If underrun, fill rest with silence. + memset(stream + write_size, 0, len - write_size); +} + +static void* sdl_audio_init(const char* device, int rate, int latency) +{ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + return NULL; + + sdl_audio_t *sdl = calloc(1, sizeof(*sdl)); + if (!sdl) + return NULL; + + SDL_AudioSpec spec = { + .freq = rate, + .format = AUDIO_S16SYS, + .channels = 2, + .samples = 512, + .callback = sdl_audio_cb, + .userdata = sdl + }; + + SDL_AudioSpec out; + + if (SDL_OpenAudio(&spec, &out) < 0) + { + SSNES_ERR("Failed to open SDL audio: %s\n", SDL_GetError()); + free(sdl); + return 0; + } + g_settings.audio.out_rate = out.freq; + + sdl->lock = SDL_CreateMutex(); + sdl->cond = SDL_CreateCond(); + + // Create a buffer twice as big as needed and prefill the buffer. + size_t bufsize = out.samples * 4 * sizeof(int16_t); + void *tmp = calloc(1, bufsize); + sdl->buffer = rsnd_fifo_new(bufsize); + if (tmp) + { + rsnd_fifo_write(sdl->buffer, tmp, bufsize); + free(tmp); + } + + SDL_PauseAudio(0); + return sdl; +} + +static ssize_t sdl_audio_write(void* data, const void* buf, size_t size) +{ + sdl_audio_t *sdl = data; + + ssize_t ret = 0; + if (sdl->nonblock) + { + SDL_LockAudio(); + size_t avail = rsnd_fifo_write_avail(sdl->buffer); + size_t write_amt = avail > size ? size : avail; + rsnd_fifo_write(sdl->buffer, buf, write_amt); + SDL_UnlockAudio(); + ret = write_amt; + } + else + { + size_t written = 0; + while (written < size) + { + SDL_LockAudio(); + size_t avail = rsnd_fifo_write_avail(sdl->buffer); + + if (avail == 0) + { + SDL_UnlockAudio(); + SDL_mutexP(sdl->lock); + SDL_CondWait(sdl->cond, sdl->lock); + SDL_mutexV(sdl->lock); + } + else + { + size_t write_amt = size - written > avail ? avail : size - written; + rsnd_fifo_write(sdl->buffer, (const char*)buf + written, write_amt); + SDL_UnlockAudio(); + written += write_amt; + } + } + ret = written; + } + + return ret; +} + +static bool sdl_audio_stop(void *data) +{ + (void)data; + SDL_PauseAudio(1); + return true; +} + +static bool sdl_audio_start(void *data) +{ + (void)data; + SDL_PauseAudio(0); + return true; +} + +static void sdl_audio_set_nonblock_state(void *data, bool state) +{ + sdl_audio_t *sdl = data; + sdl->nonblock = state; +} + +static void sdl_audio_free(void *data) +{ + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + + sdl_audio_t *sdl = data; + if (sdl) + { + rsnd_fifo_free(sdl->buffer); + SDL_DestroyMutex(sdl->lock); + SDL_DestroyCond(sdl->cond); + } + free(sdl); +} + +const audio_driver_t audio_sdl = { + .init = sdl_audio_init, + .write = sdl_audio_write, + .stop = sdl_audio_stop, + .start = sdl_audio_start, + .set_nonblock_state = sdl_audio_set_nonblock_state, + .free = sdl_audio_free, + .ident = "sdl" +}; + + + + + + diff --git a/config.def.h b/config.def.h index 1453430a5b..fea34b99a7 100644 --- a/config.def.h +++ b/config.def.h @@ -42,6 +42,7 @@ #define AUDIO_ROAR 4 #define AUDIO_AL 5 #define AUDIO_JACK 6 +#define AUDIO_SDL 8 //////////////////////// #define INPUT_SDL 7 //////////////////////// @@ -60,6 +61,8 @@ #define AUDIO_DEFAULT_DRIVER AUDIO_ROAR #elif HAVE_AL #define AUDIO_DEFAULT_DRIVER AUDIO_AL +#elif HAVE_SDL +#define AUDIO_DEFAULT_DRIVER AUDIO_SDL #endif #define INPUT_DEFAULT_DRIVER INPUT_SDL diff --git a/driver.c b/driver.c index 8c73f53eef..fafb0139d1 100644 --- a/driver.c +++ b/driver.c @@ -42,6 +42,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_JACK &audio_jack, #endif +#ifdef HAVE_SDL + &audio_sdl, +#endif }; static const video_driver_t *video_drivers[] = { diff --git a/driver.h b/driver.h index ed80623f4f..ec5f3dcca8 100644 --- a/driver.h +++ b/driver.h @@ -111,6 +111,7 @@ 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 audio_driver_t audio_sdl; extern const video_driver_t video_gl; extern const input_driver_t input_sdl; ////////////////////////////////////////////////