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