src/audio/winmm/SDL_winmm.c
author Ryan C. Gordon
Thu, 04 Aug 2011 01:24:22 -0400
changeset 5588 57bfc2a2a452
child 5593 ab22ca13c47f
permissions -rw-r--r--
Reworked Windows waveOut code.

Implemented multi-device support, changed name to "winmm".
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2011 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 /* Allow access to a raw mixing buffer */
    24 
    25 #include "../../core/windows/SDL_windows.h"
    26 #include <mmsystem.h>
    27 
    28 #include "SDL_timer.h"
    29 #include "SDL_audio.h"
    30 #include "../SDL_audio_c.h"
    31 #include "SDL_winmm.h"
    32 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
    33 #include "win_ce_semaphore.h"
    34 #endif
    35 
    36 
    37 /* !!! FIXME: this is a cut and paste of SDL_FreeUnixAudioDevices(),
    38  * !!! FIXME:  which is more proof this needs to be managed in SDL_audio.c
    39  * !!! FIXME:  and not in drivers.
    40  */
    41 static void
    42 FreeWaveOutAudioDevices(char ***devices, int *devCount)
    43 {
    44     int i = *devCount;
    45     if ((i > 0) && (*devices != NULL)) {
    46         while (i--) {
    47             SDL_free((*devices)[i]);
    48         }
    49     }
    50 
    51     if (*devices != NULL) {
    52         SDL_free(*devices);
    53     }
    54 
    55     *devices = NULL;
    56     *devCount = 0;
    57 }
    58 
    59 static char **outputDevices = NULL;
    60 static int outputDeviceCount = 0;
    61 static char **inputDevices = NULL;
    62 static int inputDeviceCount = 0;
    63 
    64 static int
    65 DetectWaveOutDevices(void)
    66 {
    67     UINT i;
    68     const UINT devcount = waveOutGetNumDevs();
    69     WAVEOUTCAPS caps;
    70     FreeWaveOutAudioDevices(&outputDevices, &outputDeviceCount);
    71     outputDevices = (const char **) SDL_malloc(sizeof (char *) * devcount);
    72     for (i = 0; i < devcount; i++) {
    73         if (waveOutGetDevCaps(i, &caps, sizeof (caps)) == MMSYSERR_NOERROR) {
    74             outputDevices[outputDeviceCount] = WIN_StringToUTF8(caps.szPname);
    75             if (outputDevices[outputDeviceCount] != NULL) {
    76                 outputDeviceCount++;
    77             }
    78         }
    79     }
    80     return outputDeviceCount;
    81 }
    82 
    83 static int
    84 DetectWaveInDevices(void)
    85 {
    86     UINT i;
    87     const UINT devcount = waveInGetNumDevs();
    88     WAVEINCAPS caps;
    89     FreeWaveInAudioDevices(&inputDevices, &inputDeviceCount);
    90     inputDevices = (const char **) SDL_malloc(sizeof (char *) * devcount);
    91     for (i = 0; i < devcount; i++) {
    92         if (waveInGetDevCaps(i, &caps, sizeof (caps)) == MMSYSERR_NOERROR) {
    93             inputDevices[inputDeviceCount] = WIN_StringToUTF8(caps.szPname);
    94             if (inputDevices[inputDeviceCount] != NULL) {
    95                 inputDeviceCount++;
    96             }
    97         }
    98     }
    99     return inputDeviceCount;
   100 }
   101 
   102 static int
   103 WINMM_DetectDevices(int iscapture)
   104 {
   105     return (iscapture) ? DetectWaveInDevices() : DetectWaveOutDevices();
   106 }
   107 
   108 static const char *
   109 WINMM_GetDeviceName(int index, int iscapture)
   110 {
   111     if ((iscapture) && (index < inputDeviceCount)) {
   112         return inputDevices[index];
   113     } else if ((!iscapture) && (index < outputDeviceCount)) {
   114         return outputDevices[index];
   115     }
   116 
   117     SDL_SetError("No such device");
   118     return NULL;
   119 }
   120 
   121 static void CALLBACK
   122 CaptureSound(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
   123           DWORD_PTR dwParam1, DWORD_PTR dwParam2)
   124 {
   125     SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance;
   126 
   127     /* Only service "buffer is filled" messages */
   128     if (uMsg != WIM_DATA)
   129         return;
   130 
   131     /* Signal that we have a new buffer of data */
   132 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
   133     ReleaseSemaphoreCE(this->hidden->audio_sem, 1, NULL);
   134 #else
   135     ReleaseSemaphore(this->hidden->audio_sem, 1, NULL);
   136 #endif
   137 }
   138 
   139 
   140 /* The Win32 callback for filling the WAVE device */
   141 static void CALLBACK
   142 FillSound(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
   143           DWORD_PTR dwParam1, DWORD_PTR dwParam2)
   144 {
   145     SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance;
   146 
   147     /* Only service "buffer done playing" messages */
   148     if (uMsg != WOM_DONE)
   149         return;
   150 
   151     /* Signal that we are done playing a buffer */
   152 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
   153     ReleaseSemaphoreCE(this->hidden->audio_sem, 1, NULL);
   154 #else
   155     ReleaseSemaphore(this->hidden->audio_sem, 1, NULL);
   156 #endif
   157 }
   158 
   159 static void
   160 SetMMerror(char *function, MMRESULT code)
   161 {
   162     size_t len;
   163     char errbuf[MAXERRORLENGTH];
   164     wchar_t werrbuf[MAXERRORLENGTH];
   165 
   166     SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: ", function);
   167     len = SDL_strlen(errbuf);
   168 
   169     waveOutGetErrorText(code, werrbuf, MAXERRORLENGTH - len);
   170     WideCharToMultiByte(CP_ACP, 0, werrbuf, -1, errbuf + len,
   171                         MAXERRORLENGTH - len, NULL, NULL);
   172 
   173     SDL_SetError("%s", errbuf);
   174 }
   175 
   176 static void
   177 WINMM_WaitDevice(_THIS)
   178 {
   179     /* Wait for an audio chunk to finish */
   180 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
   181     WaitForSemaphoreCE(this->hidden->audio_sem, INFINITE);
   182 #else
   183     WaitForSingleObject(this->hidden->audio_sem, INFINITE);
   184 #endif
   185 }
   186 
   187 static Uint8 *
   188 WINMM_GetDeviceBuf(_THIS)
   189 {
   190     return (Uint8 *) (this->hidden->
   191                       wavebuf[this->hidden->next_buffer].lpData);
   192 }
   193 
   194 static void
   195 WINMM_PlayDevice(_THIS)
   196 {
   197     /* Queue it up */
   198     waveOutWrite(this->hidden->hout,
   199                  &this->hidden->wavebuf[this->hidden->next_buffer],
   200                  sizeof(this->hidden->wavebuf[0]));
   201     this->hidden->next_buffer = (this->hidden->next_buffer + 1) % NUM_BUFFERS;
   202 }
   203 
   204 static void
   205 WINMM_WaitDone(_THIS)
   206 {
   207     int i, left;
   208 
   209     do {
   210         left = NUM_BUFFERS;
   211         for (i = 0; i < NUM_BUFFERS; ++i) {
   212             if (this->hidden->wavebuf[i].dwFlags & WHDR_DONE) {
   213                 --left;
   214             }
   215         }
   216         if (left > 0) {
   217             SDL_Delay(100);
   218         }
   219     } while (left > 0);
   220 }
   221 
   222 static void
   223 WINMM_CloseDevice(_THIS)
   224 {
   225     /* Close up audio */
   226     if (this->hidden != NULL) {
   227         int i;
   228 
   229         if (this->hidden->audio_sem) {
   230 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
   231             CloseSynchHandle(this->hidden->audio_sem);
   232 #else
   233             CloseHandle(this->hidden->audio_sem);
   234 #endif
   235             this->hidden->audio_sem = 0;
   236         }
   237 
   238         if (this->hidden->hin) {
   239             waveInClose(this->hidden->hin);
   240             this->hidden->hin = 0;
   241         }
   242 
   243         if (this->hidden->hout) {
   244             waveOutClose(this->hidden->hout);
   245             this->hidden->hout = 0;
   246         }
   247 
   248         /* Clean up mixing buffers */
   249         for (i = 0; i < NUM_BUFFERS; ++i) {
   250             if (this->hidden->wavebuf[i].dwUser != 0xFFFF) {
   251                 waveOutUnprepareHeader(this->hidden->hout,
   252                                        &this->hidden->wavebuf[i],
   253                                        sizeof(this->hidden->wavebuf[i]));
   254                 this->hidden->wavebuf[i].dwUser = 0xFFFF;
   255             }
   256         }
   257 
   258         if (this->hidden->mixbuf != NULL) {
   259             /* Free raw mixing buffer */
   260             SDL_free(this->hidden->mixbuf);
   261             this->hidden->mixbuf = NULL;
   262         }
   263 
   264         SDL_free(this->hidden);
   265         this->hidden = NULL;
   266     }
   267 }
   268 
   269 static int
   270 WINMM_OpenDevice(_THIS, const char *devname, int iscapture)
   271 {
   272     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   273     int valid_datatype = 0;
   274     MMRESULT result;
   275     WAVEFORMATEX waveformat;
   276     UINT_PTR devId = WAVE_MAPPER;  /* WAVE_MAPPER == choose system's default */
   277     char *utf8 = NULL;
   278     int i;
   279 
   280     if (devname != NULL) {  /* specific device requested? */
   281         if (iscapture) {
   282             const int devcount = (int) waveInGetNumDevs();
   283             WAVEINCAPS caps;
   284             for (i = 0; (i < devcount) && (devId == WAVE_MAPPER); i++) {
   285                 result = waveInGetDevCaps(i, &caps, sizeof (caps));
   286                 if (result != MMSYSERR_NOERROR)
   287                     continue;
   288                 else if ((utf8 = WIN_StringToUTF8(caps.szPname)) == NULL)
   289                     continue;
   290                 else if (SDL_strcmp(devname, utf8) == 0)
   291                     devId = (UINT_PTR) i;
   292                 SDL_free(utf8);
   293             }
   294         } else {
   295             const int devcount = (int) waveOutGetNumDevs();
   296             WAVEOUTCAPS caps;
   297             for (i = 0; (i < devcount) && (devId == WAVE_MAPPER); i++) {
   298                 result = waveOutGetDevCaps(i, &caps, sizeof (caps));
   299                 if (result != MMSYSERR_NOERROR)
   300                     continue;
   301                 else if ((utf8 = WIN_StringToUTF8(caps.szPname)) == NULL)
   302                     continue;
   303                 else if (SDL_strcmp(devname, utf8) == 0)
   304                     devId = (UINT_PTR) i;
   305                 SDL_free(utf8);
   306             }
   307         }
   308 
   309         if (devId == WAVE_MAPPER) {
   310             SDL_SetError("Requested device not found");
   311             return 0;
   312         }
   313     }
   314 
   315     /* Initialize all variables that we clean on shutdown */
   316     this->hidden = (struct SDL_PrivateAudioData *)
   317         SDL_malloc((sizeof *this->hidden));
   318     if (this->hidden == NULL) {
   319         SDL_OutOfMemory();
   320         return 0;
   321     }
   322     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   323 
   324     /* Initialize the wavebuf structures for closing */
   325     for (i = 0; i < NUM_BUFFERS; ++i)
   326         this->hidden->wavebuf[i].dwUser = 0xFFFF;
   327 
   328     while ((!valid_datatype) && (test_format)) {
   329         valid_datatype = 1;
   330         this->spec.format = test_format;
   331         switch (test_format) {
   332         case AUDIO_U8:
   333         case AUDIO_S16:
   334         case AUDIO_S32:
   335             break;              /* valid. */
   336 
   337         default:
   338             valid_datatype = 0;
   339             test_format = SDL_NextAudioFormat();
   340             break;
   341         }
   342     }
   343 
   344     if (!valid_datatype) {
   345         WINMM_CloseDevice(this);
   346         SDL_SetError("Unsupported audio format");
   347         return 0;
   348     }
   349 
   350     /* Set basic WAVE format parameters */
   351     SDL_memset(&waveformat, '\0', sizeof(waveformat));
   352     waveformat.wFormatTag = WAVE_FORMAT_PCM;
   353     waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format);
   354 
   355     if (this->spec.channels > 2)
   356         this->spec.channels = 2;        /* !!! FIXME: is this right? */
   357 
   358     waveformat.nChannels = this->spec.channels;
   359     waveformat.nSamplesPerSec = this->spec.freq;
   360     waveformat.nBlockAlign =
   361         waveformat.nChannels * (waveformat.wBitsPerSample / 8);
   362     waveformat.nAvgBytesPerSec =
   363         waveformat.nSamplesPerSec * waveformat.nBlockAlign;
   364 
   365     /* Check the buffer size -- minimum of 1/4 second (word aligned) */
   366     if (this->spec.samples < (this->spec.freq / 4))
   367         this->spec.samples = ((this->spec.freq / 4) + 3) & ~3;
   368 
   369     /* Update the fragment size as size in bytes */
   370     SDL_CalculateAudioSpec(&this->spec);
   371 
   372     /* Open the audio device */
   373     if (iscapture) {
   374         result = waveInOpen(&this->hidden->hin, devId, &waveformat,
   375                              (DWORD_PTR) CaptureSound, (DWORD_PTR) this,
   376                              CALLBACK_FUNCTION);
   377     } else {
   378         result = waveOutOpen(&this->hidden->hout, devId, &waveformat,
   379                              (DWORD_PTR) FillSound, (DWORD_PTR) this,
   380                              CALLBACK_FUNCTION);
   381     }
   382 
   383     if (result != MMSYSERR_NOERROR) {
   384         WINMM_CloseDevice(this);
   385         SetMMerror("waveOutOpen()", result);
   386         return 0;
   387     }
   388 #ifdef SOUND_DEBUG
   389     /* Check the sound device we retrieved */
   390     {
   391         WAVEOUTCAPS caps;
   392 
   393         result = waveOutGetDevCaps((UINT) this->hidden->hout,
   394                                    &caps, sizeof(caps));
   395         if (result != MMSYSERR_NOERROR) {
   396             WINMM_CloseDevice(this);
   397             SetMMerror("waveOutGetDevCaps()", result);
   398             return 0;
   399         }
   400         printf("Audio device: %s\n", caps.szPname);
   401     }
   402 #endif
   403 
   404     /* Create the audio buffer semaphore */
   405     this->hidden->audio_sem =
   406 #if defined(_WIN32_WCE) && (_WIN32_WCE < 300)
   407         CreateSemaphoreCE(NULL, NUM_BUFFERS - 1, NUM_BUFFERS, NULL);
   408 #else
   409         CreateSemaphore(NULL, NUM_BUFFERS - 1, NUM_BUFFERS, NULL);
   410 #endif
   411     if (this->hidden->audio_sem == NULL) {
   412         WINMM_CloseDevice(this);
   413         SDL_SetError("Couldn't create semaphore");
   414         return 0;
   415     }
   416 
   417     /* Create the sound buffers */
   418     this->hidden->mixbuf =
   419         (Uint8 *) SDL_malloc(NUM_BUFFERS * this->spec.size);
   420     if (this->hidden->mixbuf == NULL) {
   421         WINMM_CloseDevice(this);
   422         SDL_OutOfMemory();
   423         return 0;
   424     }
   425     for (i = 0; i < NUM_BUFFERS; ++i) {
   426         SDL_memset(&this->hidden->wavebuf[i], '\0',
   427                    sizeof(this->hidden->wavebuf[i]));
   428         this->hidden->wavebuf[i].dwBufferLength = this->spec.size;
   429         this->hidden->wavebuf[i].dwFlags = WHDR_DONE;
   430         this->hidden->wavebuf[i].lpData =
   431             (LPSTR) & this->hidden->mixbuf[i * this->spec.size];
   432         result = waveOutPrepareHeader(this->hidden->hout,
   433                                       &this->hidden->wavebuf[i],
   434                                       sizeof(this->hidden->wavebuf[i]));
   435         if (result != MMSYSERR_NOERROR) {
   436             WINMM_CloseDevice(this);
   437             SetMMerror("waveOutPrepareHeader()", result);
   438             return 0;
   439         }
   440     }
   441 
   442     return 1;                   /* Ready to go! */
   443 }
   444 
   445 
   446 static int
   447 WINMM_Init(SDL_AudioDriverImpl * impl)
   448 {
   449     /* Set the function pointers */
   450     impl->DetectDevices = WINMM_DetectDevices;
   451     impl->GetDeviceName = WINMM_GetDeviceName;
   452     impl->OpenDevice = WINMM_OpenDevice;
   453     impl->PlayDevice = WINMM_PlayDevice;
   454     impl->WaitDevice = WINMM_WaitDevice;
   455     impl->WaitDone = WINMM_WaitDone;
   456     impl->GetDeviceBuf = WINMM_GetDeviceBuf;
   457     impl->CloseDevice = WINMM_CloseDevice;
   458 
   459     return 1;   /* this audio target is available. */
   460 }
   461 
   462 AudioBootStrap WINMM_bootstrap = {
   463     "winmm", "Windows Waveform Audio", WINMM_Init, 0
   464 };
   465 
   466 /* vi: set ts=4 sw=4 expandtab: */