SDL_winmm.c (14243B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 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 /* MinGW32 mmsystem.h doesn't include these structures */ 36 #if defined(__MINGW32__) && defined(_MMSYSTEM_H) 37 38 typedef struct tagWAVEINCAPS2W 39 { 40 WORD wMid; 41 WORD wPid; 42 MMVERSION vDriverVersion; 43 WCHAR szPname[MAXPNAMELEN]; 44 DWORD dwFormats; 45 WORD wChannels; 46 WORD wReserved1; 47 GUID ManufacturerGuid; 48 GUID ProductGuid; 49 GUID NameGuid; 50 } WAVEINCAPS2W,*PWAVEINCAPS2W,*NPWAVEINCAPS2W,*LPWAVEINCAPS2W; 51 52 typedef struct tagWAVEOUTCAPS2W 53 { 54 WORD wMid; 55 WORD wPid; 56 MMVERSION vDriverVersion; 57 WCHAR szPname[MAXPNAMELEN]; 58 DWORD dwFormats; 59 WORD wChannels; 60 WORD wReserved1; 61 DWORD dwSupport; 62 GUID ManufacturerGuid; 63 GUID ProductGuid; 64 GUID NameGuid; 65 } WAVEOUTCAPS2W,*PWAVEOUTCAPS2W,*NPWAVEOUTCAPS2W,*LPWAVEOUTCAPS2W; 66 67 #endif /* defined(__MINGW32__) && defined(_MMSYSTEM_H) */ 68 69 #ifndef WAVE_FORMAT_IEEE_FLOAT 70 #define WAVE_FORMAT_IEEE_FLOAT 0x0003 71 #endif 72 73 #define DETECT_DEV_IMPL(iscap, typ, capstyp) \ 74 static void DetectWave##typ##Devs(void) { \ 75 const UINT iscapture = iscap ? 1 : 0; \ 76 const UINT devcount = wave##typ##GetNumDevs(); \ 77 capstyp##2W caps; \ 78 UINT i; \ 79 for (i = 0; i < devcount; i++) { \ 80 if (wave##typ##GetDevCaps(i,(LP##capstyp##W)&caps,sizeof(caps))==MMSYSERR_NOERROR) { \ 81 char *name = WIN_LookupAudioDeviceName(caps.szPname,&caps.NameGuid); \ 82 if (name != NULL) { \ 83 SDL_AddAudioDevice((int) iscapture, name, (void *) ((size_t) i+1)); \ 84 SDL_free(name); \ 85 } \ 86 } \ 87 } \ 88 } 89 90 DETECT_DEV_IMPL(SDL_FALSE, Out, WAVEOUTCAPS) 91 DETECT_DEV_IMPL(SDL_TRUE, In, WAVEINCAPS) 92 93 static void 94 WINMM_DetectDevices(void) 95 { 96 DetectWaveInDevs(); 97 DetectWaveOutDevs(); 98 } 99 100 static void CALLBACK 101 CaptureSound(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, 102 DWORD_PTR dwParam1, DWORD_PTR dwParam2) 103 { 104 SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance; 105 106 /* Only service "buffer is filled" messages */ 107 if (uMsg != WIM_DATA) 108 return; 109 110 /* Signal that we have a new buffer of data */ 111 ReleaseSemaphore(this->hidden->audio_sem, 1, NULL); 112 } 113 114 115 /* The Win32 callback for filling the WAVE device */ 116 static void CALLBACK 117 FillSound(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, 118 DWORD_PTR dwParam1, DWORD_PTR dwParam2) 119 { 120 SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance; 121 122 /* Only service "buffer done playing" messages */ 123 if (uMsg != WOM_DONE) 124 return; 125 126 /* Signal that we are done playing a buffer */ 127 ReleaseSemaphore(this->hidden->audio_sem, 1, NULL); 128 } 129 130 static int 131 SetMMerror(char *function, MMRESULT code) 132 { 133 int len; 134 char errbuf[MAXERRORLENGTH]; 135 wchar_t werrbuf[MAXERRORLENGTH]; 136 137 SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: ", function); 138 len = SDL_static_cast(int, SDL_strlen(errbuf)); 139 140 waveOutGetErrorText(code, werrbuf, MAXERRORLENGTH - len); 141 WideCharToMultiByte(CP_ACP, 0, werrbuf, -1, errbuf + len, 142 MAXERRORLENGTH - len, NULL, NULL); 143 144 return SDL_SetError("%s", errbuf); 145 } 146 147 static void 148 WINMM_WaitDevice(_THIS) 149 { 150 /* Wait for an audio chunk to finish */ 151 WaitForSingleObject(this->hidden->audio_sem, INFINITE); 152 } 153 154 static Uint8 * 155 WINMM_GetDeviceBuf(_THIS) 156 { 157 return (Uint8 *) (this->hidden-> 158 wavebuf[this->hidden->next_buffer].lpData); 159 } 160 161 static void 162 WINMM_PlayDevice(_THIS) 163 { 164 /* Queue it up */ 165 waveOutWrite(this->hidden->hout, 166 &this->hidden->wavebuf[this->hidden->next_buffer], 167 sizeof(this->hidden->wavebuf[0])); 168 this->hidden->next_buffer = (this->hidden->next_buffer + 1) % NUM_BUFFERS; 169 } 170 171 static int 172 WINMM_CaptureFromDevice(_THIS, void *buffer, int buflen) 173 { 174 const int nextbuf = this->hidden->next_buffer; 175 MMRESULT result; 176 177 SDL_assert(buflen == this->spec.size); 178 179 /* Wait for an audio chunk to finish */ 180 WaitForSingleObject(this->hidden->audio_sem, INFINITE); 181 182 /* Copy it to caller's buffer... */ 183 SDL_memcpy(buffer, this->hidden->wavebuf[nextbuf].lpData, this->spec.size); 184 185 /* requeue the buffer that just finished. */ 186 result = waveInAddBuffer(this->hidden->hin, 187 &this->hidden->wavebuf[nextbuf], 188 sizeof (this->hidden->wavebuf[nextbuf])); 189 if (result != MMSYSERR_NOERROR) { 190 return -1; /* uhoh! Disable the device. */ 191 } 192 193 /* queue the next buffer in sequence, next time. */ 194 this->hidden->next_buffer = (nextbuf + 1) % NUM_BUFFERS; 195 return this->spec.size; 196 } 197 198 static void 199 WINMM_FlushCapture(_THIS) 200 { 201 /* Wait for an audio chunk to finish */ 202 if (WaitForSingleObject(this->hidden->audio_sem, 0) == WAIT_OBJECT_0) { 203 const int nextbuf = this->hidden->next_buffer; 204 /* requeue the buffer that just finished without reading from it. */ 205 waveInAddBuffer(this->hidden->hin, 206 &this->hidden->wavebuf[nextbuf], 207 sizeof (this->hidden->wavebuf[nextbuf])); 208 this->hidden->next_buffer = (nextbuf + 1) % NUM_BUFFERS; 209 } 210 } 211 212 static void 213 WINMM_CloseDevice(_THIS) 214 { 215 int i; 216 217 if (this->hidden->hout) { 218 waveOutReset(this->hidden->hout); 219 220 /* Clean up mixing buffers */ 221 for (i = 0; i < NUM_BUFFERS; ++i) { 222 if (this->hidden->wavebuf[i].dwUser != 0xFFFF) { 223 waveOutUnprepareHeader(this->hidden->hout, 224 &this->hidden->wavebuf[i], 225 sizeof (this->hidden->wavebuf[i])); 226 } 227 } 228 229 waveOutClose(this->hidden->hout); 230 } 231 232 if (this->hidden->hin) { 233 waveInReset(this->hidden->hin); 234 235 /* Clean up mixing buffers */ 236 for (i = 0; i < NUM_BUFFERS; ++i) { 237 if (this->hidden->wavebuf[i].dwUser != 0xFFFF) { 238 waveInUnprepareHeader(this->hidden->hin, 239 &this->hidden->wavebuf[i], 240 sizeof (this->hidden->wavebuf[i])); 241 } 242 } 243 waveInClose(this->hidden->hin); 244 } 245 246 if (this->hidden->audio_sem) { 247 CloseHandle(this->hidden->audio_sem); 248 } 249 250 SDL_free(this->hidden->mixbuf); 251 SDL_free(this->hidden); 252 } 253 254 static SDL_bool 255 PrepWaveFormat(_THIS, UINT devId, WAVEFORMATEX *pfmt, const int iscapture) 256 { 257 SDL_zerop(pfmt); 258 259 if (SDL_AUDIO_ISFLOAT(this->spec.format)) { 260 pfmt->wFormatTag = WAVE_FORMAT_IEEE_FLOAT; 261 } else { 262 pfmt->wFormatTag = WAVE_FORMAT_PCM; 263 } 264 pfmt->wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); 265 266 pfmt->nChannels = this->spec.channels; 267 pfmt->nSamplesPerSec = this->spec.freq; 268 pfmt->nBlockAlign = pfmt->nChannels * (pfmt->wBitsPerSample / 8); 269 pfmt->nAvgBytesPerSec = pfmt->nSamplesPerSec * pfmt->nBlockAlign; 270 271 if (iscapture) { 272 return (waveInOpen(0, devId, pfmt, 0, 0, WAVE_FORMAT_QUERY) == 0); 273 } else { 274 return (waveOutOpen(0, devId, pfmt, 0, 0, WAVE_FORMAT_QUERY) == 0); 275 } 276 } 277 278 static int 279 WINMM_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 280 { 281 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 282 int valid_datatype = 0; 283 MMRESULT result; 284 WAVEFORMATEX waveformat; 285 UINT devId = WAVE_MAPPER; /* WAVE_MAPPER == choose system's default */ 286 UINT i; 287 288 if (handle != NULL) { /* specific device requested? */ 289 /* -1 because we increment the original value to avoid NULL. */ 290 const size_t val = ((size_t) handle) - 1; 291 devId = (UINT) val; 292 } 293 294 /* Initialize all variables that we clean on shutdown */ 295 this->hidden = (struct SDL_PrivateAudioData *) 296 SDL_malloc((sizeof *this->hidden)); 297 if (this->hidden == NULL) { 298 return SDL_OutOfMemory(); 299 } 300 SDL_zerop(this->hidden); 301 302 /* Initialize the wavebuf structures for closing */ 303 for (i = 0; i < NUM_BUFFERS; ++i) 304 this->hidden->wavebuf[i].dwUser = 0xFFFF; 305 306 if (this->spec.channels > 2) 307 this->spec.channels = 2; /* !!! FIXME: is this right? */ 308 309 while ((!valid_datatype) && (test_format)) { 310 switch (test_format) { 311 case AUDIO_U8: 312 case AUDIO_S16: 313 case AUDIO_S32: 314 case AUDIO_F32: 315 this->spec.format = test_format; 316 if (PrepWaveFormat(this, devId, &waveformat, iscapture)) { 317 valid_datatype = 1; 318 } else { 319 test_format = SDL_NextAudioFormat(); 320 } 321 break; 322 323 default: 324 test_format = SDL_NextAudioFormat(); 325 break; 326 } 327 } 328 329 if (!valid_datatype) { 330 return SDL_SetError("Unsupported audio format"); 331 } 332 333 /* Update the fragment size as size in bytes */ 334 SDL_CalculateAudioSpec(&this->spec); 335 336 /* Open the audio device */ 337 if (iscapture) { 338 result = waveInOpen(&this->hidden->hin, devId, &waveformat, 339 (DWORD_PTR) CaptureSound, (DWORD_PTR) this, 340 CALLBACK_FUNCTION); 341 if (result != MMSYSERR_NOERROR) { 342 return SetMMerror("waveInOpen()", result); 343 } 344 } else { 345 result = waveOutOpen(&this->hidden->hout, devId, &waveformat, 346 (DWORD_PTR) FillSound, (DWORD_PTR) this, 347 CALLBACK_FUNCTION); 348 if (result != MMSYSERR_NOERROR) { 349 return SetMMerror("waveOutOpen()", result); 350 } 351 } 352 353 #ifdef SOUND_DEBUG 354 /* Check the sound device we retrieved */ 355 { 356 if (iscapture) { 357 WAVEINCAPS caps; 358 result = waveInGetDevCaps((UINT) this->hidden->hout, 359 &caps, sizeof (caps)); 360 if (result != MMSYSERR_NOERROR) { 361 return SetMMerror("waveInGetDevCaps()", result); 362 } 363 printf("Audio device: %s\n", caps.szPname); 364 } else { 365 WAVEOUTCAPS caps; 366 result = waveOutGetDevCaps((UINT) this->hidden->hout, 367 &caps, sizeof(caps)); 368 if (result != MMSYSERR_NOERROR) { 369 return SetMMerror("waveOutGetDevCaps()", result); 370 } 371 printf("Audio device: %s\n", caps.szPname); 372 } 373 } 374 #endif 375 376 /* Create the audio buffer semaphore */ 377 this->hidden->audio_sem = CreateSemaphore(NULL, iscapture ? 0 : NUM_BUFFERS - 1, NUM_BUFFERS, NULL); 378 if (this->hidden->audio_sem == NULL) { 379 return SDL_SetError("Couldn't create semaphore"); 380 } 381 382 /* Create the sound buffers */ 383 this->hidden->mixbuf = 384 (Uint8 *) SDL_malloc(NUM_BUFFERS * this->spec.size); 385 if (this->hidden->mixbuf == NULL) { 386 return SDL_OutOfMemory(); 387 } 388 389 SDL_zeroa(this->hidden->wavebuf); 390 for (i = 0; i < NUM_BUFFERS; ++i) { 391 this->hidden->wavebuf[i].dwBufferLength = this->spec.size; 392 this->hidden->wavebuf[i].dwFlags = WHDR_DONE; 393 this->hidden->wavebuf[i].lpData = 394 (LPSTR) & this->hidden->mixbuf[i * this->spec.size]; 395 396 if (iscapture) { 397 result = waveInPrepareHeader(this->hidden->hin, 398 &this->hidden->wavebuf[i], 399 sizeof(this->hidden->wavebuf[i])); 400 if (result != MMSYSERR_NOERROR) { 401 return SetMMerror("waveInPrepareHeader()", result); 402 } 403 404 result = waveInAddBuffer(this->hidden->hin, 405 &this->hidden->wavebuf[i], 406 sizeof(this->hidden->wavebuf[i])); 407 if (result != MMSYSERR_NOERROR) { 408 return SetMMerror("waveInAddBuffer()", result); 409 } 410 } else { 411 result = waveOutPrepareHeader(this->hidden->hout, 412 &this->hidden->wavebuf[i], 413 sizeof(this->hidden->wavebuf[i])); 414 if (result != MMSYSERR_NOERROR) { 415 return SetMMerror("waveOutPrepareHeader()", result); 416 } 417 } 418 } 419 420 if (iscapture) { 421 result = waveInStart(this->hidden->hin); 422 if (result != MMSYSERR_NOERROR) { 423 return SetMMerror("waveInStart()", result); 424 } 425 } 426 427 return 0; /* Ready to go! */ 428 } 429 430 431 static int 432 WINMM_Init(SDL_AudioDriverImpl * impl) 433 { 434 /* Set the function pointers */ 435 impl->DetectDevices = WINMM_DetectDevices; 436 impl->OpenDevice = WINMM_OpenDevice; 437 impl->PlayDevice = WINMM_PlayDevice; 438 impl->WaitDevice = WINMM_WaitDevice; 439 impl->GetDeviceBuf = WINMM_GetDeviceBuf; 440 impl->CaptureFromDevice = WINMM_CaptureFromDevice; 441 impl->FlushCapture = WINMM_FlushCapture; 442 impl->CloseDevice = WINMM_CloseDevice; 443 444 impl->HasCaptureSupport = SDL_TRUE; 445 446 return 1; /* this audio target is available. */ 447 } 448 449 AudioBootStrap WINMM_bootstrap = { 450 "winmm", "Windows Waveform Audio", WINMM_Init, 0 451 }; 452 453 #endif /* SDL_AUDIO_DRIVER_WINMM */ 454 455 /* vi: set ts=4 sw=4 expandtab: */