src/audio/directsound/SDL_directsound.c
author Sam Lantinga
Sat, 06 Oct 2012 11:23:47 -0700
changeset 6565 1f3c0df426dc
parent 6138 4c64952a58fb
child 6885 700f1b25f77f
permissions -rw-r--r--
When using Xinerama, XVidMode always works on screen 0. Otherwise use the real X11 screen.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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_config.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 /* DirectX function pointers for audio */
    34 static void* DSoundDLL = NULL;
    35 typedef HRESULT(WINAPI*fnDirectSoundCreate8)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN);
    36 typedef HRESULT(WINAPI*fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
    37 typedef HRESULT(WINAPI*fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW,LPVOID);
    38 static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
    39 static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
    40 static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
    41 
    42 static void
    43 DSOUND_Unload(void)
    44 {
    45     pDirectSoundCreate8 = NULL;
    46     pDirectSoundEnumerateW = NULL;
    47     pDirectSoundCaptureEnumerateW = NULL;
    48 
    49     if (DSoundDLL != NULL) {
    50         SDL_UnloadObject(DSoundDLL);
    51         DSoundDLL = NULL;
    52     }
    53 }
    54 
    55 
    56 static int
    57 DSOUND_Load(void)
    58 {
    59     int loaded = 0;
    60 
    61     DSOUND_Unload();
    62 
    63     DSoundDLL = SDL_LoadObject("DSOUND.DLL");
    64     if (DSoundDLL == NULL) {
    65         SDL_SetError("DirectSound: failed to load DSOUND.DLL");
    66     } else {
    67         /* Now make sure we have DirectX 8 or better... */
    68         #define DSOUNDLOAD(f) { \
    69             p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \
    70             if (!p##f) loaded = 0; \
    71         }
    72         loaded = 1;  /* will reset if necessary. */
    73         DSOUNDLOAD(DirectSoundCreate8);
    74         DSOUNDLOAD(DirectSoundEnumerateW);
    75         DSOUNDLOAD(DirectSoundCaptureEnumerateW);
    76         #undef DSOUNDLOAD
    77 
    78         if (!loaded) {
    79             SDL_SetError("DirectSound: System doesn't appear to have DX8.");
    80         }
    81     }
    82 
    83     if (!loaded) {
    84         DSOUND_Unload();
    85     }
    86 
    87     return loaded;
    88 }
    89 
    90 static __inline__ char *
    91 utf16_to_utf8(const WCHAR *S)
    92 {
    93     /* !!! FIXME: this should be UTF-16, not UCS-2! */
    94     return SDL_iconv_string("UTF-8", "UCS-2", (char *)(S),
    95                             (SDL_wcslen(S)+1)*sizeof(WCHAR));
    96 }
    97 
    98 static void
    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     SDL_SetError("%s", errbuf);
   149     return;
   150 }
   151 
   152 
   153 static BOOL CALLBACK
   154 FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data)
   155 {
   156     SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
   157     if (guid != NULL) {  /* skip default device */
   158         char *str = utf16_to_utf8(desc);
   159         if (str != NULL) {
   160             addfn(str);
   161             SDL_free(str);  /* addfn() makes a copy of this string. */
   162         }
   163     }
   164     return TRUE;  /* keep enumerating. */
   165 }
   166 
   167 static void
   168 DSOUND_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
   169 {
   170     if (iscapture) {
   171         pDirectSoundCaptureEnumerateW(FindAllDevs, addfn);
   172     } else {
   173         pDirectSoundEnumerateW(FindAllDevs, addfn);
   174     }
   175 }
   176 
   177 
   178 static void
   179 DSOUND_WaitDevice(_THIS)
   180 {
   181     DWORD status = 0;
   182     DWORD cursor = 0;
   183     DWORD junk = 0;
   184     HRESULT result = DS_OK;
   185 
   186     /* Semi-busy wait, since we have no way of getting play notification
   187        on a primary mixing buffer located in hardware (DirectX 5.0)
   188      */
   189     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   190                                                    &junk, &cursor);
   191     if (result != DS_OK) {
   192         if (result == DSERR_BUFFERLOST) {
   193             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   194         }
   195 #ifdef DEBUG_SOUND
   196         SetDSerror("DirectSound GetCurrentPosition", result);
   197 #endif
   198         return;
   199     }
   200 
   201     while ((cursor / this->hidden->mixlen) == this->hidden->lastchunk) {
   202         /* FIXME: find out how much time is left and sleep that long */
   203         SDL_Delay(1);
   204 
   205         /* Try to restore a lost sound buffer */
   206         IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
   207         if ((status & DSBSTATUS_BUFFERLOST)) {
   208             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   209             IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
   210             if ((status & DSBSTATUS_BUFFERLOST)) {
   211                 break;
   212             }
   213         }
   214         if (!(status & DSBSTATUS_PLAYING)) {
   215             result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0,
   216                                              DSBPLAY_LOOPING);
   217             if (result == DS_OK) {
   218                 continue;
   219             }
   220 #ifdef DEBUG_SOUND
   221             SetDSerror("DirectSound Play", result);
   222 #endif
   223             return;
   224         }
   225 
   226         /* Find out where we are playing */
   227         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   228                                                        &junk, &cursor);
   229         if (result != DS_OK) {
   230             SetDSerror("DirectSound GetCurrentPosition", result);
   231             return;
   232         }
   233     }
   234 }
   235 
   236 static void
   237 DSOUND_PlayDevice(_THIS)
   238 {
   239     /* Unlock the buffer, allowing it to play */
   240     if (this->hidden->locked_buf) {
   241         IDirectSoundBuffer_Unlock(this->hidden->mixbuf,
   242                                   this->hidden->locked_buf,
   243                                   this->hidden->mixlen, NULL, 0);
   244     }
   245 
   246 }
   247 
   248 static Uint8 *
   249 DSOUND_GetDeviceBuf(_THIS)
   250 {
   251     DWORD cursor = 0;
   252     DWORD junk = 0;
   253     HRESULT result = DS_OK;
   254     DWORD rawlen = 0;
   255 
   256     /* Figure out which blocks to fill next */
   257     this->hidden->locked_buf = NULL;
   258     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   259                                                    &junk, &cursor);
   260     if (result == DSERR_BUFFERLOST) {
   261         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   262         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   263                                                        &junk, &cursor);
   264     }
   265     if (result != DS_OK) {
   266         SetDSerror("DirectSound GetCurrentPosition", result);
   267         return (NULL);
   268     }
   269     cursor /= this->hidden->mixlen;
   270 #ifdef DEBUG_SOUND
   271     /* Detect audio dropouts */
   272     {
   273         DWORD spot = cursor;
   274         if (spot < this->hidden->lastchunk) {
   275             spot += this->hidden->num_buffers;
   276         }
   277         if (spot > this->hidden->lastchunk + 1) {
   278             fprintf(stderr, "Audio dropout, missed %d fragments\n",
   279                     (spot - (this->hidden->lastchunk + 1)));
   280         }
   281     }
   282 #endif
   283     this->hidden->lastchunk = cursor;
   284     cursor = (cursor + 1) % this->hidden->num_buffers;
   285     cursor *= this->hidden->mixlen;
   286 
   287     /* Lock the audio buffer */
   288     result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
   289                                      this->hidden->mixlen,
   290                                      (LPVOID *) & this->hidden->locked_buf,
   291                                      &rawlen, NULL, &junk, 0);
   292     if (result == DSERR_BUFFERLOST) {
   293         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   294         result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
   295                                          this->hidden->mixlen,
   296                                          (LPVOID *) & this->
   297                                          hidden->locked_buf, &rawlen, NULL,
   298                                          &junk, 0);
   299     }
   300     if (result != DS_OK) {
   301         SetDSerror("DirectSound Lock", result);
   302         return (NULL);
   303     }
   304     return (this->hidden->locked_buf);
   305 }
   306 
   307 static void
   308 DSOUND_WaitDone(_THIS)
   309 {
   310     Uint8 *stream = DSOUND_GetDeviceBuf(this);
   311 
   312     /* Wait for the playing chunk to finish */
   313     if (stream != NULL) {
   314         SDL_memset(stream, this->spec.silence, this->hidden->mixlen);
   315         DSOUND_PlayDevice(this);
   316     }
   317     DSOUND_WaitDevice(this);
   318 
   319     /* Stop the looping sound buffer */
   320     IDirectSoundBuffer_Stop(this->hidden->mixbuf);
   321 }
   322 
   323 static void
   324 DSOUND_CloseDevice(_THIS)
   325 {
   326     if (this->hidden != NULL) {
   327         if (this->hidden->sound != NULL) {
   328             if (this->hidden->mixbuf != NULL) {
   329                 /* Clean up the audio buffer */
   330                 IDirectSoundBuffer_Release(this->hidden->mixbuf);
   331                 this->hidden->mixbuf = NULL;
   332             }
   333             IDirectSound_Release(this->hidden->sound);
   334             this->hidden->sound = NULL;
   335         }
   336 
   337         SDL_free(this->hidden);
   338         this->hidden = NULL;
   339     }
   340 }
   341 
   342 /* This function tries to create a secondary audio buffer, and returns the
   343    number of audio chunks available in the created buffer.
   344 */
   345 static int
   346 CreateSecondary(_THIS, HWND focus, WAVEFORMATEX * wavefmt)
   347 {
   348     LPDIRECTSOUND sndObj = this->hidden->sound;
   349     LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf;
   350     Uint32 chunksize = this->spec.size;
   351     const int numchunks = 8;
   352     HRESULT result = DS_OK;
   353     DSBUFFERDESC format;
   354     LPVOID pvAudioPtr1, pvAudioPtr2;
   355     DWORD dwAudioBytes1, dwAudioBytes2;
   356 
   357     /* Try to set primary mixing privileges */
   358     if (focus) {
   359         result = IDirectSound_SetCooperativeLevel(sndObj,
   360                                                   focus, DSSCL_PRIORITY);
   361     } else {
   362         result = IDirectSound_SetCooperativeLevel(sndObj,
   363                                                   GetDesktopWindow(),
   364                                                   DSSCL_NORMAL);
   365     }
   366     if (result != DS_OK) {
   367         SetDSerror("DirectSound SetCooperativeLevel", result);
   368         return (-1);
   369     }
   370 
   371     /* Try to create the secondary buffer */
   372     SDL_memset(&format, 0, sizeof(format));
   373     format.dwSize = sizeof(format);
   374     format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
   375     if (!focus) {
   376         format.dwFlags |= DSBCAPS_GLOBALFOCUS;
   377     } else {
   378         format.dwFlags |= DSBCAPS_STICKYFOCUS;
   379     }
   380     format.dwBufferBytes = numchunks * chunksize;
   381     if ((format.dwBufferBytes < DSBSIZE_MIN) ||
   382         (format.dwBufferBytes > DSBSIZE_MAX)) {
   383         SDL_SetError("Sound buffer size must be between %d and %d",
   384                      DSBSIZE_MIN / numchunks, DSBSIZE_MAX / numchunks);
   385         return (-1);
   386     }
   387     format.dwReserved = 0;
   388     format.lpwfxFormat = wavefmt;
   389     result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
   390     if (result != DS_OK) {
   391         SetDSerror("DirectSound CreateSoundBuffer", result);
   392         return (-1);
   393     }
   394     IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt);
   395 
   396     /* Silence the initial audio buffer */
   397     result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
   398                                      (LPVOID *) & pvAudioPtr1, &dwAudioBytes1,
   399                                      (LPVOID *) & pvAudioPtr2, &dwAudioBytes2,
   400                                      DSBLOCK_ENTIREBUFFER);
   401     if (result == DS_OK) {
   402         SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1);
   403         IDirectSoundBuffer_Unlock(*sndbuf,
   404                                   (LPVOID) pvAudioPtr1, dwAudioBytes1,
   405                                   (LPVOID) pvAudioPtr2, dwAudioBytes2);
   406     }
   407 
   408     /* We're ready to go */
   409     return (numchunks);
   410 }
   411 
   412 typedef struct FindDevGUIDData
   413 {
   414     const char *devname;
   415     GUID guid;
   416     int found;
   417 } FindDevGUIDData;
   418 
   419 static BOOL CALLBACK
   420 FindDevGUID(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID _data)
   421 {
   422     if (guid != NULL) {  /* skip the default device. */
   423         FindDevGUIDData *data = (FindDevGUIDData *) _data;
   424         char *str = utf16_to_utf8(desc);
   425         const int match = (SDL_strcmp(str, data->devname) == 0);
   426         SDL_free(str);
   427         if (match) {
   428             data->found = 1;
   429             SDL_memcpy(&data->guid, guid, sizeof (data->guid));
   430             return FALSE;  /* found it! stop enumerating. */
   431         }
   432     }
   433     return TRUE;  /* keep enumerating. */
   434 }
   435 
   436 static int
   437 DSOUND_OpenDevice(_THIS, const char *devname, int iscapture)
   438 {
   439     HRESULT result;
   440     WAVEFORMATEX waveformat;
   441     int valid_format = 0;
   442     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   443     FindDevGUIDData devguid;
   444     LPGUID guid = NULL;
   445 
   446     if (devname != NULL) {
   447         devguid.found = 0;
   448         devguid.devname = devname;
   449         if (iscapture)
   450             pDirectSoundCaptureEnumerateW(FindDevGUID, &devguid);
   451         else
   452             pDirectSoundEnumerateW(FindDevGUID, &devguid);
   453 
   454         if (!devguid.found) {
   455             SDL_SetError("DirectSound: Requested device not found");
   456             return 0;
   457         }
   458         guid = &devguid.guid;
   459     }
   460 
   461     /* Initialize all variables that we clean on shutdown */
   462     this->hidden = (struct SDL_PrivateAudioData *)
   463         SDL_malloc((sizeof *this->hidden));
   464     if (this->hidden == NULL) {
   465         SDL_OutOfMemory();
   466         return 0;
   467     }
   468     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   469 
   470     while ((!valid_format) && (test_format)) {
   471         switch (test_format) {
   472         case AUDIO_U8:
   473         case AUDIO_S16:
   474         case AUDIO_S32:
   475             this->spec.format = test_format;
   476             valid_format = 1;
   477             break;
   478         }
   479         test_format = SDL_NextAudioFormat();
   480     }
   481 
   482     if (!valid_format) {
   483         DSOUND_CloseDevice(this);
   484         SDL_SetError("DirectSound: Unsupported audio format");
   485         return 0;
   486     }
   487 
   488     SDL_memset(&waveformat, 0, sizeof(waveformat));
   489     waveformat.wFormatTag = WAVE_FORMAT_PCM;
   490     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   491     waveformat.nChannels = this->spec.channels;
   492     waveformat.nSamplesPerSec = this->spec.freq;
   493     waveformat.nBlockAlign =
   494         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   495     waveformat.nAvgBytesPerSec =
   496         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   497 
   498     /* Update the fragment size as size in bytes */
   499     SDL_CalculateAudioSpec(&this->spec);
   500 
   501     /* Open the audio device */
   502     result = pDirectSoundCreate8(guid, &this->hidden->sound, NULL);
   503     if (result != DS_OK) {
   504         DSOUND_CloseDevice(this);
   505         SetDSerror("DirectSoundCreate", result);
   506         return 0;
   507     }
   508 
   509     /* Create the audio buffer to which we write */
   510     this->hidden->num_buffers = CreateSecondary(this, NULL, &waveformat);
   511     if (this->hidden->num_buffers < 0) {
   512         DSOUND_CloseDevice(this);
   513         return 0;
   514     }
   515 
   516     /* The buffer will auto-start playing in DSOUND_WaitDevice() */
   517     this->hidden->mixlen = this->spec.size;
   518 
   519     return 1;                   /* good to go. */
   520 }
   521 
   522 
   523 static void
   524 DSOUND_Deinitialize(void)
   525 {
   526     DSOUND_Unload();
   527 }
   528 
   529 
   530 static int
   531 DSOUND_Init(SDL_AudioDriverImpl * impl)
   532 {
   533     if (!DSOUND_Load()) {
   534         return 0;
   535     }
   536 
   537     /* Set the function pointers */
   538     impl->DetectDevices = DSOUND_DetectDevices;
   539     impl->OpenDevice = DSOUND_OpenDevice;
   540     impl->PlayDevice = DSOUND_PlayDevice;
   541     impl->WaitDevice = DSOUND_WaitDevice;
   542     impl->WaitDone = DSOUND_WaitDone;
   543     impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
   544     impl->CloseDevice = DSOUND_CloseDevice;
   545     impl->Deinitialize = DSOUND_Deinitialize;
   546 
   547     return 1;   /* this audio target is available. */
   548 }
   549 
   550 AudioBootStrap DSOUND_bootstrap = {
   551     "directsound", "DirectSound", DSOUND_Init, 0
   552 };
   553 
   554 #endif /* SDL_AUDIO_DRIVER_DSOUND */
   555 
   556 /* vi: set ts=4 sw=4 expandtab: */