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