From 524c5033b89aea31a2a1417c60437e6cb85c4e46 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 4 Aug 2011 01:07:09 -0400 Subject: [PATCH] Implemented XAudio2 target for Windows (and Xbox360, theoretically!). --- VisualC/SDL/SDL_VS2005.vcproj | 8 + VisualC/SDL/SDL_VS2008.vcproj | 8 + VisualC/SDL/SDL_VS2010.vcxproj | 2 + configure.in | 9 + include/SDL_config.h.in | 1 + include/SDL_config_windows.h | 1 + src/audio/SDL_audio.c | 4 + src/audio/xaudio2/SDL_xaudio2.c | 455 ++++++++++++++++++++++++++++++++ src/audio/xaudio2/SDL_xaudio2.h | 46 ++++ 9 files changed, 534 insertions(+) create mode 100644 src/audio/xaudio2/SDL_xaudio2.c create mode 100644 src/audio/xaudio2/SDL_xaudio2.h diff --git a/VisualC/SDL/SDL_VS2005.vcproj b/VisualC/SDL/SDL_VS2005.vcproj index 1de6dc174..ec7921d2b 100644 --- a/VisualC/SDL/SDL_VS2005.vcproj +++ b/VisualC/SDL/SDL_VS2005.vcproj @@ -847,6 +847,14 @@ RelativePath="..\..\src\audio\directsound\SDL_directsound.h" > + + + + diff --git a/VisualC/SDL/SDL_VS2008.vcproj b/VisualC/SDL/SDL_VS2008.vcproj index 028d1132f..49c0d4701 100644 --- a/VisualC/SDL/SDL_VS2008.vcproj +++ b/VisualC/SDL/SDL_VS2008.vcproj @@ -836,6 +836,14 @@ RelativePath="..\..\src\audio\directsound\SDL_directsound.h" > + + + + diff --git a/VisualC/SDL/SDL_VS2010.vcxproj b/VisualC/SDL/SDL_VS2010.vcxproj index 061e06d54..33bba4d54 100644 --- a/VisualC/SDL/SDL_VS2010.vcxproj +++ b/VisualC/SDL/SDL_VS2010.vcxproj @@ -280,6 +280,7 @@ + @@ -381,6 +382,7 @@ + diff --git a/configure.in b/configure.in index dcd39e6b5..845d1d296 100644 --- a/configure.in +++ b/configure.in @@ -1726,6 +1726,7 @@ AC_HELP_STRING([--enable-directx], [use DirectX for Windows audio/video [[defaul AC_CHECK_HEADER(ddraw.h, have_ddraw=yes) AC_CHECK_HEADER(dsound.h, have_dsound=yes) AC_CHECK_HEADER(dinput.h, have_dinput=yes) + AC_CHECK_HEADER(xaudio2.h, have_xaudio2=yes) fi } @@ -2063,6 +2064,10 @@ AC_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau AC_DEFINE(SDL_AUDIO_DRIVER_DSOUND, 1, [ ]) SOURCES="$SOURCES $srcdir/src/audio/directsound/*.c" fi + if test x$have_xaudio2 = xyes; then + AC_DEFINE(SDL_AUDIO_DRIVER_XAUDIO2, 1, [ ]) + SOURCES="$SOURCES $srcdir/src/audio/xaudio2/*.c" + fi have_audio=yes fi # Set up dummy files for the joystick for now @@ -2150,6 +2155,10 @@ AC_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau AC_DEFINE(SDL_AUDIO_DRIVER_DSOUND, 1, [ ]) SOURCES="$SOURCES $srcdir/src/audio/directsound/*.c" fi + if test x$have_xaudio2 = xyes; then + AC_DEFINE(SDL_AUDIO_DRIVER_XAUDIO2, 1, [ ]) + SOURCES="$SOURCES $srcdir/src/audio/xaudio2/*.c" + fi have_audio=yes fi # Set up files for the joystick library diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index 84d921006..14167fdd3 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -184,6 +184,7 @@ #undef SDL_AUDIO_DRIVER_COREAUDIO #undef SDL_AUDIO_DRIVER_DISK #undef SDL_AUDIO_DRIVER_DUMMY +#undef SDL_AUDIO_DRIVER_XAUDIO2 #undef SDL_AUDIO_DRIVER_DSOUND #undef SDL_AUDIO_DRIVER_ESD #undef SDL_AUDIO_DRIVER_ESD_DYNAMIC diff --git a/include/SDL_config_windows.h b/include/SDL_config_windows.h index 6b9295150..b980f0d1a 100644 --- a/include/SDL_config_windows.h +++ b/include/SDL_config_windows.h @@ -145,6 +145,7 @@ typedef unsigned int uintptr_t; /* Enable various audio drivers */ #ifndef _WIN32_WCE #define SDL_AUDIO_DRIVER_DSOUND 1 +#define SDL_AUDIO_DRIVER_XAUDIO2 1 #endif #define SDL_AUDIO_DRIVER_WINMM 1 #define SDL_AUDIO_DRIVER_DISK 1 diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 7f16a6549..2df703486 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -52,6 +52,7 @@ extern AudioBootStrap SUNAUDIO_bootstrap; extern AudioBootStrap ARTS_bootstrap; extern AudioBootStrap ESD_bootstrap; extern AudioBootStrap NAS_bootstrap; +extern AudioBootStrap XAUDIO2_bootstrap; extern AudioBootStrap DSOUND_bootstrap; extern AudioBootStrap WINMM_bootstrap; extern AudioBootStrap PAUDIO_bootstrap; @@ -97,6 +98,9 @@ static const AudioBootStrap *const bootstrap[] = { #if SDL_AUDIO_DRIVER_NAS &NAS_bootstrap, #endif +#if SDL_AUDIO_DRIVER_XAUDIO2 + &XAUDIO2_bootstrap, +#endif #if SDL_AUDIO_DRIVER_DSOUND &DSOUND_bootstrap, #endif diff --git a/src/audio/xaudio2/SDL_xaudio2.c b/src/audio/xaudio2/SDL_xaudio2.c new file mode 100644 index 000000000..4c769474a --- /dev/null +++ b/src/audio/xaudio2/SDL_xaudio2.c @@ -0,0 +1,455 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2011 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_config.h" +#include "../../core/windows/SDL_windows.h" +#include "SDL_audio.h" +#include "../SDL_audio_c.h" +#include "SDL_assert.h" + +#define INITGUID 1 +#include "SDL_xaudio2.h" + +/* !!! FIXME: this is a cut and paste of SDL_FreeUnixAudioDevices(), + * !!! FIXME: which is more proof this needs to be managed in SDL_audio.c + * !!! FIXME: and not in drivers. + */ +static void +FreeXAudio2AudioDevices(char ***devices, int *devCount) +{ + int i = *devCount; + if ((i > 0) && (*devices != NULL)) { + while (i--) { + SDL_free((*devices)[i]); + } + } + + if (*devices != NULL) { + SDL_free(*devices); + } + + *devices = NULL; + *devCount = 0; +} + + +static char **outputDevices = NULL; +static int outputDeviceCount = 0; + +static __inline__ char * +utf16_to_utf8(const WCHAR *S) +{ + /* !!! FIXME: this should be UTF-16, not UCS-2! */ + return SDL_iconv_string("UTF-8", "UCS-2", (char *)(S), + (SDL_wcslen(S)+1)*sizeof(WCHAR)); +} + +static int +XAUDIO2_DetectDevices(int iscapture) +{ + IXAudio2 *ixa2 = NULL; + UINT32 devcount = 0; + UINT32 i = 0; + void *ptr = NULL; + + if (!iscapture) { + FreeXAudio2AudioDevices(&outputDevices, &outputDeviceCount); + } + + if (iscapture) { + SDL_SetError("XAudio2: capture devices unsupported."); + return 0; + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { + SDL_SetError("XAudio2: XAudio2Create() failed."); + return 0; + } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { + SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed."); + IXAudio2_Release(ixa2); + return 0; + } else if ((ptr = SDL_malloc(sizeof (char *) * devcount)) == NULL) { + SDL_OutOfMemory(); + IXAudio2_Release(ixa2); + return 0; + } + + outputDevices = (char **) ptr; + for (i = 0; i < devcount; i++) { + XAUDIO2_DEVICE_DETAILS details; + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { + char *str = utf16_to_utf8(details.DisplayName); + if (str != NULL) { + outputDevices[outputDeviceCount++] = str; + } + } + } + + IXAudio2_Release(ixa2); + + return outputDeviceCount; +} + +static const char * +XAUDIO2_GetDeviceName(int index, int iscapture) +{ + if ((!iscapture) && (index < outputDeviceCount)) { + return outputDevices[index]; + } + + SDL_SetError("XAudio2: No such device"); + return NULL; +} + +static void STDMETHODCALLTYPE +VoiceCBOnBufferEnd(THIS_ void *data) +{ + /* Just signal the SDL audio thread and get out of XAudio2's way. */ + SDL_AudioDevice *this = (SDL_AudioDevice *) data; + ReleaseSemaphore(this->hidden->semaphore, 1, NULL); +} + +static void STDMETHODCALLTYPE +VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error) +{ + /* !!! FIXME: attempt to recover, or mark device disconnected. */ + SDL_assert(0 && "write me!"); +} + +/* no-op callbacks... */ +static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {} +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {} +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {} +static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {} +static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {} + + +static Uint8 * +XAUDIO2_GetDeviceBuf(_THIS) +{ + return this->hidden->nextbuf; +} + +static void +XAUDIO2_PlayDevice(_THIS) +{ + XAUDIO2_BUFFER buffer; + Uint8 *mixbuf = this->hidden->mixbuf; + Uint8 *nextbuf = this->hidden->nextbuf; + const int mixlen = this->hidden->mixlen; + IXAudio2SourceVoice *source = this->hidden->source; + HRESULT result = S_OK; + + if (!this->enabled) { /* shutting down? */ + return; + } + + /* Submit the next filled buffer */ + SDL_zero(buffer); + buffer.AudioBytes = mixlen; + buffer.pAudioData = nextbuf; + buffer.pContext = this; + + if (nextbuf == mixbuf) { + nextbuf += mixlen; + } else { + nextbuf = mixbuf; + } + this->hidden->nextbuf = nextbuf; + + result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL); + if (result == XAUDIO2_E_DEVICE_INVALIDATED) { + /* !!! FIXME: possibly disconnected or temporary lost. Recover? */ + } + + if (result != S_OK) { /* uhoh, panic! */ + IXAudio2SourceVoice_FlushSourceBuffers(source); + this->enabled = 0; + } +} + +static void +XAUDIO2_WaitDevice(_THIS) +{ + if (this->enabled) { + WaitForSingleObject(this->hidden->semaphore, INFINITE); + } +} + +static void +XAUDIO2_WaitDone(_THIS) +{ + IXAudio2SourceVoice *source = this->hidden->source; + XAUDIO2_VOICE_STATE state; + SDL_assert(!this->enabled); /* flag that stops playing. */ + IXAudio2SourceVoice_Discontinuity(source); + IXAudio2SourceVoice_GetState(source, &state); + while (state.BuffersQueued > 0) { + WaitForSingleObject(this->hidden->semaphore, INFINITE); + IXAudio2SourceVoice_GetState(source, &state); + } +} + + +static void +XAUDIO2_CloseDevice(_THIS) +{ + if (this->hidden != NULL) { + IXAudio2 *ixa2 = this->hidden->ixa2; + IXAudio2SourceVoice *source = this->hidden->source; + IXAudio2MasteringVoice *mastering = this->hidden->mastering; + + if (source != NULL) { + IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW); + IXAudio2SourceVoice_FlushSourceBuffers(source); + IXAudio2SourceVoice_DestroyVoice(source); + } + if (ixa2 != NULL) { + IXAudio2_StopEngine(ixa2); + } + if (mastering != NULL) { + IXAudio2MasteringVoice_DestroyVoice(mastering); + } + if (ixa2 != NULL) { + IXAudio2_Release(ixa2); + } + if (this->hidden->mixbuf != NULL) { + SDL_free(this->hidden->mixbuf); + } + if (this->hidden->semaphore != NULL) { + CloseHandle(this->hidden->semaphore); + } + + SDL_free(this->hidden); + this->hidden = NULL; + } +} + +static int +XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture) +{ + HRESULT result = S_OK; + WAVEFORMATEX waveformat; + int valid_format = 0; + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + IXAudio2 *ixa2 = NULL; + IXAudio2SourceVoice *source = NULL; + UINT32 devId = 0; /* 0 == system default device. */ + + static IXAudio2VoiceCallbackVtbl callbacks_vtable = { + VoiceCBOnVoiceProcessPassStart, + VoiceCBOnVoiceProcessPassEnd, + VoiceCBOnStreamEnd, + VoiceCBOnBufferStart, + VoiceCBOnBufferEnd, + VoiceCBOnLoopEnd, + VoiceCBOnVoiceError + }; + + static IXAudio2VoiceCallback callbacks = { &callbacks_vtable }; + + if (iscapture) { + SDL_SetError("XAudio2: capture devices unsupported."); + return 0; + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { + SDL_SetError("XAudio2: XAudio2Create() failed."); + return 0; + } + + if (devname != NULL) { + UINT32 devcount = 0; + UINT32 i = 0; + + if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { + IXAudio2_Release(ixa2); + SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed."); + return 0; + } + for (i = 0; i < devcount; i++) { + XAUDIO2_DEVICE_DETAILS details; + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { + char *str = utf16_to_utf8(details.DisplayName); + if (str != NULL) { + const int match = (SDL_strcmp(str, devname) == 0); + SDL_free(str); + if (match) { + devId = i; + break; + } + } + } + } + + if (i == devcount) { + IXAudio2_Release(ixa2); + SDL_SetError("XAudio2: Requested device not found."); + return 0; + } + } + + /* Initialize all variables that we clean on shutdown */ + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *this->hidden)); + if (this->hidden == NULL) { + IXAudio2_Release(ixa2); + SDL_OutOfMemory(); + return 0; + } + SDL_memset(this->hidden, 0, (sizeof *this->hidden)); + + this->hidden->ixa2 = ixa2; + this->hidden->semaphore = CreateSemaphore(NULL, 1, 2, NULL); + if (this->hidden->semaphore == NULL) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: CreateSemaphore() failed!"); + return 0; + } + + while ((!valid_format) && (test_format)) { + switch (test_format) { + case AUDIO_U8: + case AUDIO_S16: + case AUDIO_S32: + case AUDIO_F32: + this->spec.format = test_format; + valid_format = 1; + break; + } + test_format = SDL_NextAudioFormat(); + } + + if (!valid_format) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: Unsupported audio format"); + return 0; + } + + /* Update the fragment size as size in bytes */ + SDL_CalculateAudioSpec(&this->spec); + + /* We feed a Source, it feeds the Mastering, which feeds the device. */ + this->hidden->mixlen = this->spec.size; + this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen); + if (this->hidden->mixbuf == NULL) { + XAUDIO2_CloseDevice(this); + SDL_OutOfMemory(); + return 0; + } + this->hidden->nextbuf = this->hidden->mixbuf; + SDL_memset(this->hidden->mixbuf, '\0', 2 * this->hidden->mixlen); + + /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On + Xbox360, this means 5.1 output, but on Windows, it means "figure out + what the system has." It might be preferable to let XAudio2 blast + stereo output to appropriate surround sound configurations + instead of clamping to 2 channels, even though we'll configure the + Source Voice for whatever number of channels you supply. */ + result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering, + XAUDIO2_DEFAULT_CHANNELS, + this->spec.freq, 0, devId, NULL); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: Couldn't create mastering voice"); + return 0; + } + + SDL_zero(waveformat); + if (SDL_AUDIO_ISFLOAT(this->spec.format)) { + waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } else { + waveformat.wFormatTag = WAVE_FORMAT_PCM; + } + waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); + waveformat.nChannels = this->spec.channels; + waveformat.nSamplesPerSec = this->spec.freq; + waveformat.nBlockAlign = + waveformat.nChannels * (waveformat.wBitsPerSample / 8); + waveformat.nAvgBytesPerSec = + waveformat.nSamplesPerSec * waveformat.nBlockAlign; + + result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat, + XAUDIO2_VOICE_NOSRC | + XAUDIO2_VOICE_NOPITCH, + 1.0f, &callbacks, NULL, NULL); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: Couldn't create source voice"); + return 0; + } + this->hidden->source = source; + + /* Start everything playing! */ + result = IXAudio2_StartEngine(ixa2); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: Couldn't start engine"); + return 0; + } + + result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + SDL_SetError("XAudio2: Couldn't start source voice"); + return 0; + } + + return 1; /* good to go. */ +} + +static void +XAUDIO2_Deinitialize(void) +{ + WIN_CoUninitialize(); +} + +static int +XAUDIO2_Init(SDL_AudioDriverImpl * impl) +{ + /* XAudio2Create() is a macro that uses COM; we don't load the .dll */ + IXAudio2 *ixa2 = NULL; + if (FAILED(WIN_CoInitialize())) { + SDL_SetError("XAudio2: CoInitialize() failed"); + return 0; + } + + if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { + WIN_CoUninitialize(); + SDL_SetError("XAudio2: XAudio2Create() failed"); + return 0; /* not available. */ + } + IXAudio2_Release(ixa2); + + /* Set the function pointers */ + impl->DetectDevices = XAUDIO2_DetectDevices; + impl->GetDeviceName = XAUDIO2_GetDeviceName; + impl->OpenDevice = XAUDIO2_OpenDevice; + impl->PlayDevice = XAUDIO2_PlayDevice; + impl->WaitDevice = XAUDIO2_WaitDevice; + impl->WaitDone = XAUDIO2_WaitDone; + impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf; + impl->CloseDevice = XAUDIO2_CloseDevice; + impl->Deinitialize = XAUDIO2_Deinitialize; + + return 1; /* this audio target is available. */ +} + +AudioBootStrap XAUDIO2_bootstrap = { + "xaudio2", "XAudio2", XAUDIO2_Init, 0 +}; + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/audio/xaudio2/SDL_xaudio2.h b/src/audio/xaudio2/SDL_xaudio2.h new file mode 100644 index 000000000..1794ae179 --- /dev/null +++ b/src/audio/xaudio2/SDL_xaudio2.h @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2011 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_config.h" + +#ifndef _SDL_xaudio2_h +#define _SDL_xaudio2_h + +#include "../SDL_sysaudio.h" + +#include + +/* Hidden "this" pointer for the audio functions */ +#define _THIS SDL_AudioDevice *this + +struct SDL_PrivateAudioData +{ + IXAudio2 *ixa2; + IXAudio2SourceVoice *source; + IXAudio2MasteringVoice *mastering; + HANDLE semaphore; + Uint8 *mixbuf; + int mixlen; + Uint8 *nextbuf; +}; + +#endif /* _SDL_xaudio2_h */ + +/* vi: set ts=4 sw=4 expandtab: */