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