1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 // DWORD junk, cursor;
438 HRESULT result;
439
440 SDL_zero(format);
441 format.dwSize = sizeof (format);
442 format.dwFlags = DSCBCAPS_WAVEMAPPED;
443 format.dwBufferBytes = bufsize;
444 format.lpwfxFormat = wfmt;
445
446 result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
447 if (result != DS_OK) {
448 return SetDSerror("DirectSound CreateCaptureBuffer", result);
449 }
450
451 result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
452 if (result != DS_OK) {
453 IDirectSoundCaptureBuffer_Release(*capturebuf);
454 return SetDSerror("DirectSound Start", result);
455 }
456
457 #if 0
458 /* presumably this starts at zero, but just in case... */
459 result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
460 if (result != DS_OK) {
461 IDirectSoundCaptureBuffer_Stop(*capturebuf);
462 IDirectSoundCaptureBuffer_Release(*capturebuf);
463 return SetDSerror("DirectSound GetCurrentPosition", result);
464 }
465
466 this->hidden->lastchunk = cursor / this->spec.size;
467 #endif
468
469 return 0;
470 }
471
472 static int
DSOUND_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)473 DSOUND_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
474 {
475 const DWORD numchunks = 8;
476 HRESULT result;
477 SDL_bool valid_format = SDL_FALSE;
478 SDL_bool tried_format = SDL_FALSE;
479 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
480 LPGUID guid = (LPGUID) handle;
481 DWORD bufsize;
482
483 /* Initialize all variables that we clean on shutdown */
484 this->hidden = (struct SDL_PrivateAudioData *)
485 SDL_malloc((sizeof *this->hidden));
486 if (this->hidden == NULL) {
487 return SDL_OutOfMemory();
488 }
489 SDL_zerop(this->hidden);
490
491 /* Open the audio device */
492 if (iscapture) {
493 result = pDirectSoundCaptureCreate8(guid, &this->hidden->capture, NULL);
494 if (result != DS_OK) {
495 return SetDSerror("DirectSoundCaptureCreate8", result);
496 }
497 } else {
498 result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL);
499 if (result != DS_OK) {
500 return SetDSerror("DirectSoundCreate8", result);
501 }
502 result = IDirectSound_SetCooperativeLevel(this->hidden->sound,
503 GetDesktopWindow(),
504 DSSCL_NORMAL);
505 if (result != DS_OK) {
506 return SetDSerror("DirectSound SetCooperativeLevel", result);
507 }
508 }
509
510 while ((!valid_format) && (test_format)) {
511 switch (test_format) {
512 case AUDIO_U8:
513 case AUDIO_S16:
514 case AUDIO_S32:
515 case AUDIO_F32:
516 tried_format = SDL_TRUE;
517
518 this->spec.format = test_format;
519
520 /* Update the fragment size as size in bytes */
521 SDL_CalculateAudioSpec(&this->spec);
522
523 bufsize = numchunks * this->spec.size;
524 if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
525 SDL_SetError("Sound buffer size must be between %d and %d",
526 (DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks,
527 DSBSIZE_MAX / numchunks);
528 } else {
529 int rc;
530 WAVEFORMATEX wfmt;
531 SDL_zero(wfmt);
532 if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
533 wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
534 } else {
535 wfmt.wFormatTag = WAVE_FORMAT_PCM;
536 }
537
538 wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
539 wfmt.nChannels = this->spec.channels;
540 wfmt.nSamplesPerSec = this->spec.freq;
541 wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8);
542 wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
543
544 rc = iscapture ? CreateCaptureBuffer(this, bufsize, &wfmt) : CreateSecondary(this, bufsize, &wfmt);
545 if (rc == 0) {
546 this->hidden->num_buffers = numchunks;
547 valid_format = SDL_TRUE;
548 }
549 }
550 break;
551 }
552 test_format = SDL_NextAudioFormat();
553 }
554
555 if (!valid_format) {
556 if (tried_format) {
557 return -1; /* CreateSecondary() should have called SDL_SetError(). */
558 }
559 return SDL_SetError("DirectSound: Unsupported audio format");
560 }
561
562 /* Playback buffers will auto-start playing in DSOUND_WaitDevice() */
563
564 return 0; /* good to go. */
565 }
566
567
568 static void
DSOUND_Deinitialize(void)569 DSOUND_Deinitialize(void)
570 {
571 DSOUND_Unload();
572 }
573
574
575 static int
DSOUND_Init(SDL_AudioDriverImpl * impl)576 DSOUND_Init(SDL_AudioDriverImpl * impl)
577 {
578 if (!DSOUND_Load()) {
579 return 0;
580 }
581
582 /* Set the function pointers */
583 impl->DetectDevices = DSOUND_DetectDevices;
584 impl->OpenDevice = DSOUND_OpenDevice;
585 impl->PlayDevice = DSOUND_PlayDevice;
586 impl->WaitDevice = DSOUND_WaitDevice;
587 impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
588 impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
589 impl->FlushCapture = DSOUND_FlushCapture;
590 impl->CloseDevice = DSOUND_CloseDevice;
591 impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
592 impl->Deinitialize = DSOUND_Deinitialize;
593
594 impl->HasCaptureSupport = SDL_TRUE;
595
596 return 1; /* this audio target is available. */
597 }
598
599 AudioBootStrap DSOUND_bootstrap = {
600 "directsound", "DirectSound", DSOUND_Init, 0
601 };
602
603 #endif /* SDL_AUDIO_DRIVER_DSOUND */
604
605 /* vi: set ts=4 sw=4 expandtab: */
606