From 776291a13790e111b07f0f4e638dc225c827f578 Mon Sep 17 00:00:00 2001 From: Themaister Date: Thu, 4 Aug 2011 18:45:40 +0200 Subject: [PATCH] Start working on DirectSound driver. Untested so far. --- Makefile.win32 | 7 + Makefile.win64 | 7 + audio/dsound.c | 351 +++++++++++++++++++++++++++++++++++++++++++++++++ audio/sdl.c | 5 - config.def.h | 3 + driver.c | 3 + driver.h | 1 + settings.c | 3 + 8 files changed, 375 insertions(+), 5 deletions(-) create mode 100644 audio/dsound.c diff --git a/Makefile.win32 b/Makefile.win32 index 9524b66fea..7499eb66df 100644 --- a/Makefile.win32 +++ b/Makefile.win32 @@ -13,6 +13,7 @@ HAVE_SDL_IMAGE = 1 HAVE_XML = 1 HAVE_FREETYPE = 1 HAVE_XAUDIO = 1 +HAVE_DSOUND = 1 HAVE_RSOUND = 1 HAVE_DYLIB = 1 HAVE_NETPLAY = 1 @@ -59,6 +60,12 @@ ifeq ($(HAVE_XAUDIO), 1) DEFINES += -DHAVE_XAUDIO endif +ifeq ($(HAVE_DSOUND), 1) + OBJ += audio/dsound.o + DEFINES += -DHAVE_DSOUND + LIBS += -ldxguid -ldsound +endif + ifeq ($(HAVE_RSOUND), 1) OBJ += audio/rsound.o DEFINES += -DHAVE_RSOUND diff --git a/Makefile.win64 b/Makefile.win64 index cfaa2eafea..0aaa65c114 100644 --- a/Makefile.win64 +++ b/Makefile.win64 @@ -12,6 +12,7 @@ HAVE_SDL = 1 HAVE_XML = 1 HAVE_FREETYPE = 1 HAVE_XAUDIO = 1 +HAVE_DSOUND = 1 HAVE_RSOUND = 0 HAVE_DYLIB = 1 HAVE_NETPLAY = 1 @@ -52,6 +53,12 @@ ifeq ($(HAVE_XAUDIO), 1) DEFINES += -DHAVE_XAUDIO endif +ifeq ($(HAVE_DSOUND), 1) + OBJ += audio/dsound.o + DEFINES += -DHAVE_DSOUND + LIBS += -ldxguid -ldsound +endif + ifeq ($(HAVE_RSOUND), 1) OBJ += audio/rsound.o DEFINES += -DHAVE_RSOUND diff --git a/audio/dsound.c b/audio/dsound.c new file mode 100644 index 0000000000..0c2764c106 --- /dev/null +++ b/audio/dsound.c @@ -0,0 +1,351 @@ +/* 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 "driver.h" +#include +#include +#include +#include +#include + +#include +#include "fifo_buffer.h" +#include "general.h" + +typedef struct dsound +{ + LPDIRECTSOUND ds; + LPDIRECTSOUNDBUFFER dsb; + HANDLE event; + bool nonblock; + + fifo_buffer_t *buffer; + CRITICAL_SECTION crit; + + volatile bool thread_alive; + HANDLE thread; + unsigned buffer_size; +} dsound_t; + +static inline unsigned write_avail(unsigned read_ptr, unsigned write_ptr, unsigned buffer_mask) +{ + return (read_ptr + buffer_mask + 1 - write_ptr) & buffer_mask; +} + +static inline void get_positions(dsound_t *ds, DWORD *read_ptr, DWORD *write_ptr) +{ + IDirectSoundBuffer_GetCurrentPosition(ds->dsb, read_ptr, write_ptr); +} + +#define CHUNK_SIZE 128 + +static inline void* grab_chunk(dsound_t *ds, DWORD write_ptr) +{ + void *chunk; + DWORD bytes; + HRESULT res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, &chunk, &bytes, NULL, NULL, 0); + if (res == DSERR_BUFFERLOST) + { + res = IDirectSoundBuffer_Restore(ds->dsb); + if (res != DS_OK) + return NULL; + + res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, &chunk, &bytes, NULL, NULL, 0); + if (res != DS_OK) + return NULL; + } + + if (bytes != CHUNK_SIZE) + { + SSNES_ERR("[DirectSound]: Could not lock as much data as requested, this should not happen!\n"); + IDirectSoundBuffer_Unlock(ds->dsb, chunk, bytes, NULL, 0); + return NULL; + } + + return chunk; +} + +static inline void release_chunk(dsound_t *ds, void *ptr) +{ + IDirectSoundBuffer_Unlock(ds->dsb, ptr, CHUNK_SIZE, NULL, 0); +} + + +static DWORD CALLBACK dsound_thread(PVOID data) +{ + dsound_t *ds = data; + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + unsigned buffer_mask = ds->buffer_size - 1; + DWORD write_ptr; + get_positions(ds, NULL, &write_ptr); + write_ptr = (write_ptr + ds->buffer_size / 2) & buffer_mask; + + while (ds->thread_alive) + { + DWORD read_ptr; + get_positions(ds, &read_ptr, NULL); + + DWORD avail = write_avail(read_ptr, write_ptr, buffer_mask); + if (avail > ds->buffer_size / 2) // We've underrun (or started to underrun) for some odd reason, skip ahead. + { + write_ptr = (read_ptr + ds->buffer_size - 2 * CHUNK_SIZE) & buffer_mask; + avail = 2 * CHUNK_SIZE; + } + + EnterCriticalSection(&ds->crit); + DWORD fifo_avail = fifo_read_avail(ds->buffer); + LeaveCriticalSection(&ds->crit); + + if (avail < CHUNK_SIZE) // No space to write. + { + Sleep(1); + // We could opt for using the notification interface, + // but it is not guaranteed to work, so use high priority sleeping patterns. :( + } + else if (fifo_avail < CHUNK_SIZE) // Got space to write, but nothing in FIFO (underrun), fill block with silence. + { + void *chunk; + if (!(chunk = grab_chunk(ds, write_ptr))) + { + ds->thread_alive = false; + break; + } + + memset(chunk, 0, CHUNK_SIZE); + release_chunk(ds, chunk); + write_ptr = (write_ptr + CHUNK_SIZE) & buffer_mask; + } + else // All is good. Pull from it and notify FIFO :D + { + void *chunk; + if (!(chunk = grab_chunk(ds, write_ptr))) + { + ds->thread_alive = false; + break; + } + + EnterCriticalSection(&ds->crit); + fifo_read(ds->buffer, chunk, CHUNK_SIZE); + LeaveCriticalSection(&ds->crit); + + release_chunk(ds, chunk); + write_ptr = (write_ptr + CHUNK_SIZE) & buffer_mask; + + SetEvent(ds->event); + } + } + + ExitThread(0); +} + +static void dsound_stop_thread(dsound_t *ds) +{ + if (ds->thread) + { + ds->thread_alive = false; + WaitForSingleObject(ds->thread, INFINITE); + CloseHandle(ds->thread); + ds->thread = NULL; + } +} + +static bool dsound_start_thread(dsound_t *ds) +{ + if (!ds->thread) + { + ds->thread_alive = true; + ds->thread = CreateThread(NULL, 0, dsound_thread, ds, 0, NULL); + if (ds->thread == NULL) + return false; + } + + return true; +} + +static void dsound_clear_buffer(dsound_t *ds) +{ + IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0); + void *ptr; + DWORD size; + + if (IDirectSoundBuffer_Lock(ds->dsb, 0, 0, &ptr, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER) == DS_OK) + { + memset(ptr, 0, size); + IDirectSoundBuffer_Unlock(ds->dsb, ptr, size, NULL, 0); + } +} + +static void dsound_free(void *data) +{ + dsound_t *ds = data; + if (ds) + { + DeleteCriticalSection(&ds->crit); + + if (ds->thread) + { + ds->thread_alive = false; + WaitForSingleObject(ds->thread, INFINITE); + CloseHandle(ds->thread); + } + + if (ds->dsb) + { + IDirectSoundBuffer_Stop(ds->dsb); + IDirectSoundBuffer_Release(ds->dsb); + } + + if (ds) + IDirectSound_Release(ds->ds); + + if (ds->event) + CloseHandle(ds->event); + + if (ds->buffer) + fifo_free(ds->buffer); + + free(ds); + } +} + +static void* dsound_init(const char *device, unsigned rate, unsigned latency) +{ + dsound_t *ds = calloc(1, sizeof(*ds)); + if (!ds) + goto error; + + InitializeCriticalSection(&ds->crit); + + if (DirectSoundCreate(NULL, &ds->ds, NULL) != DS_OK) + goto error; + + if (IDirectSound_SetCooperativeLevel(ds->ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK) + goto error; + + WAVEFORMATEX wfx = { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 2, + .nSamplesPerSec = rate, + .wBitsPerSample = 16, + .nBlockAlign = 2 * sizeof(int16_t), + .nAvgBytesPerSec = rate * 2 * sizeof(int16_t), + }; + + ds->buffer_size = next_pow2((latency * wfx.nAvgBytesPerSec) / 1000); + SSNES_LOG("[DirectSound]: Setting buffer size of %u bytes\n", ds->buffer_size); + + DSBUFFERDESC bufdesc = { + .dwSize = sizeof(DSBUFFERDESC), + .dwFlags = DSBCAPS_GETCURRENTPOSITION2, + .dwBufferBytes = ds->buffer_size, + .lpwfxFormat = &wfx, + }; + + ds->event = CreateEvent(NULL, false, false, NULL); + if (!ds->event) + goto error; + + ds->buffer = fifo_new(4 * 1024); + if (!ds->buffer) + goto error; + + if (IDirectSound_CreateSoundBuffer(ds->ds, &bufdesc, &ds->dsb, 0) != DS_OK) + goto error; + + dsound_clear_buffer(ds); + + if (IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK) + goto error; + + if (!dsound_start_thread(ds)) + goto error; + + return ds; + +error: + dsound_free(ds); + return NULL; +} + +static bool dsound_stop(void *data) +{ + dsound_t *ds = data; + dsound_stop_thread(ds); + return IDirectSoundBuffer_Stop(ds->dsb) == DS_OK; +} + +static bool dsound_start(void *data) +{ + dsound_t *ds = data; + dsound_clear_buffer(ds); + + if (!dsound_start_thread(ds)) + return false; + + return IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) == DS_OK; +} + +static void dsound_set_nonblock_state(void *data, bool state) +{ + dsound_t *ds = data; + ds->nonblock = state; +} + +static ssize_t dsound_write(void *data, const void *buf_, size_t size) +{ + dsound_t *ds = data; + const uint8_t *buf = buf_; + + if (!ds->thread_alive) + return -1; + + size_t written = 0; + while (size > 0) + { + EnterCriticalSection(&ds->crit); + size_t avail = fifo_write_avail(ds->buffer); + if (avail > size) + avail = size; + + fifo_write(ds->buffer, buf, avail); + buf += avail; + size -= avail; + written += avail; + LeaveCriticalSection(&ds->crit); + + if (ds->nonblock || !ds->thread_alive) + break; + + if (avail == 0) + WaitForSingleObject(ds->event, INFINITE); + } + + return written; +} + + +const audio_driver_t audio_dsound = { + .init = dsound_init, + .write = dsound_write, + .stop = dsound_stop, + .start = dsound_start, + .set_nonblock_state = dsound_set_nonblock_state, + .free = dsound_free, + .ident = "dsound" +}; + diff --git a/audio/sdl.c b/audio/sdl.c index b1d9ffa5d1..f853c3b280 100644 --- a/audio/sdl.c +++ b/audio/sdl.c @@ -196,9 +196,4 @@ const audio_driver_t audio_sdl = { .free = sdl_audio_free, .ident = "sdl" }; - - - - - diff --git a/config.def.h b/config.def.h index 1e4502ab9e..f31172c698 100644 --- a/config.def.h +++ b/config.def.h @@ -57,6 +57,7 @@ #define AUDIO_XAUDIO 9 #define AUDIO_PULSE 10 #define AUDIO_EXT 15 +#define AUDIO_DSOUND 16 //////////////////////// #define INPUT_SDL 7 #define INPUT_X 12 @@ -86,6 +87,8 @@ #define AUDIO_DEFAULT_DRIVER AUDIO_AL #elif defined(HAVE_SDL) #define AUDIO_DEFAULT_DRIVER AUDIO_SDL +#elif defined(HAVE_DSOUND) +#define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND #elif defined(HAVE_XAUDIO) #define AUDIO_DEFAULT_DRIVER AUDIO_XAUDIO #elif defined(HAVE_RSOUND) diff --git a/driver.c b/driver.c index 89cbaedc3b..0d9b5ccef1 100644 --- a/driver.c +++ b/driver.c @@ -53,6 +53,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_XAUDIO &audio_xa, #endif +#ifdef HAVE_DSOUND + &audio_dsound, +#endif #ifdef HAVE_PULSE &audio_pulse, #endif diff --git a/driver.h b/driver.h index 09de73952b..30df26393c 100644 --- a/driver.h +++ b/driver.h @@ -158,6 +158,7 @@ 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 audio_driver_t audio_dsound; extern const video_driver_t video_gl; extern const video_driver_t video_xvideo; extern const video_driver_t video_sdl; diff --git a/settings.c b/settings.c index d8ebeadb7c..e8dad3b9d9 100644 --- a/settings.c +++ b/settings.c @@ -77,6 +77,9 @@ static void set_defaults(void) case AUDIO_SDL: def_audio = "sdl"; break; + case AUDIO_DSOUND: + def_audio = "dsound"; + break; case AUDIO_XAUDIO: def_audio = "xaudio"; break;