2 Simple DirectMedia Layer
3 Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
24 A number of changes were warranted to SDL's XAudio2 backend in order to
25 get it compiling for Windows RT.
27 When compiling for WinRT, XAudio2.h requires that it be compiled in a C++
28 file, and not a straight C file. Trying to compile it as C leads to lots
29 of errors, at least with MSVC 2012 and Windows SDK 8.0, as of Nov 22, 2012.
30 To address this specific issue, a few changes were made to SDL_xaudio2.c:
32 1. SDL_xaudio2.c is compiled as a C++ file in WinRT builds. Exported
33 symbols, namely XAUDIO2_bootstrap, uses 'extern "C"' to make sure the
34 rest of SDL can access it. Non-WinRT builds continue to compile
35 SDL_xaudio2.c as a C file.
36 2. A macro redefines variables named 'this' to '_this', to prevent compiler
37 errors (C2355 in Visual C++) related to 'this' being a reserverd keyword.
38 This hack may need to be altered in the future, particularly if C++'s
39 'this' keyword needs to be used (within SDL_xaudio2.c). At the time
40 WinRT support was initially added to SDL's XAudio2 backend, this
41 capability was not needed.
42 3. The C-style macros to invoke XAudio2's COM-based methods were
43 rewritten to be C++-friendly. These are provided in the file,
44 SDL_xaudio2_winrthelpers.h.
45 4. IXAudio2::CreateSourceVoice, when used in C++, requires its callbacks to
46 be specified via a C++ class. SDL's XAudio2 backend was written with
47 C-style callbacks. A class to bridge these two interfaces,
48 SDL_XAudio2VoiceCallback, was written to make XAudio2 happy. Its methods
49 just call SDL's existing, C-style callbacks.
50 5. Multiple checks for the __cplusplus macro were made, in appropriate
54 A few additional changes to SDL's XAudio2 backend were warranted by API
55 changes to Windows. Many, but not all of these are documented by Microsoft
57 http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
59 1. Windows' thread synchronization function, CreateSemaphore, was removed
60 from Windows RT. SDL's semaphore API was substituted instead.
61 2. The method calls, IXAudio2::GetDeviceCount and IXAudio2::GetDeviceDetails
62 were removed from the XAudio2 API. Microsoft is telling developers to
63 use APIs in Windows::Foundation instead.
64 For SDL, the missing methods were reimplemented using the APIs Microsoft
66 3. CoInitialize and CoUninitialize are not available in Windows RT.
67 These calls were removed, as COM will have been initialized earlier,
68 at least by the call to the WinRT app's main function
69 (aka 'int main(Platform::Array<Platform::String^>^)). (DLudwig:
70 This was my understanding of how WinRT: the 'main' function uses
71 a tag of [MTAThread], which should initialize COM. My understanding
72 of COM is somewhat limited, and I may be incorrect here.)
73 4. IXAudio2::CreateMasteringVoice changed its integer-based 'DeviceIndex'
74 argument to a string-based one, 'szDeviceId'. In Windows RT, the
75 string-based argument will be used.
78 #include "SDL_config.h"
80 #if SDL_AUDIO_DRIVER_XAUDIO2
85 #include "../../core/windows/SDL_windows.h"
86 #include "SDL_audio.h"
87 #include "../SDL_audio_c.h"
88 #include "../SDL_sysaudio.h"
89 #include "SDL_assert.h"
94 #if defined(__WINRT__)
95 # define SDL_XAUDIO2_HAS_SDK 1
97 #if defined(__WIN32__)
98 #include <dxsdkver.h> /* XAudio2 exists as of the March 2008 DirectX SDK */
99 #if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284))
100 # pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.")
102 # define SDL_XAUDIO2_HAS_SDK 1
106 #ifdef SDL_XAUDIO2_HAS_SDK
111 /* Hidden "this" pointer for the audio functions */
112 #define _THIS SDL_AudioDevice *this
116 #include "SDL_xaudio2_winrthelpers.h"
119 struct SDL_PrivateAudioData
122 IXAudio2SourceVoice *source;
123 IXAudio2MasteringVoice *mastering;
131 static __inline__ char *
132 utf16_to_utf8(const WCHAR *S)
134 /* !!! FIXME: this should be UTF-16, not UCS-2! */
135 return SDL_iconv_string("UTF-8", "UCS-2", (char *)(S),
136 (SDL_wcslen(S)+1)*sizeof(WCHAR));
140 XAUDIO2_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
142 IXAudio2 *ixa2 = NULL;
148 SDL_SetError("XAudio2: capture devices unsupported.");
150 } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
151 SDL_SetError("XAudio2: XAudio2Create() failed.");
153 } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
154 SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
155 IXAudio2_Release(ixa2);
159 for (i = 0; i < devcount; i++) {
160 XAUDIO2_DEVICE_DETAILS details;
161 if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
162 char *str = utf16_to_utf8(details.DisplayName);
165 SDL_free(str); /* addfn() made a copy of the string. */
170 IXAudio2_Release(ixa2);
173 static void STDMETHODCALLTYPE
174 VoiceCBOnBufferEnd(THIS_ void *data)
176 /* Just signal the SDL audio thread and get out of XAudio2's way. */
177 SDL_AudioDevice *this = (SDL_AudioDevice *) data;
178 SDL_SemPost(this->hidden->semaphore);
181 static void STDMETHODCALLTYPE
182 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
184 /* !!! FIXME: attempt to recover, or mark device disconnected. */
185 SDL_assert(0 && "write me!");
188 /* no-op callbacks... */
189 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
190 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
191 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
192 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
193 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
195 #if defined(__cplusplus)
196 class SDL_XAudio2VoiceCallback : public IXAudio2VoiceCallback
199 STDMETHOD_(void, OnBufferEnd)(void *pBufferContext) {
200 VoiceCBOnBufferEnd(pBufferContext);
202 STDMETHOD_(void, OnBufferStart)(void *pBufferContext) {
203 VoiceCBOnBufferEnd(pBufferContext);
205 STDMETHOD_(void, OnLoopEnd)(void *pBufferContext) {
206 VoiceCBOnLoopEnd(pBufferContext);
208 STDMETHOD_(void, OnStreamEnd)() {
209 VoiceCBOnStreamEnd();
211 STDMETHOD_(void, OnVoiceError)(void *pBufferContext, HRESULT Error) {
212 VoiceCBOnVoiceError(pBufferContext, Error);
214 STDMETHOD_(void, OnVoiceProcessingPassEnd)() {
215 VoiceCBOnVoiceProcessPassEnd();
217 STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32 BytesRequired) {
218 VoiceCBOnVoiceProcessPassStart(BytesRequired);
224 XAUDIO2_GetDeviceBuf(_THIS)
226 return this->hidden->nextbuf;
230 XAUDIO2_PlayDevice(_THIS)
232 XAUDIO2_BUFFER buffer;
233 Uint8 *mixbuf = this->hidden->mixbuf;
234 Uint8 *nextbuf = this->hidden->nextbuf;
235 const int mixlen = this->hidden->mixlen;
236 IXAudio2SourceVoice *source = this->hidden->source;
237 HRESULT result = S_OK;
239 if (!this->enabled) { /* shutting down? */
243 /* Submit the next filled buffer */
245 buffer.AudioBytes = mixlen;
246 buffer.pAudioData = nextbuf;
247 buffer.pContext = this;
249 if (nextbuf == mixbuf) {
254 this->hidden->nextbuf = nextbuf;
256 result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
257 if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
258 /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
261 if (result != S_OK) { /* uhoh, panic! */
262 IXAudio2SourceVoice_FlushSourceBuffers(source);
268 XAUDIO2_WaitDevice(_THIS)
271 SDL_SemWait(this->hidden->semaphore);
276 XAUDIO2_WaitDone(_THIS)
278 IXAudio2SourceVoice *source = this->hidden->source;
279 XAUDIO2_VOICE_STATE state;
280 SDL_assert(!this->enabled); /* flag that stops playing. */
281 IXAudio2SourceVoice_Discontinuity(source);
282 IXAudio2SourceVoice_GetState(source, &state);
283 while (state.BuffersQueued > 0) {
284 SDL_SemWait(this->hidden->semaphore);
285 IXAudio2SourceVoice_GetState(source, &state);
291 XAUDIO2_CloseDevice(_THIS)
293 if (this->hidden != NULL) {
294 IXAudio2 *ixa2 = this->hidden->ixa2;
295 IXAudio2SourceVoice *source = this->hidden->source;
296 IXAudio2MasteringVoice *mastering = this->hidden->mastering;
298 if (source != NULL) {
299 IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
300 IXAudio2SourceVoice_FlushSourceBuffers(source);
301 IXAudio2SourceVoice_DestroyVoice(source);
304 IXAudio2_StopEngine(ixa2);
306 if (mastering != NULL) {
307 IXAudio2MasteringVoice_DestroyVoice(mastering);
310 IXAudio2_Release(ixa2);
312 if (this->hidden->mixbuf != NULL) {
313 SDL_free(this->hidden->mixbuf);
315 if (this->hidden->semaphore != NULL) {
316 CloseHandle(this->hidden->semaphore);
319 SDL_free(this->hidden);
325 XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture)
327 HRESULT result = S_OK;
328 WAVEFORMATEX waveformat;
329 int valid_format = 0;
330 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
331 IXAudio2 *ixa2 = NULL;
332 IXAudio2SourceVoice *source = NULL;
333 #if defined(__WINRT__)
336 UINT32 devId = 0; /* 0 == system default device. */
339 #if defined(__cplusplus)
340 static SDL_XAudio2VoiceCallback callbacks;
342 static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
343 VoiceCBOnVoiceProcessPassStart,
344 VoiceCBOnVoiceProcessPassEnd,
346 VoiceCBOnBufferStart,
352 static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
353 #endif // ! defined(__cplusplus)
355 #if defined(__WINRT__)
360 SDL_SetError("XAudio2: capture devices unsupported.");
362 } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
363 SDL_SetError("XAudio2: XAudio2Create() failed.");
367 if (devname != NULL) {
371 if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
372 IXAudio2_Release(ixa2);
373 SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed.");
376 for (i = 0; i < devcount; i++) {
377 XAUDIO2_DEVICE_DETAILS details;
378 if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
379 char *str = utf16_to_utf8(details.DisplayName);
381 const int match = (SDL_strcmp(str, devname) == 0);
384 #if defined(__WINRT__)
385 wcsncpy_s(devId, ARRAYSIZE(devId), details.DeviceID, _TRUNCATE);
396 IXAudio2_Release(ixa2);
397 SDL_SetError("XAudio2: Requested device not found.");
402 /* Initialize all variables that we clean on shutdown */
403 this->hidden = (struct SDL_PrivateAudioData *)
404 SDL_malloc((sizeof *this->hidden));
405 if (this->hidden == NULL) {
406 IXAudio2_Release(ixa2);
410 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
412 this->hidden->ixa2 = ixa2;
413 this->hidden->semaphore = SDL_CreateSemaphore(1);
414 if (this->hidden->semaphore == NULL) {
415 XAUDIO2_CloseDevice(this);
416 SDL_SetError("XAudio2: CreateSemaphore() failed!");
420 while ((!valid_format) && (test_format)) {
421 switch (test_format) {
426 this->spec.format = test_format;
430 test_format = SDL_NextAudioFormat();
434 XAUDIO2_CloseDevice(this);
435 SDL_SetError("XAudio2: Unsupported audio format");
439 /* Update the fragment size as size in bytes */
440 SDL_CalculateAudioSpec(&this->spec);
442 /* We feed a Source, it feeds the Mastering, which feeds the device. */
443 this->hidden->mixlen = this->spec.size;
444 this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
445 if (this->hidden->mixbuf == NULL) {
446 XAUDIO2_CloseDevice(this);
450 this->hidden->nextbuf = this->hidden->mixbuf;
451 SDL_memset(this->hidden->mixbuf, 0, 2 * this->hidden->mixlen);
453 /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
454 Xbox360, this means 5.1 output, but on Windows, it means "figure out
455 what the system has." It might be preferable to let XAudio2 blast
456 stereo output to appropriate surround sound configurations
457 instead of clamping to 2 channels, even though we'll configure the
458 Source Voice for whatever number of channels you supply. */
459 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
460 XAUDIO2_DEFAULT_CHANNELS,
461 this->spec.freq, 0, devId, NULL);
462 if (result != S_OK) {
463 XAUDIO2_CloseDevice(this);
464 SDL_SetError("XAudio2: Couldn't create mastering voice");
468 SDL_zero(waveformat);
469 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
470 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
472 waveformat.wFormatTag = WAVE_FORMAT_PCM;
474 waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
475 waveformat.nChannels = this->spec.channels;
476 waveformat.nSamplesPerSec = this->spec.freq;
477 waveformat.nBlockAlign =
478 waveformat.nChannels * (waveformat.wBitsPerSample / 8);
479 waveformat.nAvgBytesPerSec =
480 waveformat.nSamplesPerSec * waveformat.nBlockAlign;
482 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
483 XAUDIO2_VOICE_NOSRC |
484 XAUDIO2_VOICE_NOPITCH,
485 1.0f, &callbacks, NULL, NULL);
486 if (result != S_OK) {
487 XAUDIO2_CloseDevice(this);
488 SDL_SetError("XAudio2: Couldn't create source voice");
491 this->hidden->source = source;
493 /* Start everything playing! */
494 result = IXAudio2_StartEngine(ixa2);
495 if (result != S_OK) {
496 XAUDIO2_CloseDevice(this);
497 SDL_SetError("XAudio2: Couldn't start engine");
501 result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
502 if (result != S_OK) {
503 XAUDIO2_CloseDevice(this);
504 SDL_SetError("XAudio2: Couldn't start source voice");
508 return 1; /* good to go. */
512 XAUDIO2_Deinitialize(void)
514 #if defined(__WIN32__)
515 WIN_CoUninitialize();
519 #endif /* SDL_XAUDIO2_HAS_SDK */
523 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
525 #ifndef SDL_XAUDIO2_HAS_SDK
526 SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK).");
527 return 0; /* no XAudio2 support, ever. Update your SDK! */
529 /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
530 IXAudio2 *ixa2 = NULL;
531 #if defined(__WIN32__)
532 if (FAILED(WIN_CoInitialize())) {
533 SDL_SetError("XAudio2: CoInitialize() failed");
538 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
539 #if defined(__WIN32__)
540 WIN_CoUninitialize();
542 SDL_SetError("XAudio2: XAudio2Create() failed");
543 return 0; /* not available. */
545 IXAudio2_Release(ixa2);
547 /* Set the function pointers */
548 impl->DetectDevices = XAUDIO2_DetectDevices;
549 impl->OpenDevice = XAUDIO2_OpenDevice;
550 impl->PlayDevice = XAUDIO2_PlayDevice;
551 impl->WaitDevice = XAUDIO2_WaitDevice;
552 impl->WaitDone = XAUDIO2_WaitDone;
553 impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
554 impl->CloseDevice = XAUDIO2_CloseDevice;
555 impl->Deinitialize = XAUDIO2_Deinitialize;
557 return 1; /* this audio target is available. */
561 #if defined(__cplusplus)
564 AudioBootStrap XAUDIO2_bootstrap = {
565 "xaudio2", "XAudio2", XAUDIO2_Init, 0
568 #endif /* SDL_AUDIO_DRIVER_XAUDIO2 */
570 /* vi: set ts=4 sw=4 expandtab: */