SDL  2.0
SDL_directsound.c
Go to the documentation of this file.
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_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*fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW,LPVOID);
42 static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
43 static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
44 static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
45 
46 static void
47 DSOUND_Unload(void)
48 {
49  pDirectSoundCreate8 = NULL;
50  pDirectSoundEnumerateW = NULL;
51  pDirectSoundCaptureEnumerateW = NULL;
52 
53  if (DSoundDLL != NULL) {
54  SDL_UnloadObject(DSoundDLL);
55  DSoundDLL = NULL;
56  }
57 }
58 
59 
60 static int
61 DSOUND_Load(void)
62 {
63  int loaded = 0;
64 
65  DSOUND_Unload();
66 
67  DSoundDLL = SDL_LoadObject("DSOUND.DLL");
68  if (DSoundDLL == NULL) {
69  SDL_SetError("DirectSound: failed to load DSOUND.DLL");
70  } else {
71  /* Now make sure we have DirectX 8 or better... */
72  #define DSOUNDLOAD(f) { \
73  p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \
74  if (!p##f) loaded = 0; \
75  }
76  loaded = 1; /* will reset if necessary. */
77  DSOUNDLOAD(DirectSoundCreate8);
78  DSOUNDLOAD(DirectSoundEnumerateW);
79  DSOUNDLOAD(DirectSoundCaptureEnumerateW);
80  #undef DSOUNDLOAD
81 
82  if (!loaded) {
83  SDL_SetError("DirectSound: System doesn't appear to have DX8.");
84  }
85  }
86 
87  if (!loaded) {
88  DSOUND_Unload();
89  }
90 
91  return loaded;
92 }
93 
94 static int
95 SetDSerror(const char *function, int code)
96 {
97  static const char *error;
98  static char errbuf[1024];
99 
100  errbuf[0] = 0;
101  switch (code) {
102  case E_NOINTERFACE:
103  error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
104  break;
105  case DSERR_ALLOCATED:
106  error = "Audio device in use";
107  break;
108  case DSERR_BADFORMAT:
109  error = "Unsupported audio format";
110  break;
111  case DSERR_BUFFERLOST:
112  error = "Mixing buffer was lost";
113  break;
114  case DSERR_CONTROLUNAVAIL:
115  error = "Control requested is not available";
116  break;
117  case DSERR_INVALIDCALL:
118  error = "Invalid call for the current state";
119  break;
120  case DSERR_INVALIDPARAM:
121  error = "Invalid parameter";
122  break;
123  case DSERR_NODRIVER:
124  error = "No audio device found";
125  break;
126  case DSERR_OUTOFMEMORY:
127  error = "Out of memory";
128  break;
129  case DSERR_PRIOLEVELNEEDED:
130  error = "Caller doesn't have priority";
131  break;
132  case DSERR_UNSUPPORTED:
133  error = "Function not supported";
134  break;
135  default:
136  SDL_snprintf(errbuf, SDL_arraysize(errbuf),
137  "%s: Unknown DirectSound error: 0x%x", function, code);
138  break;
139  }
140  if (!errbuf[0]) {
141  SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function,
142  error);
143  }
144  return SDL_SetError("%s", errbuf);
145 }
146 
147 static void
148 DSOUND_FreeDeviceHandle(void *handle)
149 {
150  SDL_free(handle);
151 }
152 
153 static BOOL CALLBACK
154 FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data)
155 {
156  const int iscapture = (int) ((size_t) data);
157  if (guid != NULL) { /* skip default device */
158  char *str = WIN_StringToUTF8(desc);
159  if (str != NULL) {
160  LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID));
161  SDL_memcpy(cpyguid, guid, sizeof (GUID));
162  SDL_AddAudioDevice(iscapture, str, cpyguid);
163  SDL_free(str); /* addfn() makes a copy of this string. */
164  }
165  }
166  return TRUE; /* keep enumerating. */
167 }
168 
169 static void
170 DSOUND_DetectDevices(void)
171 {
172  pDirectSoundCaptureEnumerateW(FindAllDevs, (void *) ((size_t) 1));
173  pDirectSoundEnumerateW(FindAllDevs, (void *) ((size_t) 0));
174 }
175 
176 
177 static void
178 DSOUND_WaitDevice(_THIS)
179 {
180  DWORD status = 0;
181  DWORD cursor = 0;
182  DWORD junk = 0;
183  HRESULT result = DS_OK;
184 
185  /* Semi-busy wait, since we have no way of getting play notification
186  on a primary mixing buffer located in hardware (DirectX 5.0)
187  */
188  result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
189  &junk, &cursor);
190  if (result != DS_OK) {
191  if (result == DSERR_BUFFERLOST) {
192  IDirectSoundBuffer_Restore(this->hidden->mixbuf);
193  }
194 #ifdef DEBUG_SOUND
195  SetDSerror("DirectSound GetCurrentPosition", result);
196 #endif
197  return;
198  }
199 
200  while ((cursor / this->hidden->mixlen) == this->hidden->lastchunk) {
201  /* FIXME: find out how much time is left and sleep that long */
202  SDL_Delay(1);
203 
204  /* Try to restore a lost sound buffer */
205  IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
206  if ((status & DSBSTATUS_BUFFERLOST)) {
207  IDirectSoundBuffer_Restore(this->hidden->mixbuf);
208  IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
209  if ((status & DSBSTATUS_BUFFERLOST)) {
210  break;
211  }
212  }
213  if (!(status & DSBSTATUS_PLAYING)) {
214  result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0,
215  DSBPLAY_LOOPING);
216  if (result == DS_OK) {
217  continue;
218  }
219 #ifdef DEBUG_SOUND
220  SetDSerror("DirectSound Play", result);
221 #endif
222  return;
223  }
224 
225  /* Find out where we are playing */
226  result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
227  &junk, &cursor);
228  if (result != DS_OK) {
229  SetDSerror("DirectSound GetCurrentPosition", result);
230  return;
231  }
232  }
233 }
234 
235 static void
236 DSOUND_PlayDevice(_THIS)
237 {
238  /* Unlock the buffer, allowing it to play */
239  if (this->hidden->locked_buf) {
240  IDirectSoundBuffer_Unlock(this->hidden->mixbuf,
241  this->hidden->locked_buf,
242  this->hidden->mixlen, NULL, 0);
243  }
244 
245 }
246 
247 static Uint8 *
248 DSOUND_GetDeviceBuf(_THIS)
249 {
250  DWORD cursor = 0;
251  DWORD junk = 0;
252  HRESULT result = DS_OK;
253  DWORD rawlen = 0;
254 
255  /* Figure out which blocks to fill next */
256  this->hidden->locked_buf = NULL;
257  result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
258  &junk, &cursor);
259  if (result == DSERR_BUFFERLOST) {
260  IDirectSoundBuffer_Restore(this->hidden->mixbuf);
261  result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
262  &junk, &cursor);
263  }
264  if (result != DS_OK) {
265  SetDSerror("DirectSound GetCurrentPosition", result);
266  return (NULL);
267  }
268  cursor /= this->hidden->mixlen;
269 #ifdef DEBUG_SOUND
270  /* Detect audio dropouts */
271  {
272  DWORD spot = cursor;
273  if (spot < this->hidden->lastchunk) {
274  spot += this->hidden->num_buffers;
275  }
276  if (spot > this->hidden->lastchunk + 1) {
277  fprintf(stderr, "Audio dropout, missed %d fragments\n",
278  (spot - (this->hidden->lastchunk + 1)));
279  }
280  }
281 #endif
282  this->hidden->lastchunk = cursor;
283  cursor = (cursor + 1) % this->hidden->num_buffers;
284  cursor *= this->hidden->mixlen;
285 
286  /* Lock the audio buffer */
287  result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
288  this->hidden->mixlen,
289  (LPVOID *) & this->hidden->locked_buf,
290  &rawlen, NULL, &junk, 0);
291  if (result == DSERR_BUFFERLOST) {
292  IDirectSoundBuffer_Restore(this->hidden->mixbuf);
293  result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
294  this->hidden->mixlen,
295  (LPVOID *) & this->
296  hidden->locked_buf, &rawlen, NULL,
297  &junk, 0);
298  }
299  if (result != DS_OK) {
300  SetDSerror("DirectSound Lock", result);
301  return (NULL);
302  }
303  return (this->hidden->locked_buf);
304 }
305 
306 static void
307 DSOUND_WaitDone(_THIS)
308 {
309  Uint8 *stream = DSOUND_GetDeviceBuf(this);
310 
311  /* Wait for the playing chunk to finish */
312  if (stream != NULL) {
313  SDL_memset(stream, this->spec.silence, this->hidden->mixlen);
314  DSOUND_PlayDevice(this);
315  }
316  DSOUND_WaitDevice(this);
317 
318  /* Stop the looping sound buffer */
319  IDirectSoundBuffer_Stop(this->hidden->mixbuf);
320 }
321 
322 static void
323 DSOUND_CloseDevice(_THIS)
324 {
325  if (this->hidden != NULL) {
326  if (this->hidden->sound != NULL) {
327  if (this->hidden->mixbuf != NULL) {
328  /* Clean up the audio buffer */
329  IDirectSoundBuffer_Release(this->hidden->mixbuf);
330  this->hidden->mixbuf = NULL;
331  }
332  IDirectSound_Release(this->hidden->sound);
333  this->hidden->sound = NULL;
334  }
335 
336  SDL_free(this->hidden);
337  this->hidden = NULL;
338  }
339 }
340 
341 /* This function tries to create a secondary audio buffer, and returns the
342  number of audio chunks available in the created buffer.
343 */
344 static int
345 CreateSecondary(_THIS, HWND focus)
346 {
347  LPDIRECTSOUND sndObj = this->hidden->sound;
348  LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf;
349  Uint32 chunksize = this->spec.size;
350  const int numchunks = 8;
351  HRESULT result = DS_OK;
352  DSBUFFERDESC format;
353  LPVOID pvAudioPtr1, pvAudioPtr2;
354  DWORD dwAudioBytes1, dwAudioBytes2;
355  WAVEFORMATEX wfmt;
356 
357  SDL_zero(wfmt);
358 
359  if (SDL_AUDIO_ISFLOAT(this->spec.format)) {
360  wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
361  } else {
362  wfmt.wFormatTag = WAVE_FORMAT_PCM;
363  }
364 
365  wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
366  wfmt.nChannels = this->spec.channels;
367  wfmt.nSamplesPerSec = this->spec.freq;
368  wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8);
369  wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
370 
371  /* Update the fragment size as size in bytes */
373 
374  /* Try to set primary mixing privileges */
375  if (focus) {
376  result = IDirectSound_SetCooperativeLevel(sndObj,
377  focus, DSSCL_PRIORITY);
378  } else {
379  result = IDirectSound_SetCooperativeLevel(sndObj,
380  GetDesktopWindow(),
381  DSSCL_NORMAL);
382  }
383  if (result != DS_OK) {
384  return SetDSerror("DirectSound SetCooperativeLevel", result);
385  }
386 
387  /* Try to create the secondary buffer */
388  SDL_zero(format);
389  format.dwSize = sizeof(format);
390  format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
391  if (!focus) {
392  format.dwFlags |= DSBCAPS_GLOBALFOCUS;
393  } else {
394  format.dwFlags |= DSBCAPS_STICKYFOCUS;
395  }
396  format.dwBufferBytes = numchunks * chunksize;
397  if ((format.dwBufferBytes < DSBSIZE_MIN) ||
398  (format.dwBufferBytes > DSBSIZE_MAX)) {
399  return SDL_SetError("Sound buffer size must be between %d and %d",
400  DSBSIZE_MIN / numchunks, DSBSIZE_MAX / numchunks);
401  }
402  format.dwReserved = 0;
403  format.lpwfxFormat = &wfmt;
404  result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
405  if (result != DS_OK) {
406  return SetDSerror("DirectSound CreateSoundBuffer", result);
407  }
408  IDirectSoundBuffer_SetFormat(*sndbuf, &wfmt);
409 
410  /* Silence the initial audio buffer */
411  result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
412  (LPVOID *) & pvAudioPtr1, &dwAudioBytes1,
413  (LPVOID *) & pvAudioPtr2, &dwAudioBytes2,
414  DSBLOCK_ENTIREBUFFER);
415  if (result == DS_OK) {
416  SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1);
417  IDirectSoundBuffer_Unlock(*sndbuf,
418  (LPVOID) pvAudioPtr1, dwAudioBytes1,
419  (LPVOID) pvAudioPtr2, dwAudioBytes2);
420  }
421 
422  /* We're ready to go */
423  return (numchunks);
424 }
425 
426 static int
427 DSOUND_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
428 {
429  HRESULT result;
430  SDL_bool valid_format = SDL_FALSE;
431  SDL_bool tried_format = SDL_FALSE;
432  SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
433  LPGUID guid = (LPGUID) handle;
434 
435  /* Initialize all variables that we clean on shutdown */
436  this->hidden = (struct SDL_PrivateAudioData *)
437  SDL_malloc((sizeof *this->hidden));
438  if (this->hidden == NULL) {
439  return SDL_OutOfMemory();
440  }
441  SDL_memset(this->hidden, 0, (sizeof *this->hidden));
442 
443  /* Open the audio device */
444  result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL);
445  if (result != DS_OK) {
446  DSOUND_CloseDevice(this);
447  return SetDSerror("DirectSoundCreate", result);
448  }
449 
450  while ((!valid_format) && (test_format)) {
451  switch (test_format) {
452  case AUDIO_U8:
453  case AUDIO_S16:
454  case AUDIO_S32:
455  case AUDIO_F32:
456  tried_format = SDL_TRUE;
457  this->spec.format = test_format;
458  this->hidden->num_buffers = CreateSecondary(this, NULL);
459  if (this->hidden->num_buffers > 0) {
460  valid_format = SDL_TRUE;
461  }
462  break;
463  }
464  test_format = SDL_NextAudioFormat();
465  }
466 
467  if (!valid_format) {
468  DSOUND_CloseDevice(this);
469  if (tried_format) {
470  return -1; /* CreateSecondary() should have called SDL_SetError(). */
471  }
472  return SDL_SetError("DirectSound: Unsupported audio format");
473  }
474 
475  /* The buffer will auto-start playing in DSOUND_WaitDevice() */
476  this->hidden->mixlen = this->spec.size;
477 
478  return 0; /* good to go. */
479 }
480 
481 
482 static void
483 DSOUND_Deinitialize(void)
484 {
485  DSOUND_Unload();
486 }
487 
488 
489 static int
490 DSOUND_Init(SDL_AudioDriverImpl * impl)
491 {
492  if (!DSOUND_Load()) {
493  return 0;
494  }
495 
496  /* Set the function pointers */
497  impl->DetectDevices = DSOUND_DetectDevices;
498  impl->OpenDevice = DSOUND_OpenDevice;
499  impl->PlayDevice = DSOUND_PlayDevice;
500  impl->WaitDevice = DSOUND_WaitDevice;
501  impl->WaitDone = DSOUND_WaitDone;
502  impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
503  impl->CloseDevice = DSOUND_CloseDevice;
504  impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
505 
506  impl->Deinitialize = DSOUND_Deinitialize;
507 
508  return 1; /* this audio target is available. */
509 }
510 
512  "directsound", "DirectSound", DSOUND_Init, 0
513 };
514 
515 #endif /* SDL_AUDIO_DRIVER_DSOUND */
516 
517 /* vi: set ts=4 sw=4 expandtab: */
GLint GLint GLsizei GLsizei GLsizei GLint GLenum format
Definition: SDL_opengl.h:1565
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
Definition: SDL_audio.c:1398
GLuint64EXT * result
void(* DetectDevices)(void)
Definition: SDL_sysaudio.h:71
uint32_t Uint32
An unsigned 32-bit integer type.
Definition: SDL_stdinc.h:155
Uint8 silence
Definition: SDL_audio.h:173
GLuint GLuint stream
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1967
void(* PlayDevice)(_THIS)
Definition: SDL_sysaudio.h:75
void(* WaitDevice)(_THIS)
Definition: SDL_sysaudio.h:74
Uint16 SDL_AudioFormat
Audio format flags.
Definition: SDL_audio.h:64
AudioBootStrap DSOUND_bootstrap
#define SDL_LoadObject
#define SDL_UnloadObject
SDL_AudioFormat SDL_NextAudioFormat(void)
Definition: SDL_audio.c:1410
#define E_NOINTERFACE
Definition: SDL_directx.h:61
SDL_bool
Definition: SDL_stdinc.h:126
SDL_AudioSpec spec
Definition: loopwave.c:35
#define SDL_AUDIO_ISFLOAT(x)
Definition: SDL_audio.h:76
#define AUDIO_U8
Definition: SDL_audio.h:89
#define SDL_memcpy
Uint8 channels
Definition: SDL_audio.h:172
#define _THIS
uint8_t Uint8
An unsigned 8-bit integer type.
Definition: SDL_stdinc.h:139
void SDL_free(void *mem)
#define TRUE
Definition: edid-parse.c:31
#define SDL_AUDIO_BITSIZE(x)
Definition: SDL_audio.h:75
#define AUDIO_S32
Definition: SDL_audio.h:105
void(* Deinitialize)(void)
Definition: SDL_sysaudio.h:83
#define WIN_StringToUTF8(S)
Definition: SDL_windows.h:45
#define SDL_zero(x)
Definition: SDL_stdinc.h:355
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1419
SDL_Cursor * cursor
Definition: testwm2.c:40
#define SDL_Delay
Uint32 size
Definition: SDL_audio.h:176
int(* OpenDevice)(_THIS, void *handle, const char *devname, int iscapture)
Definition: SDL_sysaudio.h:72
#define NULL
Definition: begin_code.h:143
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
#define SDL_SetError
void(* CloseDevice)(_THIS)
Definition: SDL_sysaudio.h:79
void(* FreeDeviceHandle)(void *handle)
Definition: SDL_sysaudio.h:82
SDL_AudioFormat format
Definition: SDL_audio.h:171
#define AUDIO_S16
Definition: SDL_audio.h:96
Uint8 *(* GetDeviceBuf)(_THIS)
Definition: SDL_sysaudio.h:77
#define SDL_snprintf
#define chunksize(p)
Definition: SDL_malloc.c:1756
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:93
#define SDL_malloc
#define AUDIO_F32
Definition: SDL_audio.h:114
#define SDL_memset
void(* WaitDone)(_THIS)
Definition: SDL_sysaudio.h:78
void SDL_AddAudioDevice(const int iscapture, const char *name, void *handle)
Definition: SDL_audio.c:347