1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/audio/xaudio2/SDL_xaudio2.c Thu Aug 04 01:07:09 2011 -0400
1.3 @@ -0,0 +1,455 @@
1.4 +/*
1.5 + Simple DirectMedia Layer
1.6 + Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
1.7 +
1.8 + This software is provided 'as-is', without any express or implied
1.9 + warranty. In no event will the authors be held liable for any damages
1.10 + arising from the use of this software.
1.11 +
1.12 + Permission is granted to anyone to use this software for any purpose,
1.13 + including commercial applications, and to alter it and redistribute it
1.14 + freely, subject to the following restrictions:
1.15 +
1.16 + 1. The origin of this software must not be misrepresented; you must not
1.17 + claim that you wrote the original software. If you use this software
1.18 + in a product, an acknowledgment in the product documentation would be
1.19 + appreciated but is not required.
1.20 + 2. Altered source versions must be plainly marked as such, and must not be
1.21 + misrepresented as being the original software.
1.22 + 3. This notice may not be removed or altered from any source distribution.
1.23 +*/
1.24 +#include "SDL_config.h"
1.25 +#include "../../core/windows/SDL_windows.h"
1.26 +#include "SDL_audio.h"
1.27 +#include "../SDL_audio_c.h"
1.28 +#include "SDL_assert.h"
1.29 +
1.30 +#define INITGUID 1
1.31 +#include "SDL_xaudio2.h"
1.32 +
1.33 +/* !!! FIXME: this is a cut and paste of SDL_FreeUnixAudioDevices(),
1.34 + * !!! FIXME: which is more proof this needs to be managed in SDL_audio.c
1.35 + * !!! FIXME: and not in drivers.
1.36 + */
1.37 +static void
1.38 +FreeXAudio2AudioDevices(char ***devices, int *devCount)
1.39 +{
1.40 + int i = *devCount;
1.41 + if ((i > 0) && (*devices != NULL)) {
1.42 + while (i--) {
1.43 + SDL_free((*devices)[i]);
1.44 + }
1.45 + }
1.46 +
1.47 + if (*devices != NULL) {
1.48 + SDL_free(*devices);
1.49 + }
1.50 +
1.51 + *devices = NULL;
1.52 + *devCount = 0;
1.53 +}
1.54 +
1.55 +
1.56 +static char **outputDevices = NULL;
1.57 +static int outputDeviceCount = 0;
1.58 +
1.59 +static __inline__ char *
1.60 +utf16_to_utf8(const WCHAR *S)
1.61 +{
1.62 + /* !!! FIXME: this should be UTF-16, not UCS-2! */
1.63 + return SDL_iconv_string("UTF-8", "UCS-2", (char *)(S),
1.64 + (SDL_wcslen(S)+1)*sizeof(WCHAR));
1.65 +}
1.66 +
1.67 +static int
1.68 +XAUDIO2_DetectDevices(int iscapture)
1.69 +{
1.70 + IXAudio2 *ixa2 = NULL;
1.71 + UINT32 devcount = 0;
1.72 + UINT32 i = 0;
1.73 + void *ptr = NULL;
1.74 +
1.75 + if (!iscapture) {
1.76 + FreeXAudio2AudioDevices(&outputDevices, &outputDeviceCount);
1.77 + }
1.78 +
1.79 + if (iscapture) {
1.80 + SDL_SetError("XAudio2: capture devices unsupported.");
1.81 + return 0;
1.82 + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
1.83 + SDL_SetError("XAudio2: XAudio2Create() failed.");
1.84 + return 0;
1.85 + } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
1.86 + SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
1.87 + IXAudio2_Release(ixa2);
1.88 + return 0;
1.89 + } else if ((ptr = SDL_malloc(sizeof (char *) * devcount)) == NULL) {
1.90 + SDL_OutOfMemory();
1.91 + IXAudio2_Release(ixa2);
1.92 + return 0;
1.93 + }
1.94 +
1.95 + outputDevices = (char **) ptr;
1.96 + for (i = 0; i < devcount; i++) {
1.97 + XAUDIO2_DEVICE_DETAILS details;
1.98 + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
1.99 + char *str = utf16_to_utf8(details.DisplayName);
1.100 + if (str != NULL) {
1.101 + outputDevices[outputDeviceCount++] = str;
1.102 + }
1.103 + }
1.104 + }
1.105 +
1.106 + IXAudio2_Release(ixa2);
1.107 +
1.108 + return outputDeviceCount;
1.109 +}
1.110 +
1.111 +static const char *
1.112 +XAUDIO2_GetDeviceName(int index, int iscapture)
1.113 +{
1.114 + if ((!iscapture) && (index < outputDeviceCount)) {
1.115 + return outputDevices[index];
1.116 + }
1.117 +
1.118 + SDL_SetError("XAudio2: No such device");
1.119 + return NULL;
1.120 +}
1.121 +
1.122 +static void STDMETHODCALLTYPE
1.123 +VoiceCBOnBufferEnd(THIS_ void *data)
1.124 +{
1.125 + /* Just signal the SDL audio thread and get out of XAudio2's way. */
1.126 + SDL_AudioDevice *this = (SDL_AudioDevice *) data;
1.127 + ReleaseSemaphore(this->hidden->semaphore, 1, NULL);
1.128 +}
1.129 +
1.130 +static void STDMETHODCALLTYPE
1.131 +VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
1.132 +{
1.133 + /* !!! FIXME: attempt to recover, or mark device disconnected. */
1.134 + SDL_assert(0 && "write me!");
1.135 +}
1.136 +
1.137 +/* no-op callbacks... */
1.138 +static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
1.139 +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
1.140 +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
1.141 +static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
1.142 +static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
1.143 +
1.144 +
1.145 +static Uint8 *
1.146 +XAUDIO2_GetDeviceBuf(_THIS)
1.147 +{
1.148 + return this->hidden->nextbuf;
1.149 +}
1.150 +
1.151 +static void
1.152 +XAUDIO2_PlayDevice(_THIS)
1.153 +{
1.154 + XAUDIO2_BUFFER buffer;
1.155 + Uint8 *mixbuf = this->hidden->mixbuf;
1.156 + Uint8 *nextbuf = this->hidden->nextbuf;
1.157 + const int mixlen = this->hidden->mixlen;
1.158 + IXAudio2SourceVoice *source = this->hidden->source;
1.159 + HRESULT result = S_OK;
1.160 +
1.161 + if (!this->enabled) { /* shutting down? */
1.162 + return;
1.163 + }
1.164 +
1.165 + /* Submit the next filled buffer */
1.166 + SDL_zero(buffer);
1.167 + buffer.AudioBytes = mixlen;
1.168 + buffer.pAudioData = nextbuf;
1.169 + buffer.pContext = this;
1.170 +
1.171 + if (nextbuf == mixbuf) {
1.172 + nextbuf += mixlen;
1.173 + } else {
1.174 + nextbuf = mixbuf;
1.175 + }
1.176 + this->hidden->nextbuf = nextbuf;
1.177 +
1.178 + result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
1.179 + if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
1.180 + /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
1.181 + }
1.182 +
1.183 + if (result != S_OK) { /* uhoh, panic! */
1.184 + IXAudio2SourceVoice_FlushSourceBuffers(source);
1.185 + this->enabled = 0;
1.186 + }
1.187 +}
1.188 +
1.189 +static void
1.190 +XAUDIO2_WaitDevice(_THIS)
1.191 +{
1.192 + if (this->enabled) {
1.193 + WaitForSingleObject(this->hidden->semaphore, INFINITE);
1.194 + }
1.195 +}
1.196 +
1.197 +static void
1.198 +XAUDIO2_WaitDone(_THIS)
1.199 +{
1.200 + IXAudio2SourceVoice *source = this->hidden->source;
1.201 + XAUDIO2_VOICE_STATE state;
1.202 + SDL_assert(!this->enabled); /* flag that stops playing. */
1.203 + IXAudio2SourceVoice_Discontinuity(source);
1.204 + IXAudio2SourceVoice_GetState(source, &state);
1.205 + while (state.BuffersQueued > 0) {
1.206 + WaitForSingleObject(this->hidden->semaphore, INFINITE);
1.207 + IXAudio2SourceVoice_GetState(source, &state);
1.208 + }
1.209 +}
1.210 +
1.211 +
1.212 +static void
1.213 +XAUDIO2_CloseDevice(_THIS)
1.214 +{
1.215 + if (this->hidden != NULL) {
1.216 + IXAudio2 *ixa2 = this->hidden->ixa2;
1.217 + IXAudio2SourceVoice *source = this->hidden->source;
1.218 + IXAudio2MasteringVoice *mastering = this->hidden->mastering;
1.219 +
1.220 + if (source != NULL) {
1.221 + IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
1.222 + IXAudio2SourceVoice_FlushSourceBuffers(source);
1.223 + IXAudio2SourceVoice_DestroyVoice(source);
1.224 + }
1.225 + if (ixa2 != NULL) {
1.226 + IXAudio2_StopEngine(ixa2);
1.227 + }
1.228 + if (mastering != NULL) {
1.229 + IXAudio2MasteringVoice_DestroyVoice(mastering);
1.230 + }
1.231 + if (ixa2 != NULL) {
1.232 + IXAudio2_Release(ixa2);
1.233 + }
1.234 + if (this->hidden->mixbuf != NULL) {
1.235 + SDL_free(this->hidden->mixbuf);
1.236 + }
1.237 + if (this->hidden->semaphore != NULL) {
1.238 + CloseHandle(this->hidden->semaphore);
1.239 + }
1.240 +
1.241 + SDL_free(this->hidden);
1.242 + this->hidden = NULL;
1.243 + }
1.244 +}
1.245 +
1.246 +static int
1.247 +XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture)
1.248 +{
1.249 + HRESULT result = S_OK;
1.250 + WAVEFORMATEX waveformat;
1.251 + int valid_format = 0;
1.252 + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
1.253 + IXAudio2 *ixa2 = NULL;
1.254 + IXAudio2SourceVoice *source = NULL;
1.255 + UINT32 devId = 0; /* 0 == system default device. */
1.256 +
1.257 + static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
1.258 + VoiceCBOnVoiceProcessPassStart,
1.259 + VoiceCBOnVoiceProcessPassEnd,
1.260 + VoiceCBOnStreamEnd,
1.261 + VoiceCBOnBufferStart,
1.262 + VoiceCBOnBufferEnd,
1.263 + VoiceCBOnLoopEnd,
1.264 + VoiceCBOnVoiceError
1.265 + };
1.266 +
1.267 + static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
1.268 +
1.269 + if (iscapture) {
1.270 + SDL_SetError("XAudio2: capture devices unsupported.");
1.271 + return 0;
1.272 + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
1.273 + SDL_SetError("XAudio2: XAudio2Create() failed.");
1.274 + return 0;
1.275 + }
1.276 +
1.277 + if (devname != NULL) {
1.278 + UINT32 devcount = 0;
1.279 + UINT32 i = 0;
1.280 +
1.281 + if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
1.282 + IXAudio2_Release(ixa2);
1.283 + SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed.");
1.284 + return 0;
1.285 + }
1.286 + for (i = 0; i < devcount; i++) {
1.287 + XAUDIO2_DEVICE_DETAILS details;
1.288 + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
1.289 + char *str = utf16_to_utf8(details.DisplayName);
1.290 + if (str != NULL) {
1.291 + const int match = (SDL_strcmp(str, devname) == 0);
1.292 + SDL_free(str);
1.293 + if (match) {
1.294 + devId = i;
1.295 + break;
1.296 + }
1.297 + }
1.298 + }
1.299 + }
1.300 +
1.301 + if (i == devcount) {
1.302 + IXAudio2_Release(ixa2);
1.303 + SDL_SetError("XAudio2: Requested device not found.");
1.304 + return 0;
1.305 + }
1.306 + }
1.307 +
1.308 + /* Initialize all variables that we clean on shutdown */
1.309 + this->hidden = (struct SDL_PrivateAudioData *)
1.310 + SDL_malloc((sizeof *this->hidden));
1.311 + if (this->hidden == NULL) {
1.312 + IXAudio2_Release(ixa2);
1.313 + SDL_OutOfMemory();
1.314 + return 0;
1.315 + }
1.316 + SDL_memset(this->hidden, 0, (sizeof *this->hidden));
1.317 +
1.318 + this->hidden->ixa2 = ixa2;
1.319 + this->hidden->semaphore = CreateSemaphore(NULL, 1, 2, NULL);
1.320 + if (this->hidden->semaphore == NULL) {
1.321 + XAUDIO2_CloseDevice(this);
1.322 + SDL_SetError("XAudio2: CreateSemaphore() failed!");
1.323 + return 0;
1.324 + }
1.325 +
1.326 + while ((!valid_format) && (test_format)) {
1.327 + switch (test_format) {
1.328 + case AUDIO_U8:
1.329 + case AUDIO_S16:
1.330 + case AUDIO_S32:
1.331 + case AUDIO_F32:
1.332 + this->spec.format = test_format;
1.333 + valid_format = 1;
1.334 + break;
1.335 + }
1.336 + test_format = SDL_NextAudioFormat();
1.337 + }
1.338 +
1.339 + if (!valid_format) {
1.340 + XAUDIO2_CloseDevice(this);
1.341 + SDL_SetError("XAudio2: Unsupported audio format");
1.342 + return 0;
1.343 + }
1.344 +
1.345 + /* Update the fragment size as size in bytes */
1.346 + SDL_CalculateAudioSpec(&this->spec);
1.347 +
1.348 + /* We feed a Source, it feeds the Mastering, which feeds the device. */
1.349 + this->hidden->mixlen = this->spec.size;
1.350 + this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
1.351 + if (this->hidden->mixbuf == NULL) {
1.352 + XAUDIO2_CloseDevice(this);
1.353 + SDL_OutOfMemory();
1.354 + return 0;
1.355 + }
1.356 + this->hidden->nextbuf = this->hidden->mixbuf;
1.357 + SDL_memset(this->hidden->mixbuf, '\0', 2 * this->hidden->mixlen);
1.358 +
1.359 + /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
1.360 + Xbox360, this means 5.1 output, but on Windows, it means "figure out
1.361 + what the system has." It might be preferable to let XAudio2 blast
1.362 + stereo output to appropriate surround sound configurations
1.363 + instead of clamping to 2 channels, even though we'll configure the
1.364 + Source Voice for whatever number of channels you supply. */
1.365 + result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
1.366 + XAUDIO2_DEFAULT_CHANNELS,
1.367 + this->spec.freq, 0, devId, NULL);
1.368 + if (result != S_OK) {
1.369 + XAUDIO2_CloseDevice(this);
1.370 + SDL_SetError("XAudio2: Couldn't create mastering voice");
1.371 + return 0;
1.372 + }
1.373 +
1.374 + SDL_zero(waveformat);
1.375 + if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
1.376 + waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
1.377 + } else {
1.378 + waveformat.wFormatTag = WAVE_FORMAT_PCM;
1.379 + }
1.380 + waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
1.381 + waveformat.nChannels = this->spec.channels;
1.382 + waveformat.nSamplesPerSec = this->spec.freq;
1.383 + waveformat.nBlockAlign =
1.384 + waveformat.nChannels * (waveformat.wBitsPerSample / 8);
1.385 + waveformat.nAvgBytesPerSec =
1.386 + waveformat.nSamplesPerSec * waveformat.nBlockAlign;
1.387 +
1.388 + result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
1.389 + XAUDIO2_VOICE_NOSRC |
1.390 + XAUDIO2_VOICE_NOPITCH,
1.391 + 1.0f, &callbacks, NULL, NULL);
1.392 + if (result != S_OK) {
1.393 + XAUDIO2_CloseDevice(this);
1.394 + SDL_SetError("XAudio2: Couldn't create source voice");
1.395 + return 0;
1.396 + }
1.397 + this->hidden->source = source;
1.398 +
1.399 + /* Start everything playing! */
1.400 + result = IXAudio2_StartEngine(ixa2);
1.401 + if (result != S_OK) {
1.402 + XAUDIO2_CloseDevice(this);
1.403 + SDL_SetError("XAudio2: Couldn't start engine");
1.404 + return 0;
1.405 + }
1.406 +
1.407 + result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
1.408 + if (result != S_OK) {
1.409 + XAUDIO2_CloseDevice(this);
1.410 + SDL_SetError("XAudio2: Couldn't start source voice");
1.411 + return 0;
1.412 + }
1.413 +
1.414 + return 1; /* good to go. */
1.415 +}
1.416 +
1.417 +static void
1.418 +XAUDIO2_Deinitialize(void)
1.419 +{
1.420 + WIN_CoUninitialize();
1.421 +}
1.422 +
1.423 +static int
1.424 +XAUDIO2_Init(SDL_AudioDriverImpl * impl)
1.425 +{
1.426 + /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
1.427 + IXAudio2 *ixa2 = NULL;
1.428 + if (FAILED(WIN_CoInitialize())) {
1.429 + SDL_SetError("XAudio2: CoInitialize() failed");
1.430 + return 0;
1.431 + }
1.432 +
1.433 + if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
1.434 + WIN_CoUninitialize();
1.435 + SDL_SetError("XAudio2: XAudio2Create() failed");
1.436 + return 0; /* not available. */
1.437 + }
1.438 + IXAudio2_Release(ixa2);
1.439 +
1.440 + /* Set the function pointers */
1.441 + impl->DetectDevices = XAUDIO2_DetectDevices;
1.442 + impl->GetDeviceName = XAUDIO2_GetDeviceName;
1.443 + impl->OpenDevice = XAUDIO2_OpenDevice;
1.444 + impl->PlayDevice = XAUDIO2_PlayDevice;
1.445 + impl->WaitDevice = XAUDIO2_WaitDevice;
1.446 + impl->WaitDone = XAUDIO2_WaitDone;
1.447 + impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
1.448 + impl->CloseDevice = XAUDIO2_CloseDevice;
1.449 + impl->Deinitialize = XAUDIO2_Deinitialize;
1.450 +
1.451 + return 1; /* this audio target is available. */
1.452 +}
1.453 +
1.454 +AudioBootStrap XAUDIO2_bootstrap = {
1.455 + "xaudio2", "XAudio2", XAUDIO2_Init, 0
1.456 +};
1.457 +
1.458 +/* vi: set ts=4 sw=4 expandtab: */