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