SDL  2.0
SDL_wasapi.c
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2017 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 #include "SDL_log.h"
33 
34 #define COBJMACROS
35 #include <mmdeviceapi.h>
36 #include <audioclient.h>
37 
38 #include "SDL_wasapi.h"
39 
40 static const ERole SDL_WASAPI_role = eConsole; /* !!! FIXME: should this be eMultimedia? Should be a hint? */
41 
42 /* This is global to the WASAPI target, to handle hotplug and default device lookup. */
43 static IMMDeviceEnumerator *enumerator = NULL;
44 
45 /* these increment as default devices change. Opened default devices pick up changes in their threads. */
46 static SDL_atomic_t default_playback_generation;
47 static SDL_atomic_t default_capture_generation;
48 
49 /* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */
50 typedef struct DevIdList
51 {
52  WCHAR *str;
53  struct DevIdList *next;
54 } DevIdList;
55 
56 static DevIdList *deviceid_list = NULL;
57 
58 /* handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). */
59 #ifndef __WINRT__
60 static HMODULE libavrt = NULL;
61 #endif
62 typedef HANDLE (WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPWSTR,LPDWORD);
63 typedef BOOL (WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
64 static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
65 static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
66 
67 /* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
68 static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
69 static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
70 static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85, { 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } };
71 static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089, { 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } };
72 static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
73 static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
74 static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
75 static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
76 static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
77 static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
78 
79 /* PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. */
80 #ifdef PropVariantInit
81 #undef PropVariantInit
82 #endif
83 #define PropVariantInit(p) SDL_zerop(p)
84 
85 static void AddWASAPIDevice(const SDL_bool iscapture, IMMDevice *device, LPCWSTR devid);
86 static void RemoveWASAPIDevice(const SDL_bool iscapture, LPCWSTR devid);
87 
88 /* We need a COM subclass of IMMNotificationClient for hotplug support, which is
89  easy in C++, but we have to tapdance more to make work in C.
90  Thanks to this page for coaching on how to make this work:
91  https://www.codeproject.com/Articles/13601/COM-in-plain-C */
92 
93 typedef struct SDLMMNotificationClient
94 {
95  const IMMNotificationClientVtbl *lpVtbl;
96  SDL_atomic_t refcount;
97 } SDLMMNotificationClient;
98 
99 static HRESULT STDMETHODCALLTYPE
100 SDLMMNotificationClient_QueryInterface(IMMNotificationClient *this, REFIID iid, void **ppv)
101 {
102  if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient)))
103  {
104  *ppv = this;
105  this->lpVtbl->AddRef(this);
106  return S_OK;
107  }
108 
109  *ppv = NULL;
110  return E_NOINTERFACE;
111 }
112 
113 static ULONG STDMETHODCALLTYPE
114 SDLMMNotificationClient_AddRef(IMMNotificationClient *ithis)
115 {
116  SDLMMNotificationClient *this = (SDLMMNotificationClient *) ithis;
117  return (ULONG) (SDL_AtomicIncRef(&this->refcount) + 1);
118 }
119 
120 static ULONG STDMETHODCALLTYPE
121 SDLMMNotificationClient_Release(IMMNotificationClient *ithis)
122 {
123  /* this is a static object; we don't ever free it. */
124  SDLMMNotificationClient *this = (SDLMMNotificationClient *) ithis;
125  const ULONG retval = SDL_AtomicDecRef(&this->refcount);
126  if (retval == 0) {
127  SDL_AtomicSet(&this->refcount, 0); /* uhh... */
128  return 0;
129  }
130  return retval - 1;
131 }
132 
133 /* These are the entry points called when WASAPI device endpoints change. */
134 static HRESULT STDMETHODCALLTYPE
135 SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *ithis, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
136 {
137  if (role != SDL_WASAPI_role) {
138  return S_OK; /* ignore it. */
139  }
140 
141  /* Increment the "generation," so opened devices will pick this up in their threads. */
142  switch (flow) {
143  case eRender:
144  SDL_AtomicAdd(&default_playback_generation, 1);
145  break;
146 
147  case eCapture:
148  SDL_AtomicAdd(&default_capture_generation, 1);
149  break;
150 
151  case eAll:
152  SDL_AtomicAdd(&default_playback_generation, 1);
153  SDL_AtomicAdd(&default_capture_generation, 1);
154  break;
155 
156  default:
157  SDL_assert(!"uhoh, unexpected OnDefaultDeviceChange flow!");
158  break;
159  }
160 
161  return S_OK;
162 }
163 
164 static HRESULT STDMETHODCALLTYPE
165 SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
166 {
167  /* we ignore this; devices added here then progress to ACTIVE, if appropriate, in
168  OnDeviceStateChange, making that a better place to deal with device adds. More
169  importantly: the first time you plug in a USB audio device, this callback will
170  fire, but when you unplug it, it isn't removed (it's state changes to NOTPRESENT).
171  Plugging it back in won't fire this callback again. */
172  return S_OK;
173 }
174 
175 static HRESULT STDMETHODCALLTYPE
176 SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
177 {
178  /* See notes in OnDeviceAdded handler about why we ignore this. */
179  return S_OK;
180 }
181 
182 static HRESULT STDMETHODCALLTYPE
183 SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId, DWORD dwNewState)
184 {
185  IMMDevice *device = NULL;
186 
187  if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) {
188  IMMEndpoint *endpoint = NULL;
189  if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **) &endpoint))) {
190  EDataFlow flow;
191  if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) {
192  const SDL_bool iscapture = (flow == eCapture);
193  if (dwNewState == DEVICE_STATE_ACTIVE) {
194  AddWASAPIDevice(iscapture, device, pwstrDeviceId);
195  } else {
196  RemoveWASAPIDevice(iscapture, pwstrDeviceId);
197  }
198  }
199  IMMEndpoint_Release(endpoint);
200  }
201  IMMDevice_Release(device);
202  }
203 
204  return S_OK;
205 }
206 
207 static HRESULT STDMETHODCALLTYPE
208 SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this, LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
209 {
210  return S_OK; /* we don't care about these. */
211 }
212 
213 static const IMMNotificationClientVtbl notification_client_vtbl = {
214  SDLMMNotificationClient_QueryInterface,
215  SDLMMNotificationClient_AddRef,
216  SDLMMNotificationClient_Release,
217  SDLMMNotificationClient_OnDeviceStateChanged,
218  SDLMMNotificationClient_OnDeviceAdded,
219  SDLMMNotificationClient_OnDeviceRemoved,
220  SDLMMNotificationClient_OnDefaultDeviceChanged,
221  SDLMMNotificationClient_OnPropertyValueChanged
222 };
223 
224 static SDLMMNotificationClient notification_client = { &notification_client_vtbl, { 1 } };
225 
226 static SDL_bool
227 WStrEqual(const WCHAR *a, const WCHAR *b)
228 {
229  while (*a) {
230  if (*a != *b) {
231  return SDL_FALSE;
232  }
233  a++;
234  b++;
235  }
236  return *b == 0;
237 }
238 
239 static WCHAR *
240 WStrDupe(const WCHAR *wstr)
241 {
242  const int len = (lstrlenW(wstr) + 1) * sizeof (WCHAR);
243  WCHAR *retval = (WCHAR *) SDL_malloc(len);
244  if (retval) {
245  SDL_memcpy(retval, wstr, len);
246  }
247  return retval;
248 }
249 
250 static void
251 RemoveWASAPIDevice(const SDL_bool iscapture, LPCWSTR devid)
252 {
253  DevIdList *i;
254  DevIdList *next;
255  DevIdList *prev = NULL;
256  for (i = deviceid_list; i; i = next) {
257  next = i->next;
258  if (WStrEqual(i->str, devid)) {
259  if (prev) {
260  prev->next = next;
261  } else {
262  deviceid_list = next;
263  }
264  SDL_RemoveAudioDevice(iscapture, i->str);
265  SDL_free(i->str);
266  SDL_free(i);
267  }
268  prev = i;
269  }
270 }
271 
272 static void
273 AddWASAPIDevice(const SDL_bool iscapture, IMMDevice *device, LPCWSTR devid)
274 {
275  IPropertyStore *props = NULL;
276  char *utf8dev = NULL;
277  DevIdList *devidlist;
278  PROPVARIANT var;
279 
280  /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
281  In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
282  phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
283  available and switch automatically. (!!! FIXME...?) */
284 
285  /* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be
286  "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
287  its own UIs, like Volume Control, etc. */
288 
289  /* see if we already have this one. */
290  for (devidlist = deviceid_list; devidlist; devidlist = devidlist->next) {
291  if (WStrEqual(devidlist->str, devid)) {
292  return; /* we already have this. */
293  }
294  }
295 
296  devidlist = (DevIdList *) SDL_malloc(sizeof (*devidlist));
297  if (!devidlist) {
298  return; /* oh well. */
299  }
300 
301  devid = WStrDupe(devid);
302  if (!devid) {
303  SDL_free(devidlist);
304  return; /* oh well. */
305  }
306 
307  devidlist->str = (WCHAR *) devid;
308  devidlist->next = deviceid_list;
309  deviceid_list = devidlist;
310 
311  if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
312  PropVariantInit(&var);
313  if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) {
314  utf8dev = WIN_StringToUTF8(var.pwszVal);
315  if (utf8dev) {
316  SDL_AddAudioDevice(iscapture, utf8dev, (void *) devid);
317  SDL_free(utf8dev);
318  }
319  }
320  PropVariantClear(&var);
321  IPropertyStore_Release(props);
322  }
323 }
324 
325 static void
326 EnumerateEndpoints(const SDL_bool iscapture)
327 {
328  IMMDeviceCollection *collection = NULL;
329  UINT i, total;
330 
331  /* Note that WASAPI separates "adapter devices" from "audio endpoint devices"
332  ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */
333 
334  if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, iscapture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) {
335  return;
336  }
337 
338  if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) {
339  IMMDeviceCollection_Release(collection);
340  return;
341  }
342 
343  for (i = 0; i < total; i++) {
344  IMMDevice *device = NULL;
345  if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) {
346  LPWSTR devid = NULL;
347  if (SUCCEEDED(IMMDevice_GetId(device, &devid))) {
348  AddWASAPIDevice(iscapture, device, devid);
349  CoTaskMemFree(devid);
350  }
351  IMMDevice_Release(device);
352  }
353  }
354 
355  IMMDeviceCollection_Release(collection);
356 }
357 
358 static void
359 WASAPI_DetectDevices(void)
360 {
361  EnumerateEndpoints(SDL_FALSE); /* playback */
362  EnumerateEndpoints(SDL_TRUE); /* capture */
363 
364  /* if this fails, we just won't get hotplug events. Carry on anyhow. */
365  IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) &notification_client);
366 }
367 
368 static int
369 WASAPI_GetPendingBytes(_THIS)
370 {
371  UINT32 frames = 0;
372 
373  /* it's okay to fail here; we'll deal with failures in the audio thread. */
374  /* FIXME: need a lock around checking this->hidden->client */
375  if (!this->hidden->client || FAILED(IAudioClient_GetCurrentPadding(this->hidden->client, &frames))) {
376  return 0; /* oh well. */
377  }
378 
379  return ((int) frames) * this->hidden->framesize;
380 }
381 
382 static SDL_INLINE SDL_bool
383 WasapiFailed(_THIS, const HRESULT err)
384 {
385  if (err == S_OK) {
386  return SDL_FALSE;
387  }
388 
389  if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
390  this->hidden->device_lost = SDL_TRUE;
391  } else if (SDL_AtomicGet(&this->enabled)) {
392  IAudioClient_Stop(this->hidden->client);
394  SDL_assert(!SDL_AtomicGet(&this->enabled));
395  }
396 
397  return SDL_TRUE;
398 }
399 
400 static int PrepWasapiDevice(_THIS, const int iscapture, IMMDevice *device);
401 static void ReleaseWasapiDevice(_THIS);
402 
403 static SDL_bool
404 RecoverWasapiDevice(_THIS)
405 {
406  const SDL_AudioSpec oldspec = this->spec;
407  IMMDevice *device = NULL;
408  HRESULT ret = S_OK;
409 
410  if (this->hidden->default_device_generation) {
411  const EDataFlow dataflow = this->iscapture ? eCapture : eRender;
412  ReleaseWasapiDevice(this); /* dump the lost device's handles. */
413  this->hidden->default_device_generation = SDL_AtomicGet(this->iscapture ? &default_capture_generation : &default_playback_generation);
414  ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_WASAPI_role, &device);
415  if (FAILED(ret)) {
416  return SDL_FALSE; /* can't find a new default device! */
417  }
418  } else {
419  device = this->hidden->device;
420  this->hidden->device = NULL; /* don't release this in ReleaseWasapiDevice(). */
421  ReleaseWasapiDevice(this); /* dump the lost device's handles. */
422  }
423 
424  SDL_assert(device != NULL);
425 
426  /* this can fail for lots of reasons, but the most likely is we had a
427  non-default device that was disconnected, so we can't recover. Default
428  devices try to reinitialize whatever the new default is, so it's more
429  likely to carry on here, but this handles a non-default device that
430  simply had its format changed in the Windows Control Panel. */
431  if (PrepWasapiDevice(this, this->iscapture, device) == -1) {
432  return SDL_FALSE;
433  }
434 
435  /* Since WASAPI requires us to handle all audio conversion, and our
436  device format might have changed, we might have to add/remove/change
437  the audio stream that the higher level uses to convert data, so
438  SDL keeps firing the callback as if nothing happened here. */
439 
440  if ( (this->callbackspec.channels == this->spec.channels) &&
441  (this->callbackspec.format == this->spec.format) &&
442  (this->callbackspec.freq == this->spec.freq) &&
443  (this->callbackspec.samples == this->spec.samples) ) {
444  /* no need to buffer/convert in an AudioStream! */
446  this->stream = NULL;
447  } else if ( (oldspec.channels == this->spec.channels) &&
448  (oldspec.format == this->spec.format) &&
449  (oldspec.freq == this->spec.freq) ) {
450  /* The existing audio stream is okay to keep using. */
451  } else {
452  /* replace the audiostream for new format */
454  if (this->iscapture) {
455  this->stream = SDL_NewAudioStream(this->spec.format,
456  this->spec.channels, this->spec.freq,
457  this->callbackspec.format,
458  this->callbackspec.channels,
459  this->callbackspec.freq);
460  } else {
461  this->stream = SDL_NewAudioStream(this->callbackspec.format,
462  this->callbackspec.channels,
463  this->callbackspec.freq, this->spec.format,
464  this->spec.channels, this->spec.freq);
465  }
466 
467  if (!this->stream) {
468  return SDL_FALSE;
469  }
470  }
471 
472  /* make sure our scratch buffer can cover the new device spec. */
473  if (this->spec.size > this->work_buffer_len) {
474  Uint8 *ptr = (Uint8 *) SDL_realloc(this->work_buffer, this->spec.size);
475  if (ptr == NULL) {
476  SDL_OutOfMemory();
477  return SDL_FALSE;
478  }
479  this->work_buffer = ptr;
480  this->work_buffer_len = this->spec.size;
481  }
482 
483  this->hidden->device_lost = SDL_FALSE;
484 
485  return SDL_TRUE; /* okay, carry on with new device details! */
486 }
487 
488 static SDL_bool
489 RecoverWasapiIfLost(_THIS)
490 {
491  const int generation = this->hidden->default_device_generation;
492  SDL_bool lost = this->hidden->device_lost;
493 
494  if (!SDL_AtomicGet(&this->enabled)) {
495  return SDL_FALSE; /* already failed. */
496  }
497 
498  if (!lost && (generation > 0)) { /* is a default device? */
499  const int newgen = SDL_AtomicGet(this->iscapture ? &default_capture_generation : &default_playback_generation);
500  if (generation != newgen) { /* the desired default device was changed, jump over to it. */
501  lost = SDL_TRUE;
502  }
503  }
504 
505  return lost ? RecoverWasapiDevice(this) : SDL_TRUE;
506 }
507 
508 static Uint8 *
509 WASAPI_GetDeviceBuf(_THIS)
510 {
511  /* get an endpoint buffer from WASAPI. */
512  BYTE *buffer = NULL;
513 
514  while (RecoverWasapiIfLost(this)) {
515  if (!WasapiFailed(this, IAudioRenderClient_GetBuffer(this->hidden->render, this->spec.samples, &buffer))) {
516  return (Uint8 *) buffer;
517  }
518  SDL_assert(buffer == NULL);
519  }
520 
521  return (Uint8 *) buffer;
522 }
523 
524 static void
525 WASAPI_PlayDevice(_THIS)
526 {
527  /* WasapiFailed() will mark the device for reacquisition or removal elsewhere. */
528  WasapiFailed(this, IAudioRenderClient_ReleaseBuffer(this->hidden->render, this->spec.samples, 0));
529 }
530 
531 static void
532 WASAPI_WaitDevice(_THIS)
533 {
534  const UINT32 maxpadding = this->spec.samples;
535  while (RecoverWasapiIfLost(this)) {
536  UINT32 padding = 0;
537 
538  if (!WasapiFailed(this, IAudioClient_GetCurrentPadding(this->hidden->client, &padding))) {
539  if (padding <= maxpadding) {
540  break;
541  }
542  /* Sleep long enough for half the buffer to be free. */
543  SDL_Delay(((padding - maxpadding) * 1000) / this->spec.freq);
544  }
545  }
546 }
547 
548 static int
549 WASAPI_CaptureFromDevice(_THIS, void *buffer, int buflen)
550 {
551  SDL_AudioStream *stream = this->hidden->capturestream;
552  const int avail = SDL_AudioStreamAvailable(stream);
553  if (avail > 0) {
554  const int cpy = SDL_min(buflen, avail);
555  SDL_AudioStreamGet(stream, buffer, cpy);
556  return cpy;
557  }
558 
559  while (RecoverWasapiIfLost(this)) {
560  HRESULT ret;
561  BYTE *ptr = NULL;
562  UINT32 frames = 0;
563  DWORD flags = 0;
564 
565  ret = IAudioCaptureClient_GetBuffer(this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
566  if (ret != AUDCLNT_S_BUFFER_EMPTY) {
567  WasapiFailed(this, ret); /* mark device lost/failed if necessary. */
568  }
569 
570  if ((ret == AUDCLNT_S_BUFFER_EMPTY) || !frames) {
571  WASAPI_WaitDevice(this);
572  } else if (ret == S_OK) {
573  const int total = ((int) frames) * this->hidden->framesize;
574  const int cpy = SDL_min(buflen, total);
575  const int leftover = total - cpy;
576  const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
577 
578  if (silent) {
579  SDL_memset(buffer, this->spec.silence, cpy);
580  } else {
581  SDL_memcpy(buffer, ptr, cpy);
582  }
583 
584  if (leftover > 0) {
585  ptr += cpy;
586  if (silent) {
587  SDL_memset(ptr, this->spec.silence, leftover); /* I guess this is safe? */
588  }
589 
590  if (SDL_AudioStreamPut(stream, ptr, leftover) == -1) {
591  return -1; /* uhoh, out of memory, etc. Kill device. :( */
592  }
593  }
594 
595  ret = IAudioCaptureClient_ReleaseBuffer(this->hidden->capture, frames);
596  WasapiFailed(this, ret); /* mark device lost/failed if necessary. */
597 
598  return cpy;
599  }
600  }
601 
602  return -1; /* unrecoverable error. */
603 }
604 
605 static void
606 WASAPI_FlushCapture(_THIS)
607 {
608  BYTE *ptr = NULL;
609  UINT32 frames = 0;
610  DWORD flags = 0;
611 
612  /* just read until we stop getting packets, throwing them away. */
613  while (SDL_TRUE) {
614  const HRESULT ret = IAudioCaptureClient_GetBuffer(this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
615  if (ret == AUDCLNT_S_BUFFER_EMPTY) {
616  break; /* no more buffered data; we're done. */
617  } else if (WasapiFailed(this, ret)) {
618  break; /* failed for some other reason, abort. */
619  } else if (WasapiFailed(this, IAudioCaptureClient_ReleaseBuffer(this->hidden->capture, frames))) {
620  break; /* something broke. */
621  }
622  }
623  SDL_AudioStreamClear(this->hidden->capturestream);
624 }
625 
626 static void
627 ReleaseWasapiDevice(_THIS)
628 {
629  if (this->hidden->client) {
630  IAudioClient_Stop(this->hidden->client);
631  this->hidden->client = NULL;
632  }
633 
634  if (this->hidden->render) {
635  IAudioRenderClient_Release(this->hidden->render);
636  this->hidden->render = NULL;
637  }
638 
639  if (this->hidden->capture) {
640  IAudioCaptureClient_Release(this->hidden->capture);
641  this->hidden->capture = NULL;
642  }
643 
644  if (this->hidden->waveformat) {
645  CoTaskMemFree(this->hidden->waveformat);
646  this->hidden->waveformat = NULL;
647  }
648 
649  if (this->hidden->device) {
650  IMMDevice_Release(this->hidden->device);
651  this->hidden->device = NULL;
652  }
653 
654  if (this->hidden->capturestream) {
655  SDL_FreeAudioStream(this->hidden->capturestream);
656  this->hidden->capturestream = NULL;
657  }
658 }
659 
660 static void
661 WASAPI_CloseDevice(_THIS)
662 {
663  /* don't touch this->hidden->task in here; it has to be reverted from
664  our callback thread. We do that in WASAPI_ThreadDeinit().
665  (likewise for this->hidden->coinitialized). */
666  ReleaseWasapiDevice(this);
667  SDL_free(this->hidden);
668 }
669 
670 
671 static int
672 PrepWasapiDevice(_THIS, const int iscapture, IMMDevice *device)
673 {
674  /* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
675  !!! it will write into the kernel's audio buffer directly instead of
676  !!! shared memory that a user-mode mixer then writes to the kernel with
677  !!! everything else. Doing this means any other sound using this device will
678  !!! stop playing, including the user's MP3 player and system notification
679  !!! sounds. You'd probably need to release the device when the app isn't in
680  !!! the foreground, to be a good citizen of the system. It's doable, but it's
681  !!! more work and causes some annoyances, and I don't know what the latency
682  !!! wins actually look like. Maybe add a hint to force exclusive mode at
683  !!! some point. To be sure, defaulting to shared mode is the right thing to
684  !!! do in any case. */
685  const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
686  UINT32 bufsize = 0; /* this is in sample frames, not samples, not bytes. */
687  REFERENCE_TIME duration = 0;
688  IAudioClient *client = NULL;
689  IAudioRenderClient *render = NULL;
690  IAudioCaptureClient *capture = NULL;
691  WAVEFORMATEX *waveformat = NULL;
692  SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
693  SDL_AudioFormat wasapi_format = 0;
694  SDL_bool valid_format = SDL_FALSE;
695  HRESULT ret = S_OK;
696 
697  this->hidden->device = device;
698 
699  ret = IMMDevice_Activate(device, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **) &client);
700  if (FAILED(ret)) {
701  return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
702  }
703 
704  SDL_assert(client != NULL);
705  this->hidden->client = client;
706 
707  ret = IAudioClient_GetMixFormat(client, &waveformat);
708  if (FAILED(ret)) {
709  return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
710  }
711 
712  SDL_assert(waveformat != NULL);
713  this->hidden->waveformat = waveformat;
714 
715  /* WASAPI will not do any conversion on our behalf. Force channels and sample rate. */
716  this->spec.channels = (Uint8) waveformat->nChannels;
717  this->spec.freq = waveformat->nSamplesPerSec;
718 
719  /* Make sure we have a valid format that we can convert to whatever WASAPI wants. */
720  if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
721  wasapi_format = AUDIO_F32SYS;
722  } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
723  wasapi_format = AUDIO_S16SYS;
724  } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
725  wasapi_format = AUDIO_S32SYS;
726  } else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
727  const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *) waveformat;
728  if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
729  wasapi_format = AUDIO_F32SYS;
730  } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
731  wasapi_format = AUDIO_S16SYS;
732  } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
733  wasapi_format = AUDIO_S32SYS;
734  }
735  }
736 
737  while ((!valid_format) && (test_format)) {
738  if (test_format == wasapi_format) {
739  this->spec.format = test_format;
740  valid_format = SDL_TRUE;
741  break;
742  }
743  test_format = SDL_NextAudioFormat();
744  }
745 
746  if (!valid_format) {
747  return SDL_SetError("WASAPI: Unsupported audio format");
748  }
749 
750  ret = IAudioClient_GetDevicePeriod(client, NULL, &duration);
751  if (FAILED(ret)) {
752  return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
753  }
754 
755  ret = IAudioClient_Initialize(client, sharemode, 0, duration, sharemode == AUDCLNT_SHAREMODE_SHARED ? 0 : duration, waveformat, NULL);
756  if (FAILED(ret)) {
757  return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
758  }
759 
760  ret = IAudioClient_GetBufferSize(client, &bufsize);
761  if (FAILED(ret)) {
762  return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
763  }
764 
765  this->spec.samples = (Uint16) bufsize;
766  if (!iscapture) {
767  this->spec.samples /= 2; /* fill half of the DMA buffer on each run. */
768  }
769 
770  /* Update the fragment size as size in bytes */
772 
773  this->hidden->framesize = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels;
774 
775  if (iscapture) {
776  this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
777  if (!this->hidden->capturestream) {
778  return -1; /* already set SDL_Error */
779  }
780 
781  ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void**) &capture);
782  if (FAILED(ret)) {
783  return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
784  }
785 
786  SDL_assert(capture != NULL);
787  this->hidden->capture = capture;
788  ret = IAudioClient_Start(client);
789  if (FAILED(ret)) {
790  return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
791  }
792 
793  WASAPI_FlushCapture(this); /* MSDN says you should flush capture endpoint right after startup. */
794  } else {
795  ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void**) &render);
796  if (FAILED(ret)) {
797  return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
798  }
799 
800  SDL_assert(render != NULL);
801  this->hidden->render = render;
802  ret = IAudioClient_Start(client);
803  if (FAILED(ret)) {
804  return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
805  }
806  }
807 
808  return 0; /* good to go. */
809 }
810 
811 static int
812 WASAPI_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
813 {
814  const SDL_bool is_default_device = (handle == NULL);
815  IMMDevice *device = NULL;
816  HRESULT ret = S_OK;
817 
818  /* Initialize all variables that we clean on shutdown */
819  this->hidden = (struct SDL_PrivateAudioData *)
820  SDL_malloc((sizeof *this->hidden));
821  if (this->hidden == NULL) {
822  return SDL_OutOfMemory();
823  }
824  SDL_zerop(this->hidden);
825 
826  if (is_default_device) {
827  const EDataFlow dataflow = iscapture ? eCapture : eRender;
828  this->hidden->default_device_generation = SDL_AtomicGet(iscapture ? &default_capture_generation : &default_playback_generation);
829  ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_WASAPI_role, &device);
830  } else {
831  ret = IMMDeviceEnumerator_GetDevice(enumerator, (LPCWSTR) handle, &device);
832  }
833 
834  if (FAILED(ret)) {
835  return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret);
836  }
837 
838  SDL_assert(device != NULL);
839  return PrepWasapiDevice(this, iscapture, device);
840 }
841 
842 static void
843 WASAPI_ThreadInit(_THIS)
844 {
845  /* this thread uses COM. */
846  if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */
847  this->hidden->coinitialized = SDL_TRUE;
848  }
849 
850  /* Set this thread to very high "Pro Audio" priority. */
851  if (pAvSetMmThreadCharacteristicsW) {
852  DWORD idx = 0;
853  this->hidden->task = pAvSetMmThreadCharacteristicsW(TEXT("Pro Audio"), &idx);
854  }
855 }
856 
857 static void
858 WASAPI_ThreadDeinit(_THIS)
859 {
860  /* Set this thread to very high "Pro Audio" priority. */
861  if (this->hidden->task && pAvRevertMmThreadCharacteristics) {
862  pAvRevertMmThreadCharacteristics(this->hidden->task);
863  this->hidden->task = NULL;
864  }
865 
866  if (this->hidden->coinitialized) {
868  }
869 }
870 
871 static void
872 WASAPI_Deinitialize(void)
873 {
874  DevIdList *devidlist;
875  DevIdList *next;
876 
877  if (enumerator) {
878  IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) &notification_client);
879  IMMDeviceEnumerator_Release(enumerator);
880  enumerator = NULL;
881  }
882 
883  #ifndef __WINRT__
884  if (libavrt) {
885  FreeLibrary(libavrt);
886  libavrt = NULL;
887  }
888  #endif
889 
890  pAvSetMmThreadCharacteristicsW = NULL;
891  pAvRevertMmThreadCharacteristics = NULL;
892 
893  for (devidlist = deviceid_list; devidlist; devidlist = next) {
894  next = devidlist->next;
895  SDL_free(devidlist->str);
896  SDL_free(devidlist);
897  }
898  deviceid_list = NULL;
899 
901 }
902 
903 static int
904 WASAPI_Init(SDL_AudioDriverImpl * impl)
905 {
906  HRESULT ret;
907 
908  /* just skip the discussion with COM here. */
910  SDL_SetError("WASAPI support requires Windows Vista or later");
911  return 0;
912  }
913 
914  SDL_AtomicSet(&default_playback_generation, 1);
915  SDL_AtomicSet(&default_capture_generation, 1);
916 
917  if (FAILED(WIN_CoInitialize())) {
918  SDL_SetError("WASAPI: CoInitialize() failed");
919  return 0;
920  }
921 
922  ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID) &enumerator);
923  if (FAILED(ret)) {
925  WIN_SetErrorFromHRESULT("WASAPI CoCreateInstance(MMDeviceEnumerator)", ret);
926  return 0; /* oh well. */
927  }
928 
929  #ifdef __WINRT__
930  pAvSetMmThreadCharacteristicsW = AvSetMmThreadCharacteristicsW;
931  pAvRevertMmThreadCharacteristics = AvRevertMmThreadCharacteristics;
932  #else
933  libavrt = LoadLibraryW(L"avrt.dll"); /* this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! */
934  if (libavrt) {
935  pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW) GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
936  pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics) GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
937  }
938  #endif
939 
940  /* Set the function pointers */
941  impl->DetectDevices = WASAPI_DetectDevices;
942  impl->ThreadInit = WASAPI_ThreadInit;
943  impl->ThreadDeinit = WASAPI_ThreadDeinit;
944  impl->OpenDevice = WASAPI_OpenDevice;
945  impl->PlayDevice = WASAPI_PlayDevice;
946  impl->WaitDevice = WASAPI_WaitDevice;
947  impl->GetPendingBytes = WASAPI_GetPendingBytes;
948  impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
949  impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
950  impl->FlushCapture = WASAPI_FlushCapture;
951  impl->CloseDevice = WASAPI_CloseDevice;
952  impl->Deinitialize = WASAPI_Deinitialize;
953  impl->HasCaptureSupport = 1;
954 
955  return 1; /* this audio target is available. */
956 }
957 
959  "wasapi", "WASAPI", WASAPI_Init, 0
960 };
961 
962 #endif /* SDL_AUDIO_DRIVER_WASAPI */
963 
964 /* vi: set ts=4 sw=4 expandtab: */
SDL_AudioStream * SDL_NewAudioStream(const SDL_AudioFormat src_format, const Uint8 src_channels, const int src_rate, const SDL_AudioFormat dst_format, const Uint8 dst_channels, const int dst_rate)
void SDL_FreeAudioStream(SDL_AudioStream *stream)
BOOL WIN_IsWindowsVistaOrGreater(void)
#define SDL_min(x, y)
Definition: SDL_stdinc.h:359
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
Definition: SDL_audio.c:1577
void(* DetectDevices)(void)
Definition: SDL_sysaudio.h:67
Uint8 silence
Definition: SDL_audio.h:173
A type representing an atomic integer value. It is a struct so people don&#39;t accidentally use numeric ...
Definition: SDL_atomic.h:195
int SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len)
int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
void(* ThreadDeinit)(_THIS)
Definition: SDL_sysaudio.h:70
void(* PlayDevice)(_THIS)
Definition: SDL_sysaudio.h:72
Uint16 samples
Definition: SDL_audio.h:174
void(* WaitDevice)(_THIS)
Definition: SDL_sysaudio.h:71
void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device)
Definition: SDL_audio.c:446
Uint16 SDL_AudioFormat
Audio format flags.
Definition: SDL_audio.h:64
#define SDL_realloc
#define AUDIO_S16SYS
Definition: SDL_audio.h:123
#define SDL_zerop(x)
Definition: SDL_stdinc.h:370
GLenum GLsizei len
SDL_AudioFormat SDL_NextAudioFormat(void)
Definition: SDL_audio.c:1589
GLenum GLuint GLsizei bufsize
void render(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect texture_dimensions)
Definition: testshape.c:29
#define E_NOINTERFACE
Definition: SDL_directx.h:61
SDL_AudioSpec spec
Definition: loopwave.c:31
static SDL_AudioDeviceID device
Definition: loopwave.c:37
SDL_bool retval
#define FAILED(x)
Definition: SDL_directx.h:54
#define SDL_memcpy
void(* ThreadInit)(_THIS)
Definition: SDL_sysaudio.h:69
GLuint64 key
Definition: gl2ext.h:2192
EGLImageKHR EGLint EGLint * handle
Definition: eglext.h:937
#define AUDIO_F32SYS
Definition: SDL_audio.h:125
GLuint GLuint stream
AudioBootStrap WASAPI_bootstrap
void SDL_RemoveAudioDevice(const int iscapture, void *handle)
Definition: SDL_audio.c:487
Uint8 channels
Definition: SDL_audio.h:172
HRESULT WIN_CoInitialize(void)
#define _THIS
uint8_t Uint8
An unsigned 8-bit integer type.
Definition: SDL_stdinc.h:153
void SDL_free(void *mem)
#define SDL_AUDIO_BITSIZE(x)
Definition: SDL_audio.h:75
#define SDL_memcmp
int SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 len)
void(* Deinitialize)(void)
Definition: SDL_sysaudio.h:82
#define WIN_StringToUTF8(S)
Definition: SDL_windows.h:46
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1598
#define S_OK
Definition: SDL_directx.h:47
#define SDL_AtomicIncRef(a)
Increment an atomic variable used as a reference count.
Definition: SDL_atomic.h:231
#define SDL_Delay
GLenum GLenum GLsizei const GLuint GLboolean enabled
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
Uint32 size
Definition: SDL_audio.h:176
#define SDL_assert(condition)
Definition: SDL_assert.h:169
int(* OpenDevice)(_THIS, void *handle, const char *devname, int iscapture)
Definition: SDL_sysaudio.h:68
#define NULL
Definition: begin_code.h:164
#define SDL_AtomicAdd
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
SDL_bool
Definition: SDL_stdinc.h:139
GLuint buffer
void WIN_CoUninitialize(void)
int(* CaptureFromDevice)(_THIS, void *buffer, int buflen)
Definition: SDL_sysaudio.h:75
#define SUCCEEDED(x)
Definition: SDL_directx.h:51
#define SDL_SetError
GLbitfield flags
void(* CloseDevice)(_THIS)
Definition: SDL_sysaudio.h:78
int SDL_AudioStreamAvailable(SDL_AudioStream *stream)
SDL_AudioFormat format
Definition: SDL_audio.h:171
void(* FlushCapture)(_THIS)
Definition: SDL_sysaudio.h:76
#define SDL_AtomicDecRef(a)
Decrement an atomic variable used as a reference count.
Definition: SDL_atomic.h:241
BOOL WIN_IsEqualIID(REFIID a, REFIID b)
Uint8 *(* GetDeviceBuf)(_THIS)
Definition: SDL_sysaudio.h:74
#define SDL_AtomicSet
#define AUDIO_S32SYS
Definition: SDL_audio.h:124
#define SDL_AtomicGet
uint16_t Uint16
An unsigned 16-bit integer type.
Definition: SDL_stdinc.h:161
void SDL_AudioStreamClear(SDL_AudioStream *stream)
#define SDL_INLINE
Definition: begin_code.h:131
#define SDL_malloc
int(* GetPendingBytes)(_THIS)
Definition: SDL_sysaudio.h:73
GLenum GLuint GLsizei const GLenum * props
GLboolean GLboolean GLboolean GLboolean a
GLboolean GLboolean GLboolean b
#define SDL_memset
void SDL_AddAudioDevice(const int iscapture, const char *name, void *handle)
Definition: SDL_audio.c:429