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