1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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 #include "../../SDL_internal.h"
23 
24 #if SDL_AUDIO_DRIVER_WASAPI
25 
26 #include "../../core/windows/SDL_windows.h"
27 #include "SDL_audio.h"
28 #include "SDL_timer.h"
29 #include "../SDL_audio_c.h"
30 #include "../SDL_sysaudio.h"
31 #include "SDL_assert.h"
32 
33 #define COBJMACROS
34 #include <mmdeviceapi.h>
35 #include <audioclient.h>
36 
37 #include "SDL_wasapi.h"
38 
39 /* This constant isn't available on MinGW-w64 */
40 #ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
41 #define AUDCLNT_STREAMFLAGS_RATEADJUST  0x00100000
42 #endif
43 
44 /* these increment as default devices change. Opened default devices pick up changes in their threads. */
45 SDL_atomic_t WASAPI_DefaultPlaybackGeneration;
46 SDL_atomic_t WASAPI_DefaultCaptureGeneration;
47 
48 /* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */
49 typedef struct DevIdList
50 {
51     WCHAR *str;
52     struct DevIdList *next;
53 } DevIdList;
54 
55 static DevIdList *deviceid_list = NULL;
56 
57 /* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
58 static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
59 static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0,{ 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
60 static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
61 static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
62 
63 static SDL_bool
WStrEqual(const WCHAR * a,const WCHAR * b)64 WStrEqual(const WCHAR *a, const WCHAR *b)
65 {
66     while (*a) {
67         if (*a != *b) {
68             return SDL_FALSE;
69         }
70         a++;
71         b++;
72     }
73     return *b == 0;
74 }
75 
76 static size_t
WStrLen(const WCHAR * wstr)77 WStrLen(const WCHAR *wstr)
78 {
79     size_t retval = 0;
80     if (wstr) {
81         while (*(wstr++)) {
82             retval++;
83         }
84     }
85     return retval;
86 }
87 
88 static WCHAR *
WStrDupe(const WCHAR * wstr)89 WStrDupe(const WCHAR *wstr)
90 {
91     const size_t len = (WStrLen(wstr) + 1) * sizeof (WCHAR);
92     WCHAR *retval = (WCHAR *) SDL_malloc(len);
93     if (retval) {
94         SDL_memcpy(retval, wstr, len);
95     }
96     return retval;
97 }
98 
99 
100 void
WASAPI_RemoveDevice(const SDL_bool iscapture,LPCWSTR devid)101 WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
102 {
103     DevIdList *i;
104     DevIdList *next;
105     DevIdList *prev = NULL;
106     for (i = deviceid_list; i; i = next) {
107         next = i->next;
108         if (WStrEqual(i->str, devid)) {
109             if (prev) {
110                 prev->next = next;
111             } else {
112                 deviceid_list = next;
113             }
114             SDL_RemoveAudioDevice(iscapture, i->str);
115             SDL_free(i->str);
116             SDL_free(i);
117         }
118         prev = i;
119     }
120 }
121 
122 void
WASAPI_AddDevice(const SDL_bool iscapture,const char * devname,LPCWSTR devid)123 WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
124 {
125     DevIdList *devidlist;
126 
127     /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
128        In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
129        phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
130        available and switch automatically. (!!! FIXME...?) */
131 
132     /* see if we already have this one. */
133     for (devidlist = deviceid_list; devidlist; devidlist = devidlist->next) {
134         if (WStrEqual(devidlist->str, devid)) {
135             return;  /* we already have this. */
136         }
137     }
138 
139     devidlist = (DevIdList *) SDL_malloc(sizeof (*devidlist));
140     if (!devidlist) {
141         return;  /* oh well. */
142     }
143 
144     devid = WStrDupe(devid);
145     if (!devid) {
146         SDL_free(devidlist);
147         return;  /* oh well. */
148     }
149 
150     devidlist->str = (WCHAR *) devid;
151     devidlist->next = deviceid_list;
152     deviceid_list = devidlist;
153 
154     SDL_AddAudioDevice(iscapture, devname, (void *) devid);
155 }
156 
157 static void
WASAPI_DetectDevices(void)158 WASAPI_DetectDevices(void)
159 {
160     WASAPI_EnumerateEndpoints();
161 }
162 
163 static SDL_INLINE SDL_bool
WasapiFailed(_THIS,const HRESULT err)164 WasapiFailed(_THIS, const HRESULT err)
165 {
166     if (err == S_OK) {
167         return SDL_FALSE;
168     }
169 
170     if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
171         this->hidden->device_lost = SDL_TRUE;
172     } else if (SDL_AtomicGet(&this->enabled)) {
173         IAudioClient_Stop(this->hidden->client);
174         SDL_OpenedAudioDeviceDisconnected(this);
175         SDL_assert(!SDL_AtomicGet(&this->enabled));
176     }
177 
178     return SDL_TRUE;
179 }
180 
181 static int
UpdateAudioStream(_THIS,const SDL_AudioSpec * oldspec)182 UpdateAudioStream(_THIS, const SDL_AudioSpec *oldspec)
183 {
184     /* Since WASAPI requires us to handle all audio conversion, and our
185        device format might have changed, we might have to add/remove/change
186        the audio stream that the higher level uses to convert data, so
187        SDL keeps firing the callback as if nothing happened here. */
188 
189     if ( (this->callbackspec.channels == this->spec.channels) &&
190          (this->callbackspec.format == this->spec.format) &&
191          (this->callbackspec.freq == this->spec.freq) &&
192          (this->callbackspec.samples == this->spec.samples) ) {
193         /* no need to buffer/convert in an AudioStream! */
194         SDL_FreeAudioStream(this->stream);
195         this->stream = NULL;
196     } else if ( (oldspec->channels == this->spec.channels) &&
197          (oldspec->format == this->spec.format) &&
198          (oldspec->freq == this->spec.freq) ) {
199         /* The existing audio stream is okay to keep using. */
200     } else {
201         /* replace the audiostream for new format */
202         SDL_FreeAudioStream(this->stream);
203         if (this->iscapture) {
204             this->stream = SDL_NewAudioStream(this->spec.format,
205                                 this->spec.channels, this->spec.freq,
206                                 this->callbackspec.format,
207                                 this->callbackspec.channels,
208                                 this->callbackspec.freq);
209         } else {
210             this->stream = SDL_NewAudioStream(this->callbackspec.format,
211                                 this->callbackspec.channels,
212                                 this->callbackspec.freq, this->spec.format,
213                                 this->spec.channels, this->spec.freq);
214         }
215 
216         if (!this->stream) {
217             return -1;
218         }
219     }
220 
221     /* make sure our scratch buffer can cover the new device spec. */
222     if (this->spec.size > this->work_buffer_len) {
223         Uint8 *ptr = (Uint8 *) SDL_realloc(this->work_buffer, this->spec.size);
224         if (ptr == NULL) {
225             return SDL_OutOfMemory();
226         }
227         this->work_buffer = ptr;
228         this->work_buffer_len = this->spec.size;
229     }
230 
231     return 0;
232 }
233 
234 
235 static void ReleaseWasapiDevice(_THIS);
236 
237 static SDL_bool
RecoverWasapiDevice(_THIS)238 RecoverWasapiDevice(_THIS)
239 {
240     ReleaseWasapiDevice(this);  /* dump the lost device's handles. */
241 
242     if (this->hidden->default_device_generation) {
243         this->hidden->default_device_generation = SDL_AtomicGet(this->iscapture ?  &WASAPI_DefaultCaptureGeneration : &WASAPI_DefaultPlaybackGeneration);
244     }
245 
246     /* this can fail for lots of reasons, but the most likely is we had a
247        non-default device that was disconnected, so we can't recover. Default
248        devices try to reinitialize whatever the new default is, so it's more
249        likely to carry on here, but this handles a non-default device that
250        simply had its format changed in the Windows Control Panel. */
251     if (WASAPI_ActivateDevice(this, SDL_TRUE) == -1) {
252         SDL_OpenedAudioDeviceDisconnected(this);
253         return SDL_FALSE;
254     }
255 
256     this->hidden->device_lost = SDL_FALSE;
257 
258     return SDL_TRUE;  /* okay, carry on with new device details! */
259 }
260 
261 static SDL_bool
RecoverWasapiIfLost(_THIS)262 RecoverWasapiIfLost(_THIS)
263 {
264     const int generation = this->hidden->default_device_generation;
265     SDL_bool lost = this->hidden->device_lost;
266 
267     if (!SDL_AtomicGet(&this->enabled)) {
268         return SDL_FALSE;  /* already failed. */
269     }
270 
271     if (!this->hidden->client) {
272         return SDL_TRUE;  /* still waiting for activation. */
273     }
274 
275     if (!lost && (generation > 0)) { /* is a default device? */
276         const int newgen = SDL_AtomicGet(this->iscapture ? &WASAPI_DefaultCaptureGeneration : &WASAPI_DefaultPlaybackGeneration);
277         if (generation != newgen) {  /* the desired default device was changed, jump over to it. */
278             lost = SDL_TRUE;
279         }
280     }
281 
282     return lost ? RecoverWasapiDevice(this) : SDL_TRUE;
283 }
284 
285 static Uint8 *
WASAPI_GetDeviceBuf(_THIS)286 WASAPI_GetDeviceBuf(_THIS)
287 {
288     /* get an endpoint buffer from WASAPI. */
289     BYTE *buffer = NULL;
290 
291     while (RecoverWasapiIfLost(this) && this->hidden->render) {
292         if (!WasapiFailed(this, IAudioRenderClient_GetBuffer(this->hidden->render, this->spec.samples, &buffer))) {
293             return (Uint8 *) buffer;
294         }
295         SDL_assert(buffer == NULL);
296     }
297 
298     return (Uint8 *) buffer;
299 }
300 
301 static void
WASAPI_PlayDevice(_THIS)302 WASAPI_PlayDevice(_THIS)
303 {
304     if (this->hidden->render != NULL) {  /* definitely activated? */
305         /* WasapiFailed() will mark the device for reacquisition or removal elsewhere. */
306         WasapiFailed(this, IAudioRenderClient_ReleaseBuffer(this->hidden->render, this->spec.samples, 0));
307     }
308 }
309 
310 static void
WASAPI_WaitDevice(_THIS)311 WASAPI_WaitDevice(_THIS)
312 {
313     while (RecoverWasapiIfLost(this) && this->hidden->client && this->hidden->event) {
314         DWORD waitResult = WaitForSingleObjectEx(this->hidden->event, 200, FALSE);
315         if (waitResult == WAIT_OBJECT_0) {
316             const UINT32 maxpadding = this->spec.samples;
317             UINT32 padding = 0;
318             if (!WasapiFailed(this, IAudioClient_GetCurrentPadding(this->hidden->client, &padding))) {
319                 /*SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
320                 if (padding <= maxpadding) {
321                     break;
322                 }
323             }
324         } else if (waitResult != WAIT_TIMEOUT) {
325             /*SDL_Log("WASAPI FAILED EVENT!");*/
326             IAudioClient_Stop(this->hidden->client);
327             SDL_OpenedAudioDeviceDisconnected(this);
328         }
329     }
330 }
331 
332 static int
WASAPI_CaptureFromDevice(_THIS,void * buffer,int buflen)333 WASAPI_CaptureFromDevice(_THIS, void *buffer, int buflen)
334 {
335     SDL_AudioStream *stream = this->hidden->capturestream;
336     const int avail = SDL_AudioStreamAvailable(stream);
337     if (avail > 0) {
338         const int cpy = SDL_min(buflen, avail);
339         SDL_AudioStreamGet(stream, buffer, cpy);
340         return cpy;
341     }
342 
343     while (RecoverWasapiIfLost(this)) {
344         HRESULT ret;
345         BYTE *ptr = NULL;
346         UINT32 frames = 0;
347         DWORD flags = 0;
348 
349         /* uhoh, client isn't activated yet, just return silence. */
350         if (!this->hidden->capture) {
351             /* Delay so we run at about the speed that audio would be arriving. */
352             SDL_Delay(((this->spec.samples * 1000) / this->spec.freq));
353             SDL_memset(buffer, this->spec.silence, buflen);
354             return buflen;
355         }
356 
357         ret = IAudioCaptureClient_GetBuffer(this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
358         if (ret != AUDCLNT_S_BUFFER_EMPTY) {
359             WasapiFailed(this, ret); /* mark device lost/failed if necessary. */
360         }
361 
362         if ((ret == AUDCLNT_S_BUFFER_EMPTY) || !frames) {
363             WASAPI_WaitDevice(this);
364         } else if (ret == S_OK) {
365             const int total = ((int) frames) * this->hidden->framesize;
366             const int cpy = SDL_min(buflen, total);
367             const int leftover = total - cpy;
368             const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
369 
370             if (silent) {
371                 SDL_memset(buffer, this->spec.silence, cpy);
372             } else {
373                 SDL_memcpy(buffer, ptr, cpy);
374             }
375 
376             if (leftover > 0) {
377                 ptr += cpy;
378                 if (silent) {
379                     SDL_memset(ptr, this->spec.silence, leftover);  /* I guess this is safe? */
380                 }
381 
382                 if (SDL_AudioStreamPut(stream, ptr, leftover) == -1) {
383                     return -1;  /* uhoh, out of memory, etc. Kill device.  :( */
384                 }
385             }
386 
387             ret = IAudioCaptureClient_ReleaseBuffer(this->hidden->capture, frames);
388             WasapiFailed(this, ret); /* mark device lost/failed if necessary. */
389 
390             return cpy;
391         }
392     }
393 
394     return -1;  /* unrecoverable error. */
395 }
396 
397 static void
WASAPI_FlushCapture(_THIS)398 WASAPI_FlushCapture(_THIS)
399 {
400     BYTE *ptr = NULL;
401     UINT32 frames = 0;
402     DWORD flags = 0;
403 
404     if (!this->hidden->capture) {
405         return;  /* not activated yet? */
406     }
407 
408     /* just read until we stop getting packets, throwing them away. */
409     while (SDL_TRUE) {
410         const HRESULT ret = IAudioCaptureClient_GetBuffer(this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
411         if (ret == AUDCLNT_S_BUFFER_EMPTY) {
412             break;  /* no more buffered data; we're done. */
413         } else if (WasapiFailed(this, ret)) {
414             break;  /* failed for some other reason, abort. */
415         } else if (WasapiFailed(this, IAudioCaptureClient_ReleaseBuffer(this->hidden->capture, frames))) {
416             break;  /* something broke. */
417         }
418     }
419     SDL_AudioStreamClear(this->hidden->capturestream);
420 }
421 
422 static void
ReleaseWasapiDevice(_THIS)423 ReleaseWasapiDevice(_THIS)
424 {
425     if (this->hidden->client) {
426         IAudioClient_Stop(this->hidden->client);
427         IAudioClient_SetEventHandle(this->hidden->client, NULL);
428         IAudioClient_Release(this->hidden->client);
429         this->hidden->client = NULL;
430     }
431 
432     if (this->hidden->render) {
433         IAudioRenderClient_Release(this->hidden->render);
434         this->hidden->render = NULL;
435     }
436 
437     if (this->hidden->capture) {
438         IAudioCaptureClient_Release(this->hidden->capture);
439         this->hidden->capture = NULL;
440     }
441 
442     if (this->hidden->waveformat) {
443         CoTaskMemFree(this->hidden->waveformat);
444         this->hidden->waveformat = NULL;
445     }
446 
447     if (this->hidden->capturestream) {
448         SDL_FreeAudioStream(this->hidden->capturestream);
449         this->hidden->capturestream = NULL;
450     }
451 
452     if (this->hidden->activation_handler) {
453         WASAPI_PlatformDeleteActivationHandler(this->hidden->activation_handler);
454         this->hidden->activation_handler = NULL;
455     }
456 
457     if (this->hidden->event) {
458         CloseHandle(this->hidden->event);
459         this->hidden->event = NULL;
460     }
461 }
462 
463 static void
WASAPI_CloseDevice(_THIS)464 WASAPI_CloseDevice(_THIS)
465 {
466     WASAPI_UnrefDevice(this);
467 }
468 
469 void
WASAPI_RefDevice(_THIS)470 WASAPI_RefDevice(_THIS)
471 {
472     SDL_AtomicIncRef(&this->hidden->refcount);
473 }
474 
475 void
WASAPI_UnrefDevice(_THIS)476 WASAPI_UnrefDevice(_THIS)
477 {
478     if (!SDL_AtomicDecRef(&this->hidden->refcount)) {
479         return;
480     }
481 
482     /* actual closing happens here. */
483 
484     /* don't touch this->hidden->task in here; it has to be reverted from
485        our callback thread. We do that in WASAPI_ThreadDeinit().
486        (likewise for this->hidden->coinitialized). */
487     ReleaseWasapiDevice(this);
488     SDL_free(this->hidden->devid);
489     SDL_free(this->hidden);
490 }
491 
492 /* This is called once a device is activated, possibly asynchronously. */
493 int
WASAPI_PrepDevice(_THIS,const SDL_bool updatestream)494 WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
495 {
496     /* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
497        !!!  it will write into the kernel's audio buffer directly instead of
498        !!!  shared memory that a user-mode mixer then writes to the kernel with
499        !!!  everything else. Doing this means any other sound using this device will
500        !!!  stop playing, including the user's MP3 player and system notification
501        !!!  sounds. You'd probably need to release the device when the app isn't in
502        !!!  the foreground, to be a good citizen of the system. It's doable, but it's
503        !!!  more work and causes some annoyances, and I don't know what the latency
504        !!!  wins actually look like. Maybe add a hint to force exclusive mode at
505        !!!  some point. To be sure, defaulting to shared mode is the right thing to
506        !!!  do in any case. */
507     const SDL_AudioSpec oldspec = this->spec;
508     const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
509     UINT32 bufsize = 0;  /* this is in sample frames, not samples, not bytes. */
510     REFERENCE_TIME default_period = 0;
511     IAudioClient *client = this->hidden->client;
512     IAudioRenderClient *render = NULL;
513     IAudioCaptureClient *capture = NULL;
514     WAVEFORMATEX *waveformat = NULL;
515     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
516     SDL_AudioFormat wasapi_format = 0;
517     SDL_bool valid_format = SDL_FALSE;
518     HRESULT ret = S_OK;
519     DWORD streamflags = 0;
520 
521     SDL_assert(client != NULL);
522 
523 #ifdef __WINRT__  /* CreateEventEx() arrived in Vista, so we need an #ifdef for XP. */
524     this->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
525 #else
526     this->hidden->event = CreateEventW(NULL, 0, 0, NULL);
527 #endif
528 
529     if (this->hidden->event == NULL) {
530         return WIN_SetError("WASAPI can't create an event handle");
531     }
532 
533     ret = IAudioClient_GetMixFormat(client, &waveformat);
534     if (FAILED(ret)) {
535         return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
536     }
537 
538     SDL_assert(waveformat != NULL);
539     this->hidden->waveformat = waveformat;
540 
541     this->spec.channels = (Uint8) waveformat->nChannels;
542 
543     /* Make sure we have a valid format that we can convert to whatever WASAPI wants. */
544     if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
545         wasapi_format = AUDIO_F32SYS;
546     } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
547         wasapi_format = AUDIO_S16SYS;
548     } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
549         wasapi_format = AUDIO_S32SYS;
550     } else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
551         const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *) waveformat;
552         if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
553             wasapi_format = AUDIO_F32SYS;
554         } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
555             wasapi_format = AUDIO_S16SYS;
556         } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
557             wasapi_format = AUDIO_S32SYS;
558         }
559     }
560 
561     while ((!valid_format) && (test_format)) {
562         if (test_format == wasapi_format) {
563             this->spec.format = test_format;
564             valid_format = SDL_TRUE;
565             break;
566         }
567         test_format = SDL_NextAudioFormat();
568     }
569 
570     if (!valid_format) {
571         return SDL_SetError("WASAPI: Unsupported audio format");
572     }
573 
574     ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
575     if (FAILED(ret)) {
576         return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
577     }
578 
579     /* favor WASAPI's resampler over our own, in Win7+. */
580     if (this->spec.freq != waveformat->nSamplesPerSec) {
581         /* RATEADJUST only works with output devices in share mode, and is available in Win7 and later.*/
582         if (WIN_IsWindows7OrGreater() && !this->iscapture && (sharemode == AUDCLNT_SHAREMODE_SHARED)) {
583             streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST;
584             waveformat->nSamplesPerSec = this->spec.freq;
585             waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
586         }
587         else {
588             this->spec.freq = waveformat->nSamplesPerSec;  /* force sampling rate so our resampler kicks in. */
589         }
590     }
591 
592     streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
593     ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
594     if (FAILED(ret)) {
595         return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
596     }
597 
598     ret = IAudioClient_SetEventHandle(client, this->hidden->event);
599     if (FAILED(ret)) {
600         return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
601     }
602 
603     ret = IAudioClient_GetBufferSize(client, &bufsize);
604     if (FAILED(ret)) {
605         return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
606     }
607 
608     /* Match the callback size to the period size to cut down on the number of
609        interrupts waited for in each call to WaitDevice */
610     {
611         const float period_millis = default_period / 10000.0f;
612         const float period_frames = period_millis * this->spec.freq / 1000.0f;
613         this->spec.samples = (Uint16)SDL_ceilf(period_frames);
614     }
615 
616     /* Update the fragment size as size in bytes */
617     SDL_CalculateAudioSpec(&this->spec);
618 
619     this->hidden->framesize = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels;
620 
621     if (this->iscapture) {
622         this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
623         if (!this->hidden->capturestream) {
624             return -1;  /* already set SDL_Error */
625         }
626 
627         ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void**) &capture);
628         if (FAILED(ret)) {
629             return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
630         }
631 
632         SDL_assert(capture != NULL);
633         this->hidden->capture = capture;
634         ret = IAudioClient_Start(client);
635         if (FAILED(ret)) {
636             return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
637         }
638 
639         WASAPI_FlushCapture(this);  /* MSDN says you should flush capture endpoint right after startup. */
640     } else {
641         ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void**) &render);
642         if (FAILED(ret)) {
643             return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
644         }
645 
646         SDL_assert(render != NULL);
647         this->hidden->render = render;
648         ret = IAudioClient_Start(client);
649         if (FAILED(ret)) {
650             return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
651         }
652     }
653 
654     if (updatestream) {
655         if (UpdateAudioStream(this, &oldspec) == -1) {
656             return -1;
657         }
658     }
659 
660     return 0;  /* good to go. */
661 }
662 
663 
664 static int
WASAPI_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)665 WASAPI_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
666 {
667     LPCWSTR devid = (LPCWSTR) handle;
668 
669     /* Initialize all variables that we clean on shutdown */
670     this->hidden = (struct SDL_PrivateAudioData *)
671         SDL_malloc((sizeof *this->hidden));
672     if (this->hidden == NULL) {
673         return SDL_OutOfMemory();
674     }
675     SDL_zerop(this->hidden);
676 
677     WASAPI_RefDevice(this);   /* so CloseDevice() will unref to zero. */
678 
679     if (!devid) {  /* is default device? */
680         this->hidden->default_device_generation = SDL_AtomicGet(iscapture ? &WASAPI_DefaultCaptureGeneration : &WASAPI_DefaultPlaybackGeneration);
681     } else {
682         this->hidden->devid = WStrDupe(devid);
683         if (!this->hidden->devid) {
684             return SDL_OutOfMemory();
685         }
686     }
687 
688     if (WASAPI_ActivateDevice(this, SDL_FALSE) == -1) {
689         return -1;  /* already set error. */
690     }
691 
692     /* Ready, but waiting for async device activation.
693        Until activation is successful, we will report silence from capture
694        devices and ignore data on playback devices.
695        Also, since we don't know the _actual_ device format until after
696        activation, we let the app have whatever it asks for. We set up
697        an SDL_AudioStream to convert, if necessary, once the activation
698        completes. */
699 
700     return 0;
701 }
702 
703 static void
WASAPI_ThreadInit(_THIS)704 WASAPI_ThreadInit(_THIS)
705 {
706     WASAPI_PlatformThreadInit(this);
707 }
708 
709 static void
WASAPI_ThreadDeinit(_THIS)710 WASAPI_ThreadDeinit(_THIS)
711 {
712     WASAPI_PlatformThreadDeinit(this);
713 }
714 
715 void
WASAPI_BeginLoopIteration(_THIS)716 WASAPI_BeginLoopIteration(_THIS)
717 {
718 	/* no-op. */
719 }
720 
721 static void
WASAPI_Deinitialize(void)722 WASAPI_Deinitialize(void)
723 {
724     DevIdList *devidlist;
725     DevIdList *next;
726 
727     WASAPI_PlatformDeinit();
728 
729     for (devidlist = deviceid_list; devidlist; devidlist = next) {
730         next = devidlist->next;
731         SDL_free(devidlist->str);
732         SDL_free(devidlist);
733     }
734     deviceid_list = NULL;
735 }
736 
737 static int
WASAPI_Init(SDL_AudioDriverImpl * impl)738 WASAPI_Init(SDL_AudioDriverImpl * impl)
739 {
740     SDL_AtomicSet(&WASAPI_DefaultPlaybackGeneration, 1);
741     SDL_AtomicSet(&WASAPI_DefaultCaptureGeneration, 1);
742 
743     if (WASAPI_PlatformInit() == -1) {
744         return 0;
745     }
746 
747     /* Set the function pointers */
748     impl->DetectDevices = WASAPI_DetectDevices;
749     impl->ThreadInit = WASAPI_ThreadInit;
750     impl->ThreadDeinit = WASAPI_ThreadDeinit;
751     impl->BeginLoopIteration = WASAPI_BeginLoopIteration;
752     impl->OpenDevice = WASAPI_OpenDevice;
753     impl->PlayDevice = WASAPI_PlayDevice;
754     impl->WaitDevice = WASAPI_WaitDevice;
755     impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
756     impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
757     impl->FlushCapture = WASAPI_FlushCapture;
758     impl->CloseDevice = WASAPI_CloseDevice;
759     impl->Deinitialize = WASAPI_Deinitialize;
760     impl->HasCaptureSupport = 1;
761 
762     return 1;   /* this audio target is available. */
763 }
764 
765 AudioBootStrap WASAPI_bootstrap = {
766     "wasapi", "WASAPI", WASAPI_Init, 0
767 };
768 
769 #endif  /* SDL_AUDIO_DRIVER_WASAPI */
770 
771 /* vi: set ts=4 sw=4 expandtab: */
772