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