upgrade SDL to version 2.0.8
[platform/upstream/SDL.git] /
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
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
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
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... */
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) {}
180
181
182 static Uint8 *
183 XAUDIO2_GetDeviceBuf(_THIS)
184 {
185     return this->hidden->nextbuf;
186 }
187
188 static void
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 (!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
227 XAUDIO2_WaitDevice(_THIS)
228 {
229     if (this->enabled) {
230         SDL_SemWait(this->hidden->semaphore);
231     }
232 }
233
234 static void
235 XAUDIO2_WaitDone(_THIS)
236 {
237     IXAudio2SourceVoice *source = this->hidden->source;
238     XAUDIO2_VOICE_STATE state;
239     SDL_assert(!this->enabled);  /* flag that stops playing. */
240     IXAudio2SourceVoice_Discontinuity(source);
241 #if SDL_XAUDIO2_WIN8
242     IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
243 #else
244     IXAudio2SourceVoice_GetState(source, &state);
245 #endif
246     while (state.BuffersQueued > 0) {
247         SDL_SemWait(this->hidden->semaphore);
248 #if SDL_XAUDIO2_WIN8
249         IXAudio2SourceVoice_GetState(source, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
250 #else
251         IXAudio2SourceVoice_GetState(source, &state);
252 #endif
253     }
254 }
255
256
257 static void
258 XAUDIO2_CloseDevice(_THIS)
259 {
260     if (this->hidden != NULL) {
261         IXAudio2 *ixa2 = this->hidden->ixa2;
262         IXAudio2SourceVoice *source = this->hidden->source;
263         IXAudio2MasteringVoice *mastering = this->hidden->mastering;
264
265         if (source != NULL) {
266             IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW);
267             IXAudio2SourceVoice_FlushSourceBuffers(source);
268             IXAudio2SourceVoice_DestroyVoice(source);
269         }
270         if (ixa2 != NULL) {
271             IXAudio2_StopEngine(ixa2);
272         }
273         if (mastering != NULL) {
274             IXAudio2MasteringVoice_DestroyVoice(mastering);
275         }
276         if (ixa2 != NULL) {
277             IXAudio2_Release(ixa2);
278         }
279         SDL_free(this->hidden->mixbuf);
280         if (this->hidden->semaphore != NULL) {
281             SDL_DestroySemaphore(this->hidden->semaphore);
282         }
283
284         SDL_free(this->hidden);
285         this->hidden = NULL;
286     }
287 }
288
289 static int
290 XAUDIO2_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
291 {
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;
300 #else
301     UINT32 devId = 0;  /* 0 == system default device. */
302 #endif
303
304     static IXAudio2VoiceCallbackVtbl callbacks_vtable = {
305         VoiceCBOnVoiceProcessPassStart,
306         VoiceCBOnVoiceProcessPassEnd,
307         VoiceCBOnStreamEnd,
308         VoiceCBOnBufferStart,
309         VoiceCBOnBufferEnd,
310         VoiceCBOnLoopEnd,
311         VoiceCBOnVoiceError
312     };
313
314     static IXAudio2VoiceCallback callbacks = { &callbacks_vtable };
315
316 #if defined(SDL_XAUDIO2_WIN8)
317     /* !!! FIXME: hook up hotplugging. */
318 #else
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;
323     }
324 #endif
325
326     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
327         return SDL_SetError("XAudio2: XAudio2Create() failed at open.");
328     }
329
330     /*
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);
339     */
340
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();
347     }
348     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
349
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!");
355     }
356
357     while ((!valid_format) && (test_format)) {
358         switch (test_format) {
359         case AUDIO_U8:
360         case AUDIO_S16:
361         case AUDIO_S32:
362         case AUDIO_F32:
363             this->spec.format = test_format;
364             valid_format = 1;
365             break;
366         }
367         test_format = SDL_NextAudioFormat();
368     }
369
370     if (!valid_format) {
371         XAUDIO2_CloseDevice(this);
372         return SDL_SetError("XAudio2: Unsupported audio format");
373     }
374
375     /* Update the fragment size as size in bytes */
376     SDL_CalculateAudioSpec(&this->spec);
377
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();
384     }
385     this->hidden->nextbuf = this->hidden->mixbuf;
386     SDL_memset(this->hidden->mixbuf, 0, 2 * this->hidden->mixlen);
387
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. */
394 #if SDL_XAUDIO2_WIN8
395     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
396                                            XAUDIO2_DEFAULT_CHANNELS,
397                                            this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects);
398 #else
399     result = IXAudio2_CreateMasteringVoice(ixa2, &this->hidden->mastering,
400                                            XAUDIO2_DEFAULT_CHANNELS,
401                                            this->spec.freq, 0, devId, NULL);
402 #endif
403     if (result != S_OK) {
404         XAUDIO2_CloseDevice(this);
405         return SDL_SetError("XAudio2: Couldn't create mastering voice");
406     }
407
408     SDL_zero(waveformat);
409     if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
410         waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
411     } else {
412         waveformat.wFormatTag = WAVE_FORMAT_PCM;
413     }
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);
422
423 #ifdef __WINRT__
424     // DLudwig: for now, make XAudio2 do sample rate conversion, just to
425     // get the loopwave test to work.
426     //
427     // TODO, WinRT: consider removing WinRT-specific source-voice creation code from SDL_xaudio2.c
428     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
429                                         0,
430                                         1.0f, &callbacks, NULL, NULL);
431 #else
432     result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat,
433                                         XAUDIO2_VOICE_NOSRC |
434                                         XAUDIO2_VOICE_NOPITCH,
435                                         1.0f, &callbacks, NULL, NULL);
436
437 #endif
438     if (result != S_OK) {
439         XAUDIO2_CloseDevice(this);
440         return SDL_SetError("XAudio2: Couldn't create source voice");
441     }
442     this->hidden->source = source;
443
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");
449     }
450
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");
455     }
456
457     return 0; /* good to go. */
458 }
459
460 static void
461 XAUDIO2_Deinitialize(void)
462 {
463 #if defined(__WIN32__)
464     WIN_CoUninitialize();
465 #endif
466 }
467
468 #endif  /* SDL_XAUDIO2_HAS_SDK */
469
470
471 static int
472 XAUDIO2_Init(SDL_AudioDriverImpl * impl)
473 {
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! */
477 #else
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");
484         return 0;
485     }
486 #endif
487
488     if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) {
489 #if defined(__WIN32__)
490         WIN_CoUninitialize();
491 #endif
492         SDL_SetError("XAudio2: XAudio2Create() failed at initialization");
493         return 0;  /* not available. */
494     }
495     IXAudio2_Release(ixa2);
496
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;
506
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.
512      */
513 #if defined(SDL_XAUDIO2_WIN8) || defined(__WINRT__)
514     impl->OnlyHasDefaultOutputDevice = 1;
515 #endif
516
517     return 1;   /* this audio target is available. */
518 #endif
519 }
520
521 AudioBootStrap XAUDIO2_bootstrap = {
522     "xaudio2", "XAudio2", XAUDIO2_Init, 0
523 };
524
525 #endif  /* SDL_AUDIO_DRIVER_XAUDIO2 */
526
527 /* vi: set ts=4 sw=4 expandtab: */