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