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