SDL_xaudio2.c (17584B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22/* WinRT NOTICE: 23 24 A few changes to SDL's XAudio2 backend were warranted by API 25 changes to Windows. Many, but not all of these are documented by Microsoft 26 at: 27 http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx 28 29 1. Windows' thread synchronization function, CreateSemaphore, was removed 30 from WinRT. SDL's semaphore API was substituted instead. 31 2. The method calls, IXAudio2::GetDeviceCount and IXAudio2::GetDeviceDetails 32 were removed from the XAudio2 API. Microsoft is telling developers to 33 use APIs in Windows::Foundation instead. 34 For SDL, the missing methods were reimplemented using the APIs Microsoft 35 said to use. 36 3. CoInitialize and CoUninitialize are not available in WinRT. 37 These calls were removed, as COM will have been initialized earlier, 38 at least by the call to the WinRT app's main function 39 (aka 'int main(Platform::Array<Platform::String^>^)). (DLudwig: 40 This was my understanding of how WinRT: the 'main' function uses 41 a tag of [MTAThread], which should initialize COM. My understanding 42 of COM is somewhat limited, and I may be incorrect here.) 43 4. IXAudio2::CreateMasteringVoice changed its integer-based 'DeviceIndex' 44 argument to a string-based one, 'szDeviceId'. In WinRT, the 45 string-based argument will be used. 46*/ 47#include "../../SDL_internal.h" 48 49#if SDL_AUDIO_DRIVER_XAUDIO2 50 51#include "../../core/windows/SDL_windows.h" 52#include "SDL_audio.h" 53#include "../SDL_audio_c.h" 54#include "../SDL_sysaudio.h" 55#include "SDL_assert.h" 56 57#ifdef __GNUC__ 58/* The configure script already did any necessary checking */ 59# define SDL_XAUDIO2_HAS_SDK 1 60#elif defined(__WINRT__) 61/* WinRT always has access to the XAudio 2 SDK */ 62# define SDL_XAUDIO2_HAS_SDK 63#else 64/* XAudio2 exists as of the March 2008 DirectX SDK 65 The XAudio2 implementation available in the Windows 8 SDK targets Windows 8 and newer. 66 If you want to build SDL with XAudio2 support you should install the DirectX SDK. 67 */ 68#include <dxsdkver.h> 69#if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284)) 70# pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.") 71#else 72# define SDL_XAUDIO2_HAS_SDK 1 73#endif 74#endif 75 76#ifdef SDL_XAUDIO2_HAS_SDK 77 78/* Check to see if we're compiling for XAudio 2.8, or higher. */ 79#ifdef WINVER 80#if WINVER >= 0x0602 /* Windows 8 SDK or higher? */ 81#define SDL_XAUDIO2_WIN8 1 82#endif 83#endif 84 85/* The XAudio header file, when #include'd on WinRT, will only compile in C++ 86 files, but not C. A few preprocessor-based hacks are defined below in order 87 to get xaudio2.h to compile in the C/non-C++ file, SDL_xaudio2.c. 88 */ 89#ifdef __WINRT__ 90#define uuid(x) 91#define DX_BUILD 92#endif 93 94#define INITGUID 1 95#include <xaudio2.h> 96 97/* Hidden "this" pointer for the audio functions */ 98#define _THIS SDL_AudioDevice *this 99 100#ifdef __WINRT__ 101#include "SDL_xaudio2_winrthelpers.h" 102#endif 103 104/* Fixes bug 1210 where some versions of gcc need named parameters */ 105#ifdef __GNUC__ 106#ifdef THIS 107#undef THIS 108#endif 109#define THIS INTERFACE *p 110#ifdef THIS_ 111#undef THIS_ 112#endif 113#define THIS_ INTERFACE *p, 114#endif 115 116struct SDL_PrivateAudioData 117{ 118 IXAudio2 *ixa2; 119 IXAudio2SourceVoice *source; 120 IXAudio2MasteringVoice *mastering; 121 SDL_sem * semaphore; 122 Uint8 *mixbuf; 123 int mixlen; 124 Uint8 *nextbuf; 125}; 126 127 128static void 129XAUDIO2_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) 130{ 131 IXAudio2 *ixa2 = NULL; 132 UINT32 devcount = 0; 133 UINT32 i = 0; 134 135 if (iscapture) { 136 SDL_SetError("XAudio2: capture devices unsupported."); 137 return; 138 } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { 139 SDL_SetError("XAudio2: XAudio2Create() failed at detection."); 140 return; 141 } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { 142 SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed."); 143 IXAudio2_Release(ixa2); 144 return; 145 } 146 147 for (i = 0; i < devcount; i++) { 148 XAUDIO2_DEVICE_DETAILS details; 149 if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { 150 char *str = WIN_StringToUTF8(details.DisplayName); 151 if (str != NULL) { 152 addfn(str); 153 SDL_free(str); /* addfn() made a copy of the string. */ 154 } 155 } 156 } 157 158 IXAudio2_Release(ixa2); 159} 160 161static void STDMETHODCALLTYPE 162VoiceCBOnBufferEnd(THIS_ void *data) 163{ 164 /* Just signal the SDL audio thread and get out of XAudio2's way. */ 165 SDL_AudioDevice *this = (SDL_AudioDevice *) data; 166 SDL_SemPost(this->hidden->semaphore); 167} 168 169static void STDMETHODCALLTYPE 170VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error) 171{ 172 /* !!! FIXME: attempt to recover, or mark device disconnected. */ 173 SDL_assert(0 && "write me!"); 174} 175 176/* no-op callbacks... */ 177static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {} 178static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {} 179static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {} 180static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {} 181static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {} 182 183 184static Uint8 * 185XAUDIO2_GetDeviceBuf(_THIS) 186{ 187 return this->hidden->nextbuf; 188} 189 190static void 191XAUDIO2_PlayDevice(_THIS) 192{ 193 XAUDIO2_BUFFER buffer; 194 Uint8 *mixbuf = this->hidden->mixbuf; 195 Uint8 *nextbuf = this->hidden->nextbuf; 196 const int mixlen = this->hidden->mixlen; 197 IXAudio2SourceVoice *source = this->hidden->source; 198 HRESULT result = S_OK; 199 200 if (!this->enabled) { /* shutting down? */ 201 return; 202 } 203 204 /* Submit the next filled buffer */ 205 SDL_zero(buffer); 206 buffer.AudioBytes = mixlen; 207 buffer.pAudioData = nextbuf; 208 buffer.pContext = this; 209 210 if (nextbuf == mixbuf) { 211 nextbuf += mixlen; 212 } else { 213 nextbuf = mixbuf; 214 } 215 this->hidden->nextbuf = nextbuf; 216 217 result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL); 218 if (result == XAUDIO2_E_DEVICE_INVALIDATED) { 219 /* !!! FIXME: possibly disconnected or temporary lost. Recover? */ 220 } 221 222 if (result != S_OK) { /* uhoh, panic! */ 223 IXAudio2SourceVoice_FlushSourceBuffers(source); 224 this->enabled = 0; 225 } 226} 227 228static void 229XAUDIO2_WaitDevice(_THIS) 230{ 231 if (this->enabled) { 232 SDL_SemWait(this->hidden->semaphore); 233 } 234} 235 236static void 237XAUDIO2_WaitDone(_THIS) 238{ 239 IXAudio2SourceVoice *source = this->hidden->source; 240 XAUDIO2_VOICE_STATE state; 241 SDL_assert(!this->enabled); /* flag that stops playing. */ 242 IXAudio2SourceVoice_Discontinuity(source); 243#if SDL_XAUDIO2_WIN8 244 IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED); 245#else 246 IXAudio2SourceVoice_GetState(source, &state); 247#endif 248 while (state.BuffersQueued > 0) { 249 SDL_SemWait(this->hidden->semaphore); 250#if SDL_XAUDIO2_WIN8 251 IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED); 252#else 253 IXAudio2SourceVoice_GetState(source, &state); 254#endif 255 } 256} 257 258 259static void 260XAUDIO2_CloseDevice(_THIS) 261{ 262 if (this->hidden != NULL) { 263 IXAudio2 *ixa2 = this->hidden->ixa2; 264 IXAudio2SourceVoice *source = this->hidden->source; 265 IXAudio2MasteringVoice *mastering = this->hidden->mastering; 266 267 if (source != NULL) { 268 IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW); 269 IXAudio2SourceVoice_FlushSourceBuffers(source); 270 IXAudio2SourceVoice_DestroyVoice(source); 271 } 272 if (ixa2 != NULL) { 273 IXAudio2_StopEngine(ixa2); 274 } 275 if (mastering != NULL) { 276 IXAudio2MasteringVoice_DestroyVoice(mastering); 277 } 278 if (ixa2 != NULL) { 279 IXAudio2_Release(ixa2); 280 } 281 SDL_free(this->hidden->mixbuf); 282 if (this->hidden->semaphore != NULL) { 283 SDL_DestroySemaphore(this->hidden->semaphore); 284 } 285 286 SDL_free(this->hidden); 287 this->hidden = NULL; 288 } 289} 290 291static int 292XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture) 293{ 294 HRESULT result = S_OK; 295 WAVEFORMATEX waveformat; 296 int valid_format = 0; 297 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 298 IXAudio2 *ixa2 = NULL; 299 IXAudio2SourceVoice *source = NULL; 300#if defined(SDL_XAUDIO2_WIN8) 301 LPCWSTR devId = NULL; 302#else 303 UINT32 devId = 0; /* 0 == system default device. */ 304#endif 305 306 static IXAudio2VoiceCallbackVtbl callbacks_vtable = { 307 VoiceCBOnVoiceProcessPassStart, 308 VoiceCBOnVoiceProcessPassEnd, 309 VoiceCBOnStreamEnd, 310 VoiceCBOnBufferStart, 311 VoiceCBOnBufferEnd, 312 VoiceCBOnLoopEnd, 313 VoiceCBOnVoiceError 314 }; 315 316 static IXAudio2VoiceCallback callbacks = { &callbacks_vtable }; 317 318 if (iscapture) { 319 return SDL_SetError("XAudio2: capture devices unsupported."); 320 } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { 321 return SDL_SetError("XAudio2: XAudio2Create() failed at open."); 322 } 323 324 /* 325 XAUDIO2_DEBUG_CONFIGURATION debugConfig; 326 debugConfig.TraceMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL | XAUDIO2_LOG_FUNC_CALLS | XAUDIO2_LOG_TIMING | XAUDIO2_LOG_LOCKS | XAUDIO2_LOG_MEMORY | XAUDIO2_LOG_STREAMING; 327 debugConfig.BreakMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS; 328 debugConfig.LogThreadID = TRUE; 329 debugConfig.LogFileline = TRUE; 330 debugConfig.LogFunctionName = TRUE; 331 debugConfig.LogTiming = TRUE; 332 ixa2->SetDebugConfiguration(&debugConfig); 333 */ 334 335#if ! defined(__WINRT__) 336 if (devname != NULL) { 337 UINT32 devcount = 0; 338 UINT32 i = 0; 339 340 if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { 341 IXAudio2_Release(ixa2); 342 return SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed."); 343 } 344 for (i = 0; i < devcount; i++) { 345 XAUDIO2_DEVICE_DETAILS details; 346 if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { 347 char *str = WIN_StringToUTF8(details.DisplayName); 348 if (str != NULL) { 349 const int match = (SDL_strcmp(str, devname) == 0); 350 SDL_free(str); 351 if (match) { 352 devId = i; 353 break; 354 } 355 } 356 } 357 } 358 359 if (i == devcount) { 360 IXAudio2_Release(ixa2); 361 return SDL_SetError("XAudio2: Requested device not found."); 362 } 363 } 364#endif 365 366 /* Initialize all variables that we clean on shutdown */ 367 this->hidden = (struct SDL_PrivateAudioData *) 368 SDL_malloc((sizeof *this->hidden)); 369 if (this->hidden == NULL) { 370 IXAudio2_Release(ixa2); 371 return SDL_OutOfMemory(); 372 } 373 SDL_memset(this->hidden, 0, (sizeof *this->hidden)); 374 375 this->hidden->ixa2 = ixa2; 376 this->hidden->semaphore = SDL_CreateSemaphore(1); 377 if (this->hidden->semaphore == NULL) { 378 XAUDIO2_CloseDevice(this); 379 return SDL_SetError("XAudio2: CreateSemaphore() failed!"); 380 } 381 382 while ((!valid_format) && (test_format)) { 383 switch (test_format) { 384 case AUDIO_U8: 385 case AUDIO_S16: 386 case AUDIO_S32: 387 case AUDIO_F32: 388 this->spec.format = test_format; 389 valid_format = 1; 390 break; 391 } 392 test_format = SDL_NextAudioFormat(); 393 } 394 395 if (!valid_format) { 396 XAUDIO2_CloseDevice(this); 397 return SDL_SetError("XAudio2: Unsupported audio format"); 398 } 399 400 /* Update the fragment size as size in bytes */ 401 SDL_CalculateAudioSpec(&this->spec); 402 403 /* We feed a Source, it feeds the Mastering, which feeds the device. */ 404 this->hidden->mixlen = this->spec.size; 405 this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen); 406 if (this->hidden->mixbuf == NULL) { 407 XAUDIO2_CloseDevice(this); 408 return SDL_OutOfMemory(); 409 } 410 this->hidden->nextbuf = this->hidden->mixbuf; 411 SDL_memset(this->hidden->mixbuf, 0, 2 * this->hidden->mixlen); 412 413 /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On 414 Xbox360, this means 5.1 output, but on Windows, it means "figure out 415 what the system has." It might be preferable to let XAudio2 blast 416 stereo output to appropriate surround sound configurations 417 instead of clamping to 2 channels, even though we'll configure the 418 Source Voice for whatever number of channels you supply. */ 419#if SDL_XAUDIO2_WIN8 420 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering, 421 XAUDIO2_DEFAULT_CHANNELS, 422 this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects); 423#else 424 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering, 425 XAUDIO2_DEFAULT_CHANNELS, 426 this->spec.freq, 0, devId, NULL); 427#endif 428 if (result != S_OK) { 429 XAUDIO2_CloseDevice(this); 430 return SDL_SetError("XAudio2: Couldn't create mastering voice"); 431 } 432 433 SDL_zero(waveformat); 434 if (SDL_AUDIO_ISFLOAT(this->spec.format)) { 435 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; 436 } else { 437 waveformat.wFormatTag = WAVE_FORMAT_PCM; 438 } 439 waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); 440 waveformat.nChannels = this->spec.channels; 441 waveformat.nSamplesPerSec = this->spec.freq; 442 waveformat.nBlockAlign = 443 waveformat.nChannels * (waveformat.wBitsPerSample / 8); 444 waveformat.nAvgBytesPerSec = 445 waveformat.nSamplesPerSec * waveformat.nBlockAlign; 446 waveformat.cbSize = sizeof(waveformat); 447 448#ifdef __WINRT__ 449 // DLudwig: for now, make XAudio2 do sample rate conversion, just to 450 // get the loopwave test to work. 451 // 452 // TODO, WinRT: consider removing WinRT-specific source-voice creation code from SDL_xaudio2.c 453 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat, 454 0, 455 1.0f, &callbacks, NULL, NULL); 456#else 457 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat, 458 XAUDIO2_VOICE_NOSRC | 459 XAUDIO2_VOICE_NOPITCH, 460 1.0f, &callbacks, NULL, NULL); 461 462#endif 463 if (result != S_OK) { 464 XAUDIO2_CloseDevice(this); 465 return SDL_SetError("XAudio2: Couldn't create source voice"); 466 } 467 this->hidden->source = source; 468 469 /* Start everything playing! */ 470 result = IXAudio2_StartEngine(ixa2); 471 if (result != S_OK) { 472 XAUDIO2_CloseDevice(this); 473 return SDL_SetError("XAudio2: Couldn't start engine"); 474 } 475 476 result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW); 477 if (result != S_OK) { 478 XAUDIO2_CloseDevice(this); 479 return SDL_SetError("XAudio2: Couldn't start source voice"); 480 } 481 482 return 0; /* good to go. */ 483} 484 485static void 486XAUDIO2_Deinitialize(void) 487{ 488#if defined(__WIN32__) 489 WIN_CoUninitialize(); 490#endif 491} 492 493#endif /* SDL_XAUDIO2_HAS_SDK */ 494 495 496static int 497XAUDIO2_Init(SDL_AudioDriverImpl * impl) 498{ 499#ifndef SDL_XAUDIO2_HAS_SDK 500 SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK)."); 501 return 0; /* no XAudio2 support, ever. Update your SDK! */ 502#else 503 /* XAudio2Create() is a macro that uses COM; we don't load the .dll */ 504 IXAudio2 *ixa2 = NULL; 505#if defined(__WIN32__) 506 // TODO, WinRT: Investigate using CoInitializeEx here 507 if (FAILED(WIN_CoInitialize())) { 508 SDL_SetError("XAudio2: CoInitialize() failed"); 509 return 0; 510 } 511#endif 512 513 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { 514#if defined(__WIN32__) 515 WIN_CoUninitialize(); 516#endif 517 SDL_SetError("XAudio2: XAudio2Create() failed at initialization"); 518 return 0; /* not available. */ 519 } 520 IXAudio2_Release(ixa2); 521 522 /* Set the function pointers */ 523 impl->DetectDevices = XAUDIO2_DetectDevices; 524 impl->OpenDevice = XAUDIO2_OpenDevice; 525 impl->PlayDevice = XAUDIO2_PlayDevice; 526 impl->WaitDevice = XAUDIO2_WaitDevice; 527 impl->WaitDone = XAUDIO2_WaitDone; 528 impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf; 529 impl->CloseDevice = XAUDIO2_CloseDevice; 530 impl->Deinitialize = XAUDIO2_Deinitialize; 531 532 return 1; /* this audio target is available. */ 533#endif 534} 535 536AudioBootStrap XAUDIO2_bootstrap = { 537 "xaudio2", "XAudio2", XAUDIO2_Init, 0 538}; 539 540#endif /* SDL_AUDIO_DRIVER_XAUDIO2 */ 541 542/* vi: set ts=4 sw=4 expandtab: */