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