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