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