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