1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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
DSOUND_Unload(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
DSOUND_Load(void)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
SetDSerror(const char * function,int code)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
DSOUND_FreeDeviceHandle(void * handle)152 DSOUND_FreeDeviceHandle(void *handle)
153 {
154 SDL_free(handle);
155 }
156
157 static BOOL CALLBACK
FindAllDevs(LPGUID guid,LPCWSTR desc,LPCWSTR module,LPVOID data)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
167 /* Note that spec is NULL, because we are required to connect to the
168 * device before getting the channel mask and output format, making
169 * this information inaccessible at enumeration time
170 */
171 SDL_AddAudioDevice(iscapture, str, NULL, cpyguid);
172 SDL_free(str); /* addfn() makes a copy of this string. */
173 }
174 }
175 return TRUE; /* keep enumerating. */
176 }
177
178 static void
DSOUND_DetectDevices(void)179 DSOUND_DetectDevices(void)
180 {
181 pDirectSoundCaptureEnumerateW(FindAllDevs, (void *) ((size_t) 1));
182 pDirectSoundEnumerateW(FindAllDevs, (void *) ((size_t) 0));
183 }
184
185
186 static void
DSOUND_WaitDevice(_THIS)187 DSOUND_WaitDevice(_THIS)
188 {
189 DWORD status = 0;
190 DWORD cursor = 0;
191 DWORD junk = 0;
192 HRESULT result = DS_OK;
193
194 /* Semi-busy wait, since we have no way of getting play notification
195 on a primary mixing buffer located in hardware (DirectX 5.0)
196 */
197 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
198 &junk, &cursor);
199 if (result != DS_OK) {
200 if (result == DSERR_BUFFERLOST) {
201 IDirectSoundBuffer_Restore(this->hidden->mixbuf);
202 }
203 #ifdef DEBUG_SOUND
204 SetDSerror("DirectSound GetCurrentPosition", result);
205 #endif
206 return;
207 }
208
209 while ((cursor / this->spec.size) == this->hidden->lastchunk) {
210 /* FIXME: find out how much time is left and sleep that long */
211 SDL_Delay(1);
212
213 /* Try to restore a lost sound buffer */
214 IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
215 if ((status & DSBSTATUS_BUFFERLOST)) {
216 IDirectSoundBuffer_Restore(this->hidden->mixbuf);
217 IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
218 if ((status & DSBSTATUS_BUFFERLOST)) {
219 break;
220 }
221 }
222 if (!(status & DSBSTATUS_PLAYING)) {
223 result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0,
224 DSBPLAY_LOOPING);
225 if (result == DS_OK) {
226 continue;
227 }
228 #ifdef DEBUG_SOUND
229 SetDSerror("DirectSound Play", result);
230 #endif
231 return;
232 }
233
234 /* Find out where we are playing */
235 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
236 &junk, &cursor);
237 if (result != DS_OK) {
238 SetDSerror("DirectSound GetCurrentPosition", result);
239 return;
240 }
241 }
242 }
243
244 static void
DSOUND_PlayDevice(_THIS)245 DSOUND_PlayDevice(_THIS)
246 {
247 /* Unlock the buffer, allowing it to play */
248 if (this->hidden->locked_buf) {
249 IDirectSoundBuffer_Unlock(this->hidden->mixbuf,
250 this->hidden->locked_buf,
251 this->spec.size, NULL, 0);
252 }
253 }
254
255 static Uint8 *
DSOUND_GetDeviceBuf(_THIS)256 DSOUND_GetDeviceBuf(_THIS)
257 {
258 DWORD cursor = 0;
259 DWORD junk = 0;
260 HRESULT result = DS_OK;
261 DWORD rawlen = 0;
262
263 /* Figure out which blocks to fill next */
264 this->hidden->locked_buf = NULL;
265 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
266 &junk, &cursor);
267 if (result == DSERR_BUFFERLOST) {
268 IDirectSoundBuffer_Restore(this->hidden->mixbuf);
269 result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
270 &junk, &cursor);
271 }
272 if (result != DS_OK) {
273 SetDSerror("DirectSound GetCurrentPosition", result);
274 return (NULL);
275 }
276 cursor /= this->spec.size;
277 #ifdef DEBUG_SOUND
278 /* Detect audio dropouts */
279 {
280 DWORD spot = cursor;
281 if (spot < this->hidden->lastchunk) {
282 spot += this->hidden->num_buffers;
283 }
284 if (spot > this->hidden->lastchunk + 1) {
285 fprintf(stderr, "Audio dropout, missed %d fragments\n",
286 (spot - (this->hidden->lastchunk + 1)));
287 }
288 }
289 #endif
290 this->hidden->lastchunk = cursor;
291 cursor = (cursor + 1) % this->hidden->num_buffers;
292 cursor *= this->spec.size;
293
294 /* Lock the audio buffer */
295 result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
296 this->spec.size,
297 (LPVOID *) & this->hidden->locked_buf,
298 &rawlen, NULL, &junk, 0);
299 if (result == DSERR_BUFFERLOST) {
300 IDirectSoundBuffer_Restore(this->hidden->mixbuf);
301 result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
302 this->spec.size,
303 (LPVOID *) & this->
304 hidden->locked_buf, &rawlen, NULL,
305 &junk, 0);
306 }
307 if (result != DS_OK) {
308 SetDSerror("DirectSound Lock", result);
309 return (NULL);
310 }
311 return (this->hidden->locked_buf);
312 }
313
314 static int
DSOUND_CaptureFromDevice(_THIS,void * buffer,int buflen)315 DSOUND_CaptureFromDevice(_THIS, void *buffer, int buflen)
316 {
317 struct SDL_PrivateAudioData *h = this->hidden;
318 DWORD junk, cursor, ptr1len, ptr2len;
319 VOID *ptr1, *ptr2;
320
321 SDL_assert(buflen == this->spec.size);
322
323 while (SDL_TRUE) {
324 if (SDL_AtomicGet(&this->shutdown)) { /* in case the buffer froze... */
325 SDL_memset(buffer, this->spec.silence, buflen);
326 return buflen;
327 }
328
329 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
330 return -1;
331 }
332 if ((cursor / this->spec.size) == h->lastchunk) {
333 SDL_Delay(1); /* FIXME: find out how much time is left and sleep that long */
334 } else {
335 break;
336 }
337 }
338
339 if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * this->spec.size, this->spec.size, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
340 return -1;
341 }
342
343 SDL_assert(ptr1len == this->spec.size);
344 SDL_assert(ptr2 == NULL);
345 SDL_assert(ptr2len == 0);
346
347 SDL_memcpy(buffer, ptr1, ptr1len);
348
349 if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
350 return -1;
351 }
352
353 h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
354
355 return ptr1len;
356 }
357
358 static void
DSOUND_FlushCapture(_THIS)359 DSOUND_FlushCapture(_THIS)
360 {
361 struct SDL_PrivateAudioData *h = this->hidden;
362 DWORD junk, cursor;
363 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
364 h->lastchunk = cursor / this->spec.size;
365 }
366 }
367
368 static void
DSOUND_CloseDevice(_THIS)369 DSOUND_CloseDevice(_THIS)
370 {
371 if (this->hidden->mixbuf != NULL) {
372 IDirectSoundBuffer_Stop(this->hidden->mixbuf);
373 IDirectSoundBuffer_Release(this->hidden->mixbuf);
374 }
375 if (this->hidden->sound != NULL) {
376 IDirectSound_Release(this->hidden->sound);
377 }
378 if (this->hidden->capturebuf != NULL) {
379 IDirectSoundCaptureBuffer_Stop(this->hidden->capturebuf);
380 IDirectSoundCaptureBuffer_Release(this->hidden->capturebuf);
381 }
382 if (this->hidden->capture != NULL) {
383 IDirectSoundCapture_Release(this->hidden->capture);
384 }
385 SDL_free(this->hidden);
386 }
387
388 /* This function tries to create a secondary audio buffer, and returns the
389 number of audio chunks available in the created buffer. This is for
390 playback devices, not capture.
391 */
392 static int
CreateSecondary(_THIS,const DWORD bufsize,WAVEFORMATEX * wfmt)393 CreateSecondary(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt)
394 {
395 LPDIRECTSOUND sndObj = this->hidden->sound;
396 LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf;
397 HRESULT result = DS_OK;
398 DSBUFFERDESC format;
399 LPVOID pvAudioPtr1, pvAudioPtr2;
400 DWORD dwAudioBytes1, dwAudioBytes2;
401
402 /* Try to create the secondary buffer */
403 SDL_zero(format);
404 format.dwSize = sizeof(format);
405 format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
406 format.dwFlags |= DSBCAPS_GLOBALFOCUS;
407 format.dwBufferBytes = bufsize;
408 format.lpwfxFormat = wfmt;
409 result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
410 if (result != DS_OK) {
411 return SetDSerror("DirectSound CreateSoundBuffer", result);
412 }
413 IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
414
415 /* Silence the initial audio buffer */
416 result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
417 (LPVOID *) & pvAudioPtr1, &dwAudioBytes1,
418 (LPVOID *) & pvAudioPtr2, &dwAudioBytes2,
419 DSBLOCK_ENTIREBUFFER);
420 if (result == DS_OK) {
421 SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1);
422 IDirectSoundBuffer_Unlock(*sndbuf,
423 (LPVOID) pvAudioPtr1, dwAudioBytes1,
424 (LPVOID) pvAudioPtr2, dwAudioBytes2);
425 }
426
427 /* We're ready to go */
428 return 0;
429 }
430
431 /* This function tries to create a capture buffer, and returns the
432 number of audio chunks available in the created buffer. This is for
433 capture devices, not playback.
434 */
435 static int
CreateCaptureBuffer(_THIS,const DWORD bufsize,WAVEFORMATEX * wfmt)436 CreateCaptureBuffer(_THIS, const DWORD bufsize, WAVEFORMATEX *wfmt)
437 {
438 LPDIRECTSOUNDCAPTURE capture = this->hidden->capture;
439 LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &this->hidden->capturebuf;
440 DSCBUFFERDESC format;
441 HRESULT result;
442
443 SDL_zero(format);
444 format.dwSize = sizeof (format);
445 format.dwFlags = DSCBCAPS_WAVEMAPPED;
446 format.dwBufferBytes = bufsize;
447 format.lpwfxFormat = wfmt;
448
449 result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
450 if (result != DS_OK) {
451 return SetDSerror("DirectSound CreateCaptureBuffer", result);
452 }
453
454 result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
455 if (result != DS_OK) {
456 IDirectSoundCaptureBuffer_Release(*capturebuf);
457 return SetDSerror("DirectSound Start", result);
458 }
459
460 #if 0
461 /* presumably this starts at zero, but just in case... */
462 result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
463 if (result != DS_OK) {
464 IDirectSoundCaptureBuffer_Stop(*capturebuf);
465 IDirectSoundCaptureBuffer_Release(*capturebuf);
466 return SetDSerror("DirectSound GetCurrentPosition", result);
467 }
468
469 this->hidden->lastchunk = cursor / this->spec.size;
470 #endif
471
472 return 0;
473 }
474
475 static int
DSOUND_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)476 DSOUND_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
477 {
478 const DWORD numchunks = 8;
479 HRESULT result;
480 SDL_bool valid_format = SDL_FALSE;
481 SDL_bool tried_format = SDL_FALSE;
482 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
483 LPGUID guid = (LPGUID) handle;
484 DWORD bufsize;
485
486 /* Initialize all variables that we clean on shutdown */
487 this->hidden = (struct SDL_PrivateAudioData *)
488 SDL_malloc((sizeof *this->hidden));
489 if (this->hidden == NULL) {
490 return SDL_OutOfMemory();
491 }
492 SDL_zerop(this->hidden);
493
494 /* Open the audio device */
495 if (iscapture) {
496 result = pDirectSoundCaptureCreate8(guid, &this->hidden->capture, NULL);
497 if (result != DS_OK) {
498 return SetDSerror("DirectSoundCaptureCreate8", result);
499 }
500 } else {
501 result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL);
502 if (result != DS_OK) {
503 return SetDSerror("DirectSoundCreate8", result);
504 }
505 result = IDirectSound_SetCooperativeLevel(this->hidden->sound,
506 GetDesktopWindow(),
507 DSSCL_NORMAL);
508 if (result != DS_OK) {
509 return SetDSerror("DirectSound SetCooperativeLevel", result);
510 }
511 }
512
513 while ((!valid_format) && (test_format)) {
514 switch (test_format) {
515 case AUDIO_U8:
516 case AUDIO_S16:
517 case AUDIO_S32:
518 case AUDIO_F32:
519 tried_format = SDL_TRUE;
520
521 this->spec.format = test_format;
522
523 /* Update the fragment size as size in bytes */
524 SDL_CalculateAudioSpec(&this->spec);
525
526 bufsize = numchunks * this->spec.size;
527 if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
528 SDL_SetError("Sound buffer size must be between %d and %d",
529 (int) ((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
530 (int) (DSBSIZE_MAX / numchunks));
531 } else {
532 int rc;
533 WAVEFORMATEX wfmt;
534 SDL_zero(wfmt);
535 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
536 wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
537 } else {
538 wfmt.wFormatTag = WAVE_FORMAT_PCM;
539 }
540
541 wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
542 wfmt.nChannels = this->spec.channels;
543 wfmt.nSamplesPerSec = this->spec.freq;
544 wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8);
545 wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
546
547 rc = iscapture ? CreateCaptureBuffer(this, bufsize, &wfmt) : CreateSecondary(this, bufsize, &wfmt);
548 if (rc == 0) {
549 this->hidden->num_buffers = numchunks;
550 valid_format = SDL_TRUE;
551 }
552 }
553 break;
554 }
555 test_format = SDL_NextAudioFormat();
556 }
557
558 if (!valid_format) {
559 if (tried_format) {
560 return -1; /* CreateSecondary() should have called SDL_SetError(). */
561 }
562 return SDL_SetError("DirectSound: Unsupported audio format");
563 }
564
565 /* Playback buffers will auto-start playing in DSOUND_WaitDevice() */
566
567 return 0; /* good to go. */
568 }
569
570
571 static void
DSOUND_Deinitialize(void)572 DSOUND_Deinitialize(void)
573 {
574 DSOUND_Unload();
575 }
576
577
578 static int
DSOUND_Init(SDL_AudioDriverImpl * impl)579 DSOUND_Init(SDL_AudioDriverImpl * impl)
580 {
581 if (!DSOUND_Load()) {
582 return 0;
583 }
584
585 /* Set the function pointers */
586 impl->DetectDevices = DSOUND_DetectDevices;
587 impl->OpenDevice = DSOUND_OpenDevice;
588 impl->PlayDevice = DSOUND_PlayDevice;
589 impl->WaitDevice = DSOUND_WaitDevice;
590 impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
591 impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
592 impl->FlushCapture = DSOUND_FlushCapture;
593 impl->CloseDevice = DSOUND_CloseDevice;
594 impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
595 impl->Deinitialize = DSOUND_Deinitialize;
596
597 impl->HasCaptureSupport = SDL_TRUE;
598
599 return 1; /* this audio target is available. */
600 }
601
602 AudioBootStrap DSOUND_bootstrap = {
603 "directsound", "DirectSound", DSOUND_Init, 0
604 };
605
606 #endif /* SDL_AUDIO_DRIVER_DSOUND */
607
608 /* vi: set ts=4 sw=4 expandtab: */
609