2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 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
27 http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
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
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.
47 #include "../../SDL_internal.h"
49 #if SDL_AUDIO_DRIVER_XAUDIO2
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"
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 (albeit with a header file
62 that doesn't compile as C code).
64 # define SDL_XAUDIO2_HAS_SDK
65 #include "SDL_xaudio2.h" /* ... compiles as C code, in contrast to XAudio2 headers
66 in the Windows SDK, v.10.0.10240.0 (Win 10's initial SDK)
69 /* XAudio2 exists in the last DirectX SDK as well as the latest Windows SDK.
70 To enable XAudio2 support, you will need to add the location of your DirectX SDK headers to
71 the SDL projects additional include directories and then set SDL_XAUDIO2_HAS_SDK=1 as a
74 #if 0 /* See comment above */
76 #if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284))
77 # pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.")
79 # define SDL_XAUDIO2_HAS_SDK 1
84 #ifdef SDL_XAUDIO2_HAS_SDK
86 /* Check to see if we're compiling for XAudio 2.8, or higher. */
88 #if WINVER >= 0x0602 /* Windows 8 SDK or higher? */
89 #define SDL_XAUDIO2_WIN8 1
93 #if !defined(_SDL_XAUDIO2_H)
98 /* Hidden "this" pointer for the audio functions */
99 #define _THIS SDL_AudioDevice *this
102 #include "SDL_xaudio2_winrthelpers.h"
105 /* Fixes bug 1210 where some versions of gcc need named parameters */
110 #define THIS INTERFACE *p
114 #define THIS_ INTERFACE *p,
117 struct SDL_PrivateAudioData
120 IXAudio2SourceVoice *source;
121 IXAudio2MasteringVoice *mastering;
130 XAUDIO2_DetectDevices(void)
132 IXAudio2 *ixa2 = NULL;
136 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
137 SDL_SetError("XAudio2: XAudio2Create() failed at detection.");
139 } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
140 SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
141 IXAudio2_Release(ixa2);
145 for (i = 0; i < devcount; i++) {
146 XAUDIO2_DEVICE_DETAILS details;
147 if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) {
148 char *str = WIN_StringToUTF8(details.DisplayName);
150 SDL_AddAudioDevice(SDL_FALSE, str, (void *) ((size_t) i+1));
151 SDL_free(str); /* SDL_AddAudioDevice made a copy of the string. */
156 IXAudio2_Release(ixa2);
159 static void STDMETHODCALLTYPE
160 VoiceCBOnBufferEnd(THIS_ void *data)
162 /* Just signal the SDL audio thread and get out of XAudio2's way. */
163 SDL_AudioDevice *this = (SDL_AudioDevice *) data;
164 SDL_SemPost(this->hidden->semaphore);
167 static void STDMETHODCALLTYPE
168 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
170 SDL_AudioDevice *this = (SDL_AudioDevice *) data;
171 SDL_OpenedAudioDeviceDisconnected(this);
174 /* no-op callbacks... */
175 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
176 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
177 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
178 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
179 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
183 XAUDIO2_GetDeviceBuf(_THIS)
185 return this->hidden->nextbuf;
189 XAUDIO2_PlayDevice(_THIS)
191 XAUDIO2_BUFFER buffer;
192 Uint8 *mixbuf = this->hidden->mixbuf;
193 Uint8 *nextbuf = this->hidden->nextbuf;
194 const int mixlen = this->hidden->mixlen;
195 IXAudio2SourceVoice *source = this->hidden->source;
196 HRESULT result = S_OK;
198 if (!this->enabled) { /* shutting down? */
202 /* Submit the next filled buffer */
204 buffer.AudioBytes = mixlen;
205 buffer.pAudioData = nextbuf;
206 buffer.pContext = this;
208 if (nextbuf == mixbuf) {
213 this->hidden->nextbuf = nextbuf;
215 result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
216 if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
217 /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
220 if (result != S_OK) { /* uhoh, panic! */
221 IXAudio2SourceVoice_FlushSourceBuffers(source);
222 SDL_OpenedAudioDeviceDisconnected(this);
227 XAUDIO2_WaitDevice(_THIS)
230 SDL_SemWait(this->hidden->semaphore);
235 XAUDIO2_WaitDone(_THIS)
237 IXAudio2SourceVoice *source = this->hidden->source;
238 XAUDIO2_VOICE_STATE state;
239 SDL_assert(!this->enabled); /* flag that stops playing. */
240 IXAudio2SourceVoice_Discontinuity(source);
242 IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
244 IXAudio2SourceVoice_GetState(source, &state);
246 while (state.BuffersQueued > 0) {
247 SDL_SemWait(this->hidden->semaphore);
249 IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
251 IXAudio2SourceVoice_GetState(source, &state);
258 XAUDIO2_CloseDevice(_THIS)
260 if (this->hidden != NULL) {
261 IXAudio2 *ixa2 = this->hidden->ixa2;
262 IXAudio2SourceVoice *source = this->hidden->source;
263 IXAudio2MasteringVoice *mastering = this->hidden->mastering;
265 if (source != NULL) {
266 IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
267 IXAudio2SourceVoice_FlushSourceBuffers(source);
268 IXAudio2SourceVoice_DestroyVoice(source);
271 IXAudio2_StopEngine(ixa2);
273 if (mastering != NULL) {
274 IXAudio2MasteringVoice_DestroyVoice(mastering);
277 IXAudio2_Release(ixa2);
279 SDL_free(this->hidden->mixbuf);
280 if (this->hidden->semaphore != NULL) {
281 SDL_DestroySemaphore(this->hidden->semaphore);
284 SDL_free(this->hidden);
290 XAUDIO2_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
292 HRESULT result = S_OK;
293 WAVEFORMATEX waveformat;
294 int valid_format = 0;
295 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
296 IXAudio2 *ixa2 = NULL;
297 IXAudio2SourceVoice *source = NULL;
298 #if defined(SDL_XAUDIO2_WIN8)
299 LPCWSTR devId = NULL;
301 UINT32 devId = 0; /* 0 == system default device. */
304 static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
305 VoiceCBOnVoiceProcessPassStart,
306 VoiceCBOnVoiceProcessPassEnd,
308 VoiceCBOnBufferStart,
314 static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
316 #if defined(SDL_XAUDIO2_WIN8)
317 /* !!! FIXME: hook up hotplugging. */
319 if (handle != NULL) { /* specific device requested? */
320 /* -1 because we increment the original value to avoid NULL. */
321 const size_t val = ((size_t) handle) - 1;
322 devId = (UINT32) val;
326 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
327 return SDL_SetError("XAudio2: XAudio2Create() failed at open.");
331 XAUDIO2_DEBUG_CONFIGURATION debugConfig;
332 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;
333 debugConfig.BreakMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS;
334 debugConfig.LogThreadID = TRUE;
335 debugConfig.LogFileline = TRUE;
336 debugConfig.LogFunctionName = TRUE;
337 debugConfig.LogTiming = TRUE;
338 ixa2->SetDebugConfiguration(&debugConfig);
341 /* Initialize all variables that we clean on shutdown */
342 this->hidden = (struct SDL_PrivateAudioData *)
343 SDL_malloc((sizeof *this->hidden));
344 if (this->hidden == NULL) {
345 IXAudio2_Release(ixa2);
346 return SDL_OutOfMemory();
348 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
350 this->hidden->ixa2 = ixa2;
351 this->hidden->semaphore = SDL_CreateSemaphore(1);
352 if (this->hidden->semaphore == NULL) {
353 XAUDIO2_CloseDevice(this);
354 return SDL_SetError("XAudio2: CreateSemaphore() failed!");
357 while ((!valid_format) && (test_format)) {
358 switch (test_format) {
363 this->spec.format = test_format;
367 test_format = SDL_NextAudioFormat();
371 XAUDIO2_CloseDevice(this);
372 return SDL_SetError("XAudio2: Unsupported audio format");
375 /* Update the fragment size as size in bytes */
376 SDL_CalculateAudioSpec(&this->spec);
378 /* We feed a Source, it feeds the Mastering, which feeds the device. */
379 this->hidden->mixlen = this->spec.size;
380 this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
381 if (this->hidden->mixbuf == NULL) {
382 XAUDIO2_CloseDevice(this);
383 return SDL_OutOfMemory();
385 this->hidden->nextbuf = this->hidden->mixbuf;
386 SDL_memset(this->hidden->mixbuf, 0, 2 * this->hidden->mixlen);
388 /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
389 Xbox360, this means 5.1 output, but on Windows, it means "figure out
390 what the system has." It might be preferable to let XAudio2 blast
391 stereo output to appropriate surround sound configurations
392 instead of clamping to 2 channels, even though we'll configure the
393 Source Voice for whatever number of channels you supply. */
395 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
396 XAUDIO2_DEFAULT_CHANNELS,
397 this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects);
399 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
400 XAUDIO2_DEFAULT_CHANNELS,
401 this->spec.freq, 0, devId, NULL);
403 if (result != S_OK) {
404 XAUDIO2_CloseDevice(this);
405 return SDL_SetError("XAudio2: Couldn't create mastering voice");
408 SDL_zero(waveformat);
409 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
410 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
412 waveformat.wFormatTag = WAVE_FORMAT_PCM;
414 waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
415 waveformat.nChannels = this->spec.channels;
416 waveformat.nSamplesPerSec = this->spec.freq;
417 waveformat.nBlockAlign =
418 waveformat.nChannels * (waveformat.wBitsPerSample / 8);
419 waveformat.nAvgBytesPerSec =
420 waveformat.nSamplesPerSec * waveformat.nBlockAlign;
421 waveformat.cbSize = sizeof(waveformat);
424 // DLudwig: for now, make XAudio2 do sample rate conversion, just to
425 // get the loopwave test to work.
427 // TODO, WinRT: consider removing WinRT-specific source-voice creation code from SDL_xaudio2.c
428 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
430 1.0f, &callbacks, NULL, NULL);
432 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
433 XAUDIO2_VOICE_NOSRC |
434 XAUDIO2_VOICE_NOPITCH,
435 1.0f, &callbacks, NULL, NULL);
438 if (result != S_OK) {
439 XAUDIO2_CloseDevice(this);
440 return SDL_SetError("XAudio2: Couldn't create source voice");
442 this->hidden->source = source;
444 /* Start everything playing! */
445 result = IXAudio2_StartEngine(ixa2);
446 if (result != S_OK) {
447 XAUDIO2_CloseDevice(this);
448 return SDL_SetError("XAudio2: Couldn't start engine");
451 result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
452 if (result != S_OK) {
453 XAUDIO2_CloseDevice(this);
454 return SDL_SetError("XAudio2: Couldn't start source voice");
457 return 0; /* good to go. */
461 XAUDIO2_Deinitialize(void)
463 #if defined(__WIN32__)
464 WIN_CoUninitialize();
468 #endif /* SDL_XAUDIO2_HAS_SDK */
472 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
474 #ifndef SDL_XAUDIO2_HAS_SDK
475 SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK).");
476 return 0; /* no XAudio2 support, ever. Update your SDK! */
478 /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
479 IXAudio2 *ixa2 = NULL;
480 #if defined(__WIN32__)
481 // TODO, WinRT: Investigate using CoInitializeEx here
482 if (FAILED(WIN_CoInitialize())) {
483 SDL_SetError("XAudio2: CoInitialize() failed");
488 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
489 #if defined(__WIN32__)
490 WIN_CoUninitialize();
492 SDL_SetError("XAudio2: XAudio2Create() failed at initialization");
493 return 0; /* not available. */
495 IXAudio2_Release(ixa2);
497 /* Set the function pointers */
498 impl->DetectDevices = XAUDIO2_DetectDevices;
499 impl->OpenDevice = XAUDIO2_OpenDevice;
500 impl->PlayDevice = XAUDIO2_PlayDevice;
501 impl->WaitDevice = XAUDIO2_WaitDevice;
502 impl->WaitDone = XAUDIO2_WaitDone;
503 impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
504 impl->CloseDevice = XAUDIO2_CloseDevice;
505 impl->Deinitialize = XAUDIO2_Deinitialize;
507 /* !!! FIXME: We can apparently use a C++ interface on Windows 8
508 * !!! FIXME: (Windows::Devices::Enumeration::DeviceInformation) for device
509 * !!! FIXME: detection, but it's not implemented here yet.
510 * !!! FIXME: see http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
511 * !!! FIXME: for now, force the default device.
513 #if defined(SDL_XAUDIO2_WIN8) || defined(__WINRT__)
514 impl->OnlyHasDefaultOutputDevice = 1;
517 return 1; /* this audio target is available. */
521 AudioBootStrap XAUDIO2_bootstrap = {
522 "xaudio2", "XAudio2", XAUDIO2_Init, 0
525 #endif /* SDL_AUDIO_DRIVER_XAUDIO2 */
527 /* vi: set ts=4 sw=4 expandtab: */