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