1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 (albeit with a header file
62 that doesn't compile as C code).
63 */
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)
67 */
68 #else
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
72 preprocessor define
73 */
74 #if 0 /* See comment above */
75 #include <dxsdkver.h>
76 #if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284))
77 # pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.")
78 #else
79 # define SDL_XAUDIO2_HAS_SDK 1
80 #endif
81 #endif
82 #endif /* 0 */
83
84 #ifdef SDL_XAUDIO2_HAS_SDK
85
86 /* Check to see if we're compiling for XAudio 2.8, or higher. */
87 #ifdef WINVER
88 #if WINVER >= 0x0602 /* Windows 8 SDK or higher? */
89 #define SDL_XAUDIO2_WIN8 1
90 #endif
91 #endif
92
93 #if !defined(_SDL_XAUDIO2_H)
94 #define INITGUID 1
95 #include <xaudio2.h>
96 #endif
97
98 /* Hidden "this" pointer for the audio functions */
99 #define _THIS SDL_AudioDevice *this
100
101 #ifdef __WINRT__
102 #include "SDL_xaudio2_winrthelpers.h"
103 #endif
104
105 /* Fixes bug 1210 where some versions of gcc need named parameters */
106 #ifdef __GNUC__
107 #ifdef THIS
108 #undef THIS
109 #endif
110 #define THIS INTERFACE *p
111 #ifdef THIS_
112 #undef THIS_
113 #endif
114 #define THIS_ INTERFACE *p,
115 #endif
116
117 struct SDL_PrivateAudioData
118 {
119 IXAudio2 *ixa2;
120 IXAudio2SourceVoice *source;
121 IXAudio2MasteringVoice *mastering;
122 SDL_sem * semaphore;
123 Uint8 *mixbuf;
124 int mixlen;
125 Uint8 *nextbuf;
126 };
127
128
129 static void
XAUDIO2_DetectDevices(void)130 XAUDIO2_DetectDevices(void)
131 {
132 IXAudio2 *ixa2 = NULL;
133 UINT32 devcount = 0;
134 UINT32 i = 0;
135
136 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
137 SDL_SetError("XAudio2: XAudio2Create() failed at detection.");
138 return;
139 } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) {
140 SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed.");
141 IXAudio2_Release(ixa2);
142 return;
143 }
144
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);
149 if (str != NULL) {
150 SDL_AddAudioDevice(SDL_FALSE, str, (void *) ((size_t) i+1));
151 SDL_free(str); /* SDL_AddAudioDevice made a copy of the string. */
152 }
153 }
154 }
155
156 IXAudio2_Release(ixa2);
157 }
158
159 static void STDMETHODCALLTYPE
VoiceCBOnBufferEnd(THIS_ void * data)160 VoiceCBOnBufferEnd(THIS_ void *data)
161 {
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);
165 }
166
167 static void STDMETHODCALLTYPE
VoiceCBOnVoiceError(THIS_ void * data,HRESULT Error)168 VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error)
169 {
170 SDL_AudioDevice *this = (SDL_AudioDevice *) data;
171 SDL_OpenedAudioDeviceDisconnected(this);
172 }
173
174 /* no-op callbacks... */
VoiceCBOnStreamEnd(THIS)175 static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {}
VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b)176 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {}
VoiceCBOnVoiceProcessPassEnd(THIS)177 static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {}
VoiceCBOnBufferStart(THIS_ void * data)178 static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {}
VoiceCBOnLoopEnd(THIS_ void * data)179 static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {}
180
181
182 static Uint8 *
XAUDIO2_GetDeviceBuf(_THIS)183 XAUDIO2_GetDeviceBuf(_THIS)
184 {
185 return this->hidden->nextbuf;
186 }
187
188 static void
XAUDIO2_PlayDevice(_THIS)189 XAUDIO2_PlayDevice(_THIS)
190 {
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;
197
198 if (!SDL_AtomicGet(&this->enabled)) { /* shutting down? */
199 return;
200 }
201
202 /* Submit the next filled buffer */
203 SDL_zero(buffer);
204 buffer.AudioBytes = mixlen;
205 buffer.pAudioData = nextbuf;
206 buffer.pContext = this;
207
208 if (nextbuf == mixbuf) {
209 nextbuf += mixlen;
210 } else {
211 nextbuf = mixbuf;
212 }
213 this->hidden->nextbuf = nextbuf;
214
215 result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL);
216 if (result == XAUDIO2_E_DEVICE_INVALIDATED) {
217 /* !!! FIXME: possibly disconnected or temporary lost. Recover? */
218 }
219
220 if (result != S_OK) { /* uhoh, panic! */
221 IXAudio2SourceVoice_FlushSourceBuffers(source);
222 SDL_OpenedAudioDeviceDisconnected(this);
223 }
224 }
225
226 static void
XAUDIO2_WaitDevice(_THIS)227 XAUDIO2_WaitDevice(_THIS)
228 {
229 if (SDL_AtomicGet(&this->enabled)) {
230 SDL_SemWait(this->hidden->semaphore);
231 }
232 }
233
234 static void
XAUDIO2_PrepareToClose(_THIS)235 XAUDIO2_PrepareToClose(_THIS)
236 {
237 IXAudio2SourceVoice *source = this->hidden->source;
238 if (source) {
239 IXAudio2SourceVoice_Discontinuity(source);
240 }
241 }
242
243 static void
XAUDIO2_CloseDevice(_THIS)244 XAUDIO2_CloseDevice(_THIS)
245 {
246 IXAudio2 *ixa2 = this->hidden->ixa2;
247 IXAudio2SourceVoice *source = this->hidden->source;
248 IXAudio2MasteringVoice *mastering = this->hidden->mastering;
249
250 if (source != NULL) {
251 IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
252 IXAudio2SourceVoice_FlushSourceBuffers(source);
253 IXAudio2SourceVoice_DestroyVoice(source);
254 }
255 if (ixa2 != NULL) {
256 IXAudio2_StopEngine(ixa2);
257 }
258 if (mastering != NULL) {
259 IXAudio2MasteringVoice_DestroyVoice(mastering);
260 }
261 if (ixa2 != NULL) {
262 IXAudio2_Release(ixa2);
263 }
264 if (this->hidden->semaphore != NULL) {
265 SDL_DestroySemaphore(this->hidden->semaphore);
266 }
267
268 SDL_free(this->hidden->mixbuf);
269 SDL_free(this->hidden);
270 }
271
272 static int
XAUDIO2_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)273 XAUDIO2_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
274 {
275 HRESULT result = S_OK;
276 WAVEFORMATEX waveformat;
277 int valid_format = 0;
278 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
279 IXAudio2 *ixa2 = NULL;
280 IXAudio2SourceVoice *source = NULL;
281 #if defined(SDL_XAUDIO2_WIN8)
282 LPCWSTR devId = NULL;
283 #else
284 UINT32 devId = 0; /* 0 == system default device. */
285 #endif
286
287 static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
288 VoiceCBOnVoiceProcessPassStart,
289 VoiceCBOnVoiceProcessPassEnd,
290 VoiceCBOnStreamEnd,
291 VoiceCBOnBufferStart,
292 VoiceCBOnBufferEnd,
293 VoiceCBOnLoopEnd,
294 VoiceCBOnVoiceError
295 };
296
297 static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
298
299 #if defined(SDL_XAUDIO2_WIN8)
300 /* !!! FIXME: hook up hotplugging. */
301 #else
302 if (handle != NULL) { /* specific device requested? */
303 /* -1 because we increment the original value to avoid NULL. */
304 const size_t val = ((size_t) handle) - 1;
305 devId = (UINT32) val;
306 }
307 #endif
308
309 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
310 return SDL_SetError("XAudio2: XAudio2Create() failed at open.");
311 }
312
313 /*
314 XAUDIO2_DEBUG_CONFIGURATION debugConfig;
315 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;
316 debugConfig.BreakMask = XAUDIO2_LOG_ERRORS; //XAUDIO2_LOG_WARNINGS;
317 debugConfig.LogThreadID = TRUE;
318 debugConfig.LogFileline = TRUE;
319 debugConfig.LogFunctionName = TRUE;
320 debugConfig.LogTiming = TRUE;
321 ixa2->SetDebugConfiguration(&debugConfig);
322 */
323
324 /* Initialize all variables that we clean on shutdown */
325 this->hidden = (struct SDL_PrivateAudioData *)
326 SDL_malloc((sizeof *this->hidden));
327 if (this->hidden == NULL) {
328 IXAudio2_Release(ixa2);
329 return SDL_OutOfMemory();
330 }
331 SDL_zerop(this->hidden);
332
333 this->hidden->ixa2 = ixa2;
334 this->hidden->semaphore = SDL_CreateSemaphore(1);
335 if (this->hidden->semaphore == NULL) {
336 return SDL_SetError("XAudio2: CreateSemaphore() failed!");
337 }
338
339 while ((!valid_format) && (test_format)) {
340 switch (test_format) {
341 case AUDIO_U8:
342 case AUDIO_S16:
343 case AUDIO_S32:
344 case AUDIO_F32:
345 this->spec.format = test_format;
346 valid_format = 1;
347 break;
348 }
349 test_format = SDL_NextAudioFormat();
350 }
351
352 if (!valid_format) {
353 return SDL_SetError("XAudio2: Unsupported audio format");
354 }
355
356 /* Update the fragment size as size in bytes */
357 SDL_CalculateAudioSpec(&this->spec);
358
359 /* We feed a Source, it feeds the Mastering, which feeds the device. */
360 this->hidden->mixlen = this->spec.size;
361 this->hidden->mixbuf = (Uint8 *) SDL_malloc(2 * this->hidden->mixlen);
362 if (this->hidden->mixbuf == NULL) {
363 return SDL_OutOfMemory();
364 }
365 this->hidden->nextbuf = this->hidden->mixbuf;
366 SDL_memset(this->hidden->mixbuf, this->spec.silence, 2 * this->hidden->mixlen);
367
368 /* We use XAUDIO2_DEFAULT_CHANNELS instead of this->spec.channels. On
369 Xbox360, this means 5.1 output, but on Windows, it means "figure out
370 what the system has." It might be preferable to let XAudio2 blast
371 stereo output to appropriate surround sound configurations
372 instead of clamping to 2 channels, even though we'll configure the
373 Source Voice for whatever number of channels you supply. */
374 #if SDL_XAUDIO2_WIN8
375 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
376 XAUDIO2_DEFAULT_CHANNELS,
377 this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects);
378 #else
379 result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
380 XAUDIO2_DEFAULT_CHANNELS,
381 this->spec.freq, 0, devId, NULL);
382 #endif
383 if (result != S_OK) {
384 return SDL_SetError("XAudio2: Couldn't create mastering voice");
385 }
386
387 SDL_zero(waveformat);
388 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
389 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
390 } else {
391 waveformat.wFormatTag = WAVE_FORMAT_PCM;
392 }
393 waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
394 waveformat.nChannels = this->spec.channels;
395 waveformat.nSamplesPerSec = this->spec.freq;
396 waveformat.nBlockAlign =
397 waveformat.nChannels * (waveformat.wBitsPerSample / 8);
398 waveformat.nAvgBytesPerSec =
399 waveformat.nSamplesPerSec * waveformat.nBlockAlign;
400 waveformat.cbSize = sizeof(waveformat);
401
402 #ifdef __WINRT__
403 // DLudwig: for now, make XAudio2 do sample rate conversion, just to
404 // get the loopwave test to work.
405 //
406 // TODO, WinRT: consider removing WinRT-specific source-voice creation code from SDL_xaudio2.c
407 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
408 0,
409 1.0f, &callbacks, NULL, NULL);
410 #else
411 result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
412 XAUDIO2_VOICE_NOSRC |
413 XAUDIO2_VOICE_NOPITCH,
414 1.0f, &callbacks, NULL, NULL);
415
416 #endif
417 if (result != S_OK) {
418 return SDL_SetError("XAudio2: Couldn't create source voice");
419 }
420 this->hidden->source = source;
421
422 /* Start everything playing! */
423 result = IXAudio2_StartEngine(ixa2);
424 if (result != S_OK) {
425 return SDL_SetError("XAudio2: Couldn't start engine");
426 }
427
428 result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW);
429 if (result != S_OK) {
430 return SDL_SetError("XAudio2: Couldn't start source voice");
431 }
432
433 return 0; /* good to go. */
434 }
435
436 static void
XAUDIO2_Deinitialize(void)437 XAUDIO2_Deinitialize(void)
438 {
439 #if defined(__WIN32__)
440 WIN_CoUninitialize();
441 #endif
442 }
443
444 #endif /* SDL_XAUDIO2_HAS_SDK */
445
446
447 static int
XAUDIO2_Init(SDL_AudioDriverImpl * impl)448 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
449 {
450 #ifndef SDL_XAUDIO2_HAS_SDK
451 SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK).");
452 return 0; /* no XAudio2 support, ever. Update your SDK! */
453 #else
454 /* XAudio2Create() is a macro that uses COM; we don't load the .dll */
455 IXAudio2 *ixa2 = NULL;
456 #if defined(__WIN32__)
457 // TODO, WinRT: Investigate using CoInitializeEx here
458 if (FAILED(WIN_CoInitialize())) {
459 SDL_SetError("XAudio2: CoInitialize() failed");
460 return 0;
461 }
462 #endif
463
464 if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
465 #if defined(__WIN32__)
466 WIN_CoUninitialize();
467 #endif
468 SDL_SetError("XAudio2: XAudio2Create() failed at initialization");
469 return 0; /* not available. */
470 }
471 IXAudio2_Release(ixa2);
472
473 /* Set the function pointers */
474 impl->DetectDevices = XAUDIO2_DetectDevices;
475 impl->OpenDevice = XAUDIO2_OpenDevice;
476 impl->PlayDevice = XAUDIO2_PlayDevice;
477 impl->WaitDevice = XAUDIO2_WaitDevice;
478 impl->PrepareToClose = XAUDIO2_PrepareToClose;
479 impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf;
480 impl->CloseDevice = XAUDIO2_CloseDevice;
481 impl->Deinitialize = XAUDIO2_Deinitialize;
482
483 /* !!! FIXME: We can apparently use a C++ interface on Windows 8
484 * !!! FIXME: (Windows::Devices::Enumeration::DeviceInformation) for device
485 * !!! FIXME: detection, but it's not implemented here yet.
486 * !!! FIXME: see http://blogs.msdn.com/b/chuckw/archive/2012/04/02/xaudio2-and-windows-8-consumer-preview.aspx
487 * !!! FIXME: for now, force the default device.
488 */
489 #if defined(SDL_XAUDIO2_WIN8) || defined(__WINRT__)
490 impl->OnlyHasDefaultOutputDevice = 1;
491 #endif
492
493 return 1; /* this audio target is available. */
494 #endif
495 }
496
497 AudioBootStrap XAUDIO2_bootstrap = {
498 "xaudio2", "XAudio2", XAUDIO2_Init, 0
499 };
500
501 #endif /* SDL_AUDIO_DRIVER_XAUDIO2 */
502
503 /* vi: set ts=4 sw=4 expandtab: */
504