SDL_directsound.c (19492B)
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_DSOUND 24 25 /* Allow access to a raw mixing buffer */ 26 27 #include "SDL_timer.h" 28 #include "SDL_loadso.h" 29 #include "SDL_audio.h" 30 #include "../SDL_audio_c.h" 31 #include "SDL_directsound.h" 32 33 #ifndef WAVE_FORMAT_IEEE_FLOAT 34 #define WAVE_FORMAT_IEEE_FLOAT 0x0003 35 #endif 36 37 /* DirectX function pointers for audio */ 38 static void* DSoundDLL = NULL; 39 typedef HRESULT (WINAPI *fnDirectSoundCreate8)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN); 40 typedef HRESULT (WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); 41 typedef HRESULT (WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID,LPDIRECTSOUNDCAPTURE8 *,LPUNKNOWN); 42 typedef HRESULT (WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW,LPVOID); 43 static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; 44 static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; 45 static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; 46 static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; 47 48 static void 49 DSOUND_Unload(void) 50 { 51 pDirectSoundCreate8 = NULL; 52 pDirectSoundEnumerateW = NULL; 53 pDirectSoundCaptureCreate8 = NULL; 54 pDirectSoundCaptureEnumerateW = NULL; 55 56 if (DSoundDLL != NULL) { 57 SDL_UnloadObject(DSoundDLL); 58 DSoundDLL = NULL; 59 } 60 } 61 62 63 static int 64 DSOUND_Load(void) 65 { 66 int loaded = 0; 67 68 DSOUND_Unload(); 69 70 DSoundDLL = SDL_LoadObject("DSOUND.DLL"); 71 if (DSoundDLL == NULL) { 72 SDL_SetError("DirectSound: failed to load DSOUND.DLL"); 73 } else { 74 /* Now make sure we have DirectX 8 or better... */ 75 #define DSOUNDLOAD(f) { \ 76 p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \ 77 if (!p##f) loaded = 0; \ 78 } 79 loaded = 1; /* will reset if necessary. */ 80 DSOUNDLOAD(DirectSoundCreate8); 81 DSOUNDLOAD(DirectSoundEnumerateW); 82 DSOUNDLOAD(DirectSoundCaptureCreate8); 83 DSOUNDLOAD(DirectSoundCaptureEnumerateW); 84 #undef DSOUNDLOAD 85 86 if (!loaded) { 87 SDL_SetError("DirectSound: System doesn't appear to have DX8."); 88 } 89 } 90 91 if (!loaded) { 92 DSOUND_Unload(); 93 } 94 95 return loaded; 96 } 97 98 static int 99 SetDSerror(const char *function, int code) 100 { 101 static const char *error; 102 static char errbuf[1024]; 103 104 errbuf[0] = 0; 105 switch (code) { 106 case E_NOINTERFACE: 107 error = "Unsupported interface -- Is DirectX 8.0 or later installed?"; 108 break; 109 case DSERR_ALLOCATED: 110 error = "Audio device in use"; 111 break; 112 case DSERR_BADFORMAT: 113 error = "Unsupported audio format"; 114 break; 115 case DSERR_BUFFERLOST: 116 error = "Mixing buffer was lost"; 117 break; 118 case DSERR_CONTROLUNAVAIL: 119 error = "Control requested is not available"; 120 break; 121 case DSERR_INVALIDCALL: 122 error = "Invalid call for the current state"; 123 break; 124 case DSERR_INVALIDPARAM: 125 error = "Invalid parameter"; 126 break; 127 case DSERR_NODRIVER: 128 error = "No audio device found"; 129 break; 130 case DSERR_OUTOFMEMORY: 131 error = "Out of memory"; 132 break; 133 case DSERR_PRIOLEVELNEEDED: 134 error = "Caller doesn't have priority"; 135 break; 136 case DSERR_UNSUPPORTED: 137 error = "Function not supported"; 138 break; 139 default: 140 SDL_snprintf(errbuf, SDL_arraysize(errbuf), 141 "%s: Unknown DirectSound error: 0x%x", function, code); 142 break; 143 } 144 if (!errbuf[0]) { 145 SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function, 146 error); 147 } 148 return SDL_SetError("%s", errbuf); 149 } 150 151 static void 152 DSOUND_FreeDeviceHandle(void *handle) 153 { 154 SDL_free(handle); 155 } 156 157 static BOOL CALLBACK 158 FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data) 159 { 160 const int iscapture = (int) ((size_t) data); 161 if (guid != NULL) { /* skip default device */ 162 char *str = WIN_LookupAudioDeviceName(desc, guid); 163 if (str != NULL) { 164 LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID)); 165 SDL_memcpy(cpyguid, guid, sizeof (GUID)); 166 SDL_AddAudioDevice(iscapture, str, cpyguid); 167 SDL_free(str); /* addfn() makes a copy of this string. */ 168 } 169 } 170 return TRUE; /* keep enumerating. */ 171 } 172 173 static void 174 DSOUND_DetectDevices(void) 175 { 176 pDirectSoundCaptureEnumerateW(FindAllDevs, (void *) ((size_t) 1)); 177 pDirectSoundEnumerateW(FindAllDevs, (void *) ((size_t) 0)); 178 } 179 180 181 static void 182 DSOUND_WaitDevice(_THIS) 183 { 184 DWORD status = 0; 185 DWORD cursor = 0; 186 DWORD junk = 0; 187 HRESULT result = DS_OK; 188 189 /* Semi-busy wait, since we have no way of getting play notification 190 on a primary mixing buffer located in hardware (DirectX 5.0) 191 */ 192 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, 193 &junk, &cursor); 194 if (result != DS_OK) { 195 if (result == DSERR_BUFFERLOST) { 196 IDirectSoundBuffer_Restore(this->hidden->mixbuf); 197 } 198 #ifdef DEBUG_SOUND 199 SetDSerror("DirectSound GetCurrentPosition", result); 200 #endif 201 return; 202 } 203 204 while ((cursor / this->spec.size) == this->hidden->lastchunk) { 205 /* FIXME: find out how much time is left and sleep that long */ 206 SDL_Delay(1); 207 208 /* Try to restore a lost sound buffer */ 209 IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status); 210 if ((status & DSBSTATUS_BUFFERLOST)) { 211 IDirectSoundBuffer_Restore(this->hidden->mixbuf); 212 IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status); 213 if ((status & DSBSTATUS_BUFFERLOST)) { 214 break; 215 } 216 } 217 if (!(status & DSBSTATUS_PLAYING)) { 218 result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0, 219 DSBPLAY_LOOPING); 220 if (result == DS_OK) { 221 continue; 222 } 223 #ifdef DEBUG_SOUND 224 SetDSerror("DirectSound Play", result); 225 #endif 226 return; 227 } 228 229 /* Find out where we are playing */ 230 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, 231 &junk, &cursor); 232 if (result != DS_OK) { 233 SetDSerror("DirectSound GetCurrentPosition", result); 234 return; 235 } 236 } 237 } 238 239 static void 240 DSOUND_PlayDevice(_THIS) 241 { 242 /* Unlock the buffer, allowing it to play */ 243 if (this->hidden->locked_buf) { 244 IDirectSoundBuffer_Unlock(this->hidden->mixbuf, 245 this->hidden->locked_buf, 246 this->spec.size, NULL, 0); 247 } 248 } 249 250 static Uint8 * 251 DSOUND_GetDeviceBuf(_THIS) 252 { 253 DWORD cursor = 0; 254 DWORD junk = 0; 255 HRESULT result = DS_OK; 256 DWORD rawlen = 0; 257 258 /* Figure out which blocks to fill next */ 259 this->hidden->locked_buf = NULL; 260 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, 261 &junk, &cursor); 262 if (result == DSERR_BUFFERLOST) { 263 IDirectSoundBuffer_Restore(this->hidden->mixbuf); 264 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf, 265 &junk, &cursor); 266 } 267 if (result != DS_OK) { 268 SetDSerror("DirectSound GetCurrentPosition", result); 269 return (NULL); 270 } 271 cursor /= this->spec.size; 272 #ifdef DEBUG_SOUND 273 /* Detect audio dropouts */ 274 { 275 DWORD spot = cursor; 276 if (spot < this->hidden->lastchunk) { 277 spot += this->hidden->num_buffers; 278 } 279 if (spot > this->hidden->lastchunk + 1) { 280 fprintf(stderr, "Audio dropout, missed %d fragments\n", 281 (spot - (this->hidden->lastchunk + 1))); 282 } 283 } 284 #endif 285 this->hidden->lastchunk = cursor; 286 cursor = (cursor + 1) % this->hidden->num_buffers; 287 cursor *= this->spec.size; 288 289 /* Lock the audio buffer */ 290 result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor, 291 this->spec.size, 292 (LPVOID *) & this->hidden->locked_buf, 293 &rawlen, NULL, &junk, 0); 294 if (result == DSERR_BUFFERLOST) { 295 IDirectSoundBuffer_Restore(this->hidden->mixbuf); 296 result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor, 297 this->spec.size, 298 (LPVOID *) & this-> 299 hidden->locked_buf, &rawlen, NULL, 300 &junk, 0); 301 } 302 if (result != DS_OK) { 303 SetDSerror("DirectSound Lock", result); 304 return (NULL); 305 } 306 return (this->hidden->locked_buf); 307 } 308 309 static int 310 DSOUND_CaptureFromDevice(_THIS, void *buffer, int buflen) 311 { 312 struct SDL_PrivateAudioData *h = this->hidden; 313 DWORD junk, cursor, ptr1len, ptr2len; 314 VOID *ptr1, *ptr2; 315 316 SDL_assert(buflen == this->spec.size); 317 318 while (SDL_TRUE) { 319 if (SDL_AtomicGet(&this->shutdown)) { /* in case the buffer froze... */ 320 SDL_memset(buffer, this->spec.silence, buflen); 321 return buflen; 322 } 323 324 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) { 325 return -1; 326 } 327 if ((cursor / this->spec.size) == h->lastchunk) { 328 SDL_Delay(1); /* FIXME: find out how much time is left and sleep that long */ 329 } else { 330 break; 331 } 332 } 333 334 if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * this->spec.size, this->spec.size, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) { 335 return -1; 336 } 337 338 SDL_assert(ptr1len == this->spec.size); 339 SDL_assert(ptr2 == NULL); 340 SDL_assert(ptr2len == 0); 341 342 SDL_memcpy(buffer, ptr1, ptr1len); 343 344 if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) { 345 return -1; 346 } 347 348 h->lastchunk = (h->lastchunk + 1) % h->num_buffers; 349 350 return ptr1len; 351 } 352 353 static void 354 DSOUND_FlushCapture(_THIS) 355 { 356 struct SDL_PrivateAudioData *h = this->hidden; 357 DWORD junk, cursor; 358 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) { 359 h->lastchunk = cursor / this->spec.size; 360 } 361 } 362 363 static void 364 DSOUND_CloseDevice(_THIS) 365 { 366 if (this->hidden->mixbuf != NULL) { 367 IDirectSoundBuffer_Stop(this->hidden->mixbuf); 368 IDirectSoundBuffer_Release(this->hidden->mixbuf); 369 } 370 if (this->hidden->sound != NULL) { 371 IDirectSound_Release(this->hidden->sound); 372 } 373 if (this->hidden->capturebuf != NULL) { 374 IDirectSoundCaptureBuffer_Stop(this->hidden->capturebuf); 375 IDirectSoundCaptureBuffer_Release(this->hidden->capturebuf); 376 } 377 if (this->hidden->capture != NULL) { 378 IDirectSoundCapture_Release(this->hidden->capture); 379 } 380 SDL_free(this->hidden); 381 } 382 383 /* This function tries to create a secondary audio buffer, and returns the 384 number of audio chunks available in the created buffer. This is for 385 playback devices, not capture. 386 */ 387 static int 388 CreateSecondary(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt) 389 { 390 LPDIRECTSOUND sndObj = this->hidden->sound; 391 LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf; 392 HRESULT result = DS_OK; 393 DSBUFFERDESC format; 394 LPVOID pvAudioPtr1, pvAudioPtr2; 395 DWORD dwAudioBytes1, dwAudioBytes2; 396 397 /* Try to create the secondary buffer */ 398 SDL_zero(format); 399 format.dwSize = sizeof(format); 400 format.dwFlags = DSBCAPS_GETCURRENTPOSITION2; 401 format.dwFlags |= DSBCAPS_GLOBALFOCUS; 402 format.dwBufferBytes = bufsize; 403 format.lpwfxFormat = wfmt; 404 result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL); 405 if (result != DS_OK) { 406 return SetDSerror("DirectSound CreateSoundBuffer", result); 407 } 408 IDirectSoundBuffer_SetFormat(*sndbuf, wfmt); 409 410 /* Silence the initial audio buffer */ 411 result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes, 412 (LPVOID *) & pvAudioPtr1, &dwAudioBytes1, 413 (LPVOID *) & pvAudioPtr2, &dwAudioBytes2, 414 DSBLOCK_ENTIREBUFFER); 415 if (result == DS_OK) { 416 SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1); 417 IDirectSoundBuffer_Unlock(*sndbuf, 418 (LPVOID) pvAudioPtr1, dwAudioBytes1, 419 (LPVOID) pvAudioPtr2, dwAudioBytes2); 420 } 421 422 /* We're ready to go */ 423 return 0; 424 } 425 426 /* This function tries to create a capture buffer, and returns the 427 number of audio chunks available in the created buffer. This is for 428 capture devices, not playback. 429 */ 430 static int 431 CreateCaptureBuffer(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt) 432 { 433 LPDIRECTSOUNDCAPTURE capture = this->hidden->capture; 434 LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &this->hidden->capturebuf; 435 DSCBUFFERDESC format; 436 HRESULT result; 437 438 SDL_zero(format); 439 format.dwSize = sizeof (format); 440 format.dwFlags = DSCBCAPS_WAVEMAPPED; 441 format.dwBufferBytes = bufsize; 442 format.lpwfxFormat = wfmt; 443 444 result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL); 445 if (result != DS_OK) { 446 return SetDSerror("DirectSound CreateCaptureBuffer", result); 447 } 448 449 result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING); 450 if (result != DS_OK) { 451 IDirectSoundCaptureBuffer_Release(*capturebuf); 452 return SetDSerror("DirectSound Start", result); 453 } 454 455 #if 0 456 /* presumably this starts at zero, but just in case... */ 457 result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor); 458 if (result != DS_OK) { 459 IDirectSoundCaptureBuffer_Stop(*capturebuf); 460 IDirectSoundCaptureBuffer_Release(*capturebuf); 461 return SetDSerror("DirectSound GetCurrentPosition", result); 462 } 463 464 this->hidden->lastchunk = cursor / this->spec.size; 465 #endif 466 467 return 0; 468 } 469 470 static int 471 DSOUND_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 472 { 473 const DWORD numchunks = 8; 474 HRESULT result; 475 SDL_bool valid_format = SDL_FALSE; 476 SDL_bool tried_format = SDL_FALSE; 477 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 478 LPGUID guid = (LPGUID) handle; 479 DWORD bufsize; 480 481 /* Initialize all variables that we clean on shutdown */ 482 this->hidden = (struct SDL_PrivateAudioData *) 483 SDL_malloc((sizeof *this->hidden)); 484 if (this->hidden == NULL) { 485 return SDL_OutOfMemory(); 486 } 487 SDL_zerop(this->hidden); 488 489 /* Open the audio device */ 490 if (iscapture) { 491 result = pDirectSoundCaptureCreate8(guid, &this->hidden->capture, NULL); 492 if (result != DS_OK) { 493 return SetDSerror("DirectSoundCaptureCreate8", result); 494 } 495 } else { 496 result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL); 497 if (result != DS_OK) { 498 return SetDSerror("DirectSoundCreate8", result); 499 } 500 result = IDirectSound_SetCooperativeLevel(this->hidden->sound, 501 GetDesktopWindow(), 502 DSSCL_NORMAL); 503 if (result != DS_OK) { 504 return SetDSerror("DirectSound SetCooperativeLevel", result); 505 } 506 } 507 508 while ((!valid_format) && (test_format)) { 509 switch (test_format) { 510 case AUDIO_U8: 511 case AUDIO_S16: 512 case AUDIO_S32: 513 case AUDIO_F32: 514 tried_format = SDL_TRUE; 515 516 this->spec.format = test_format; 517 518 /* Update the fragment size as size in bytes */ 519 SDL_CalculateAudioSpec(&this->spec); 520 521 bufsize = numchunks * this->spec.size; 522 if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) { 523 SDL_SetError("Sound buffer size must be between %d and %d", 524 (int) ((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks), 525 (int) (DSBSIZE_MAX / numchunks)); 526 } else { 527 int rc; 528 WAVEFORMATEX wfmt; 529 SDL_zero(wfmt); 530 if (SDL_AUDIO_ISFLOAT(this->spec.format)) { 531 wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; 532 } else { 533 wfmt.wFormatTag = WAVE_FORMAT_PCM; 534 } 535 536 wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); 537 wfmt.nChannels = this->spec.channels; 538 wfmt.nSamplesPerSec = this->spec.freq; 539 wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8); 540 wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; 541 542 rc = iscapture ? CreateCaptureBuffer(this, bufsize, &wfmt) : CreateSecondary(this, bufsize, &wfmt); 543 if (rc == 0) { 544 this->hidden->num_buffers = numchunks; 545 valid_format = SDL_TRUE; 546 } 547 } 548 break; 549 } 550 test_format = SDL_NextAudioFormat(); 551 } 552 553 if (!valid_format) { 554 if (tried_format) { 555 return -1; /* CreateSecondary() should have called SDL_SetError(). */ 556 } 557 return SDL_SetError("DirectSound: Unsupported audio format"); 558 } 559 560 /* Playback buffers will auto-start playing in DSOUND_WaitDevice() */ 561 562 return 0; /* good to go. */ 563 } 564 565 566 static void 567 DSOUND_Deinitialize(void) 568 { 569 DSOUND_Unload(); 570 } 571 572 573 static int 574 DSOUND_Init(SDL_AudioDriverImpl * impl) 575 { 576 if (!DSOUND_Load()) { 577 return 0; 578 } 579 580 /* Set the function pointers */ 581 impl->DetectDevices = DSOUND_DetectDevices; 582 impl->OpenDevice = DSOUND_OpenDevice; 583 impl->PlayDevice = DSOUND_PlayDevice; 584 impl->WaitDevice = DSOUND_WaitDevice; 585 impl->GetDeviceBuf = DSOUND_GetDeviceBuf; 586 impl->CaptureFromDevice = DSOUND_CaptureFromDevice; 587 impl->FlushCapture = DSOUND_FlushCapture; 588 impl->CloseDevice = DSOUND_CloseDevice; 589 impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle; 590 impl->Deinitialize = DSOUND_Deinitialize; 591 592 impl->HasCaptureSupport = SDL_TRUE; 593 594 return 1; /* this audio target is available. */ 595 } 596 597 AudioBootStrap DSOUND_bootstrap = { 598 "directsound", "DirectSound", DSOUND_Init, 0 599 }; 600 601 #endif /* SDL_AUDIO_DRIVER_DSOUND */ 602 603 /* vi: set ts=4 sw=4 expandtab: */