src/audio/windx5/SDL_dx5audio.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 08 Dec 2008 00:27:32 +0000
changeset 2859 99210400e8b9
parent 2735 204be4fc2726
child 2938 2929ed239d2a
permissions -rw-r--r--
Updated copyright date
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2009 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 #include "SDL_config.h"
    23 
    24 /* Allow access to a raw mixing buffer */
    25 
    26 #include "SDL_timer.h"
    27 #include "SDL_audio.h"
    28 #include "../SDL_audio_c.h"
    29 #include "SDL_dx5audio.h"
    30 
    31 /* !!! FIXME: move this somewhere that other drivers can use it... */
    32 #if defined(_WIN32_WCE)
    33 #define WINDOWS_OS_NAME "Windows CE/PocketPC"
    34 #elif defined(WIN64)
    35 #define WINDOWS_OS_NAME "Win64"
    36 #else
    37 #define WINDOWS_OS_NAME "Win32"
    38 #endif
    39 
    40 /* DirectX function pointers for audio */
    41 static HINSTANCE DSoundDLL = NULL;
    42 static HRESULT(WINAPI * DSoundCreate) (LPGUID, LPDIRECTSOUND *, LPUNKNOWN) =
    43     NULL;
    44 
    45 static void
    46 DSOUND_Unload(void)
    47 {
    48     if (DSoundDLL != NULL) {
    49         FreeLibrary(DSoundDLL);
    50     }
    51 
    52     DSoundCreate = NULL;
    53     DSoundDLL = NULL;
    54 }
    55 
    56 
    57 static int
    58 DSOUND_Load(void)
    59 {
    60     int loaded = 0;
    61 
    62     DSOUND_Unload();
    63 
    64     DSoundDLL = LoadLibrary(TEXT("DSOUND.DLL"));
    65     if (DSoundDLL == NULL) {
    66         SDL_SetError("DirectSound: failed to load DSOUND.DLL");
    67     } else {
    68         /* Now make sure we have DirectX 5 or better... */
    69         /*  (DirectSoundCaptureCreate was added in DX5) */
    70         if (!GetProcAddress(DSoundDLL, TEXT("DirectSoundCaptureCreate"))) {
    71             SDL_SetError("DirectSound: System doesn't appear to have DX5.");
    72         } else {
    73             DSoundCreate = (void *) GetProcAddress(DSoundDLL,
    74                                                    TEXT("DirectSoundCreate"));
    75         }
    76 
    77         if (!DSoundCreate) {
    78             SDL_SetError("DirectSound: Failed to find DirectSoundCreate");
    79         } else {
    80             loaded = 1;
    81         }
    82     }
    83 
    84     if (!loaded) {
    85         DSOUND_Unload();
    86     }
    87 
    88     return loaded;
    89 }
    90 
    91 
    92 static void
    93 SetDSerror(const char *function, int code)
    94 {
    95     static const char *error;
    96     static char errbuf[1024];
    97 
    98     errbuf[0] = 0;
    99     switch (code) {
   100     case E_NOINTERFACE:
   101         error = "Unsupported interface -- Is DirectX 5.0 or later installed?";
   102         break;
   103     case DSERR_ALLOCATED:
   104         error = "Audio device in use";
   105         break;
   106     case DSERR_BADFORMAT:
   107         error = "Unsupported audio format";
   108         break;
   109     case DSERR_BUFFERLOST:
   110         error = "Mixing buffer was lost";
   111         break;
   112     case DSERR_CONTROLUNAVAIL:
   113         error = "Control requested is not available";
   114         break;
   115     case DSERR_INVALIDCALL:
   116         error = "Invalid call for the current state";
   117         break;
   118     case DSERR_INVALIDPARAM:
   119         error = "Invalid parameter";
   120         break;
   121     case DSERR_NODRIVER:
   122         error = "No audio device found";
   123         break;
   124     case DSERR_OUTOFMEMORY:
   125         error = "Out of memory";
   126         break;
   127     case DSERR_PRIOLEVELNEEDED:
   128         error = "Caller doesn't have priority";
   129         break;
   130     case DSERR_UNSUPPORTED:
   131         error = "Function not supported";
   132         break;
   133     default:
   134         SDL_snprintf(errbuf, SDL_arraysize(errbuf),
   135                      "%s: Unknown DirectSound error: 0x%x", function, code);
   136         break;
   137     }
   138     if (!errbuf[0]) {
   139         SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function,
   140                      error);
   141     }
   142     SDL_SetError("%s", errbuf);
   143     return;
   144 }
   145 
   146 /* DirectSound needs to be associated with a window */
   147 static HWND mainwin = NULL;
   148 /* */
   149 
   150 void
   151 DSOUND_SoundFocus(HWND hwnd)
   152 {
   153     /* !!! FIXME: probably broken with multi-window support in SDL 1.3 ... */
   154     mainwin = hwnd;
   155 }
   156 
   157 static void
   158 DSOUND_ThreadInit(_THIS)
   159 {
   160     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
   161 }
   162 
   163 static void
   164 DSOUND_WaitDevice(_THIS)
   165 {
   166     DWORD status = 0;
   167     DWORD cursor = 0;
   168     DWORD junk = 0;
   169     HRESULT result = DS_OK;
   170 
   171     /* Semi-busy wait, since we have no way of getting play notification
   172        on a primary mixing buffer located in hardware (DirectX 5.0)
   173      */
   174     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   175                                                    &junk, &cursor);
   176     if (result != DS_OK) {
   177         if (result == DSERR_BUFFERLOST) {
   178             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   179         }
   180 #ifdef DEBUG_SOUND
   181         SetDSerror("DirectSound GetCurrentPosition", result);
   182 #endif
   183         return;
   184     }
   185 
   186     while ((cursor / this->hidden->mixlen) == this->hidden->lastchunk) {
   187         /* FIXME: find out how much time is left and sleep that long */
   188         SDL_Delay(1);
   189 
   190         /* Try to restore a lost sound buffer */
   191         IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
   192         if ((status & DSBSTATUS_BUFFERLOST)) {
   193             IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   194             IDirectSoundBuffer_GetStatus(this->hidden->mixbuf, &status);
   195             if ((status & DSBSTATUS_BUFFERLOST)) {
   196                 break;
   197             }
   198         }
   199         if (!(status & DSBSTATUS_PLAYING)) {
   200             result = IDirectSoundBuffer_Play(this->hidden->mixbuf, 0, 0,
   201                                              DSBPLAY_LOOPING);
   202             if (result == DS_OK) {
   203                 continue;
   204             }
   205 #ifdef DEBUG_SOUND
   206             SetDSerror("DirectSound Play", result);
   207 #endif
   208             return;
   209         }
   210 
   211         /* Find out where we are playing */
   212         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   213                                                        &junk, &cursor);
   214         if (result != DS_OK) {
   215             SetDSerror("DirectSound GetCurrentPosition", result);
   216             return;
   217         }
   218     }
   219 }
   220 
   221 static void
   222 DSOUND_PlayDevice(_THIS)
   223 {
   224     /* Unlock the buffer, allowing it to play */
   225     if (this->hidden->locked_buf) {
   226         IDirectSoundBuffer_Unlock(this->hidden->mixbuf,
   227                                   this->hidden->locked_buf,
   228                                   this->hidden->mixlen, NULL, 0);
   229     }
   230 
   231 }
   232 
   233 static Uint8 *
   234 DSOUND_GetDeviceBuf(_THIS)
   235 {
   236     DWORD cursor = 0;
   237     DWORD junk = 0;
   238     HRESULT result = DS_OK;
   239     DWORD rawlen = 0;
   240 
   241     /* Figure out which blocks to fill next */
   242     this->hidden->locked_buf = NULL;
   243     result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   244                                                    &junk, &cursor);
   245     if (result == DSERR_BUFFERLOST) {
   246         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   247         result = IDirectSoundBuffer_GetCurrentPosition(this->hidden->mixbuf,
   248                                                        &junk, &cursor);
   249     }
   250     if (result != DS_OK) {
   251         SetDSerror("DirectSound GetCurrentPosition", result);
   252         return (NULL);
   253     }
   254     cursor /= this->hidden->mixlen;
   255 #ifdef DEBUG_SOUND
   256     /* Detect audio dropouts */
   257     {
   258         DWORD spot = cursor;
   259         if (spot < this->hidden->lastchunk) {
   260             spot += this->hidden->num_buffers;
   261         }
   262         if (spot > this->hidden->lastchunk + 1) {
   263             fprintf(stderr, "Audio dropout, missed %d fragments\n",
   264                     (spot - (this->hidden->lastchunk + 1)));
   265         }
   266     }
   267 #endif
   268     this->hidden->lastchunk = cursor;
   269     cursor = (cursor + 1) % this->hidden->num_buffers;
   270     cursor *= this->hidden->mixlen;
   271 
   272     /* Lock the audio buffer */
   273     result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
   274                                      this->hidden->mixlen,
   275                                      (LPVOID *) & this->hidden->locked_buf,
   276                                      &rawlen, NULL, &junk, 0);
   277     if (result == DSERR_BUFFERLOST) {
   278         IDirectSoundBuffer_Restore(this->hidden->mixbuf);
   279         result = IDirectSoundBuffer_Lock(this->hidden->mixbuf, cursor,
   280                                          this->hidden->mixlen,
   281                                          (LPVOID *) & this->
   282                                          hidden->locked_buf, &rawlen, NULL,
   283                                          &junk, 0);
   284     }
   285     if (result != DS_OK) {
   286         SetDSerror("DirectSound Lock", result);
   287         return (NULL);
   288     }
   289     return (this->hidden->locked_buf);
   290 }
   291 
   292 static void
   293 DSOUND_WaitDone(_THIS)
   294 {
   295     Uint8 *stream = DSOUND_GetDeviceBuf(this);
   296 
   297     /* Wait for the playing chunk to finish */
   298     if (stream != NULL) {
   299         SDL_memset(stream, this->spec.silence, this->hidden->mixlen);
   300         DSOUND_PlayDevice(this);
   301     }
   302     DSOUND_WaitDevice(this);
   303 
   304     /* Stop the looping sound buffer */
   305     IDirectSoundBuffer_Stop(this->hidden->mixbuf);
   306 }
   307 
   308 static void
   309 DSOUND_CloseDevice(_THIS)
   310 {
   311     if (this->hidden != NULL) {
   312         if (this->hidden->sound != NULL) {
   313             if (this->hidden->mixbuf != NULL) {
   314                 /* Clean up the audio buffer */
   315                 IDirectSoundBuffer_Release(this->hidden->mixbuf);
   316                 this->hidden->mixbuf = NULL;
   317             }
   318             IDirectSound_Release(this->hidden->sound);
   319             this->hidden->sound = NULL;
   320         }
   321 
   322         SDL_free(this->hidden);
   323         this->hidden = NULL;
   324     }
   325 }
   326 
   327 /* This function tries to create a secondary audio buffer, and returns the
   328    number of audio chunks available in the created buffer.
   329 */
   330 static int
   331 CreateSecondary(_THIS, HWND focus, WAVEFORMATEX * wavefmt)
   332 {
   333     LPDIRECTSOUND sndObj = this->hidden->sound;
   334     LPDIRECTSOUNDBUFFER *sndbuf = &this->hidden->mixbuf;
   335     Uint32 chunksize = this->spec.size;
   336     const int numchunks = 8;
   337     HRESULT result = DS_OK;
   338     DSBUFFERDESC format;
   339     LPVOID pvAudioPtr1, pvAudioPtr2;
   340     DWORD dwAudioBytes1, dwAudioBytes2;
   341 
   342     /* Try to set primary mixing privileges */
   343     if (focus) {
   344         result = IDirectSound_SetCooperativeLevel(sndObj,
   345                                                   focus, DSSCL_PRIORITY);
   346     } else {
   347         result = IDirectSound_SetCooperativeLevel(sndObj,
   348                                                   GetDesktopWindow(),
   349                                                   DSSCL_NORMAL);
   350     }
   351     if (result != DS_OK) {
   352         SetDSerror("DirectSound SetCooperativeLevel", result);
   353         return (-1);
   354     }
   355 
   356     /* Try to create the secondary buffer */
   357     SDL_memset(&format, 0, sizeof(format));
   358     format.dwSize = sizeof(format);
   359     format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
   360     if (!focus) {
   361         format.dwFlags |= DSBCAPS_GLOBALFOCUS;
   362     } else {
   363         format.dwFlags |= DSBCAPS_STICKYFOCUS;
   364     }
   365     format.dwBufferBytes = numchunks * chunksize;
   366     if ((format.dwBufferBytes < DSBSIZE_MIN) ||
   367         (format.dwBufferBytes > DSBSIZE_MAX)) {
   368         SDL_SetError("Sound buffer size must be between %d and %d",
   369                      DSBSIZE_MIN / numchunks, DSBSIZE_MAX / numchunks);
   370         return (-1);
   371     }
   372     format.dwReserved = 0;
   373     format.lpwfxFormat = wavefmt;
   374     result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
   375     if (result != DS_OK) {
   376         SetDSerror("DirectSound CreateSoundBuffer", result);
   377         return (-1);
   378     }
   379     IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt);
   380 
   381     /* Silence the initial audio buffer */
   382     result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
   383                                      (LPVOID *) & pvAudioPtr1, &dwAudioBytes1,
   384                                      (LPVOID *) & pvAudioPtr2, &dwAudioBytes2,
   385                                      DSBLOCK_ENTIREBUFFER);
   386     if (result == DS_OK) {
   387         SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1);
   388         IDirectSoundBuffer_Unlock(*sndbuf,
   389                                   (LPVOID) pvAudioPtr1, dwAudioBytes1,
   390                                   (LPVOID) pvAudioPtr2, dwAudioBytes2);
   391     }
   392 
   393     /* We're ready to go */
   394     return (numchunks);
   395 }
   396 
   397 static int
   398 DSOUND_OpenDevice(_THIS, const char *devname, int iscapture)
   399 {
   400     HRESULT result;
   401     WAVEFORMATEX waveformat;
   402     int valid_format = 0;
   403     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   404 
   405     /* !!! FIXME: handle devname */
   406     /* !!! FIXME: handle iscapture */
   407 
   408     /* Initialize all variables that we clean on shutdown */
   409     this->hidden = (struct SDL_PrivateAudioData *)
   410         SDL_malloc((sizeof *this->hidden));
   411     if (this->hidden == NULL) {
   412         SDL_OutOfMemory();
   413         return 0;
   414     }
   415     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   416 
   417     while ((!valid_format) && (test_format)) {
   418         switch (test_format) {
   419         case AUDIO_U8:
   420         case AUDIO_S16:
   421         case AUDIO_S32:
   422             this->spec.format = test_format;
   423             valid_format = 1;
   424             break;
   425         }
   426         test_format = SDL_NextAudioFormat();
   427     }
   428 
   429     if (!valid_format) {
   430         DSOUND_CloseDevice(this);
   431         SDL_SetError("DirectSound: Unsupported audio format");
   432         return 0;
   433     }
   434 
   435     SDL_memset(&waveformat, 0, sizeof(waveformat));
   436     waveformat.wFormatTag = WAVE_FORMAT_PCM;
   437     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   438     waveformat.nChannels = this->spec.channels;
   439     waveformat.nSamplesPerSec = this->spec.freq;
   440     waveformat.nBlockAlign =
   441         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   442     waveformat.nAvgBytesPerSec =
   443         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   444 
   445     /* Update the fragment size as size in bytes */
   446     SDL_CalculateAudioSpec(&this->spec);
   447 
   448     /* Open the audio device */
   449     result = DSoundCreate(NULL, &this->hidden->sound, NULL);
   450     if (result != DS_OK) {
   451         DSOUND_CloseDevice(this);
   452         SetDSerror("DirectSoundCreate", result);
   453         return 0;
   454     }
   455 
   456     /* Create the audio buffer to which we write */
   457     this->hidden->num_buffers = CreateSecondary(this, mainwin, &waveformat);
   458     if (this->hidden->num_buffers < 0) {
   459         DSOUND_CloseDevice(this);
   460         return 0;
   461     }
   462 
   463     /* The buffer will auto-start playing in DSOUND_WaitDevice() */
   464     this->hidden->mixlen = this->spec.size;
   465 
   466     return 1;                   /* good to go. */
   467 }
   468 
   469 
   470 static void
   471 DSOUND_Deinitialize(void)
   472 {
   473     DSOUND_Unload();
   474 }
   475 
   476 
   477 static int
   478 DSOUND_Init(SDL_AudioDriverImpl * impl)
   479 {
   480     OSVERSIONINFO ver;
   481 
   482     /*
   483      * Unfortunately, the sound drivers on NT have higher latencies than the
   484      *  audio buffers used by many SDL applications, so there are gaps in the
   485      *  audio - it sounds terrible.  Punt for now.
   486      */
   487     SDL_memset(&ver, '\0', sizeof(OSVERSIONINFO));
   488     ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
   489     GetVersionEx(&ver);
   490     if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
   491         if (ver.dwMajorVersion <= 4) {
   492             return 0;           /* NT4.0 or earlier. Disable dsound support. */
   493         }
   494     }
   495 
   496     if (!DSOUND_Load()) {
   497         return 0;
   498     }
   499 
   500     /* Set the function pointers */
   501     impl->OpenDevice = DSOUND_OpenDevice;
   502     impl->PlayDevice = DSOUND_PlayDevice;
   503     impl->WaitDevice = DSOUND_WaitDevice;
   504     impl->WaitDone = DSOUND_WaitDone;
   505     impl->ThreadInit = DSOUND_ThreadInit;
   506     impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
   507     impl->CloseDevice = DSOUND_CloseDevice;
   508     impl->Deinitialize = DSOUND_Deinitialize;
   509     impl->OnlyHasDefaultOutputDevice = 1;       /* !!! FIXME */
   510 
   511     return 1;
   512 }
   513 
   514 AudioBootStrap DSOUND_bootstrap = {
   515     "dsound", WINDOWS_OS_NAME "DirectSound", DSOUND_Init, 0
   516 };
   517 
   518 /* vi: set ts=4 sw=4 expandtab: */