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