1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 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