1 /*
2  * Copyright © 2011 Mozilla Foundation
3  *
4  * This program is made available under an ISC-style license.  See the
5  * accompanying file LICENSE for details.
6  */
7 #undef WINVER
8 #define WINVER 0x0501
9 #undef WIN32_LEAN_AND_MEAN
10 
11 #include <malloc.h>
12 #include <windows.h>
13 #include <mmreg.h>
14 #include <mmsystem.h>
15 #include <process.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <math.h>
19 #include "cubeb/cubeb.h"
20 #include "cubeb-internal.h"
21 
22 /* This is missing from the MinGW headers. Use a safe fallback. */
23 #if !defined(MEMORY_ALLOCATION_ALIGNMENT)
24 #define MEMORY_ALLOCATION_ALIGNMENT 16
25 #endif
26 
27 /**This is also missing from the MinGW headers. It  also appears to be undocumented by Microsoft.*/
28 #ifndef WAVE_FORMAT_48M08
29 #define WAVE_FORMAT_48M08      0x00001000       /* 48     kHz, Mono, 8-bit */
30 #endif
31 #ifndef WAVE_FORMAT_48M16
32 #define WAVE_FORMAT_48M16      0x00002000       /* 48     kHz, Mono, 16-bit */
33 #endif
34 #ifndef WAVE_FORMAT_48S08
35 #define WAVE_FORMAT_48S08      0x00004000       /* 48     kHz, Stereo, 8-bit */
36 #endif
37 #ifndef WAVE_FORMAT_48S16
38 #define WAVE_FORMAT_48S16      0x00008000       /* 48     kHz, Stereo, 16-bit */
39 #endif
40 #ifndef WAVE_FORMAT_96M08
41 #define WAVE_FORMAT_96M08      0x00010000       /* 96     kHz, Mono, 8-bit */
42 #endif
43 #ifndef WAVE_FORMAT_96M16
44 #define WAVE_FORMAT_96M16      0x00020000       /* 96     kHz, Mono, 16-bit */
45 #endif
46 #ifndef WAVE_FORMAT_96S08
47 #define WAVE_FORMAT_96S08      0x00040000       /* 96     kHz, Stereo, 8-bit */
48 #endif
49 #ifndef WAVE_FORMAT_96S16
50 #define WAVE_FORMAT_96S16      0x00080000       /* 96     kHz, Stereo, 16-bit */
51 #endif
52 
53 /**Taken from winbase.h, also not in MinGW.*/
54 #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
55 #define STACK_SIZE_PARAM_IS_A_RESERVATION   0x00010000    // Threads only
56 #endif
57 
58 #ifndef DRVM_MAPPER
59 #define DRVM_MAPPER             (0x2000)
60 #endif
61 #ifndef DRVM_MAPPER_PREFERRED_GET
62 #define DRVM_MAPPER_PREFERRED_GET                 (DRVM_MAPPER+21)
63 #endif
64 #ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
65 #define DRVM_MAPPER_CONSOLEVOICECOM_GET           (DRVM_MAPPER+23)
66 #endif
67 
68 #define CUBEB_STREAM_MAX 32
69 #define NBUFS 4
70 
71 const GUID KSDATAFORMAT_SUBTYPE_PCM =
72 { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
73 const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
74 { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
75 
76 struct cubeb_stream_item {
77   SLIST_ENTRY head;
78   cubeb_stream * stream;
79 };
80 
81 static struct cubeb_ops const winmm_ops;
82 
83 struct cubeb {
84   struct cubeb_ops const * ops;
85   HANDLE event;
86   HANDLE thread;
87   int shutdown;
88   PSLIST_HEADER work;
89   CRITICAL_SECTION lock;
90   unsigned int active_streams;
91   unsigned int minimum_latency_ms;
92 };
93 
94 struct cubeb_stream {
95   /* Note: Must match cubeb_stream layout in cubeb.c. */
96   cubeb * context;
97   void * user_ptr;
98   /**/
99   cubeb_stream_params params;
100   cubeb_data_callback data_callback;
101   cubeb_state_callback state_callback;
102   WAVEHDR buffers[NBUFS];
103   size_t buffer_size;
104   int next_buffer;
105   int free_buffers;
106   int shutdown;
107   int draining;
108   HANDLE event;
109   HWAVEOUT waveout;
110   CRITICAL_SECTION lock;
111   uint64_t written;
112   float soft_volume;
113 };
114 
115 static size_t
bytes_per_frame(cubeb_stream_params params)116 bytes_per_frame(cubeb_stream_params params)
117 {
118   size_t bytes;
119 
120   switch (params.format) {
121   case CUBEB_SAMPLE_S16LE:
122     bytes = sizeof(signed short);
123     break;
124   case CUBEB_SAMPLE_FLOAT32LE:
125     bytes = sizeof(float);
126     break;
127   default:
128     XASSERT(0);
129   }
130 
131   return bytes * params.channels;
132 }
133 
134 static WAVEHDR *
winmm_get_next_buffer(cubeb_stream * stm)135 winmm_get_next_buffer(cubeb_stream * stm)
136 {
137   WAVEHDR * hdr = NULL;
138 
139   XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
140   hdr = &stm->buffers[stm->next_buffer];
141   XASSERT(hdr->dwFlags & WHDR_PREPARED ||
142           (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
143   stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
144   stm->free_buffers -= 1;
145 
146   return hdr;
147 }
148 
149 static void
winmm_refill_stream(cubeb_stream * stm)150 winmm_refill_stream(cubeb_stream * stm)
151 {
152   WAVEHDR * hdr;
153   long got;
154   long wanted;
155   MMRESULT r;
156 
157   EnterCriticalSection(&stm->lock);
158   stm->free_buffers += 1;
159   XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
160 
161   if (stm->draining) {
162     LeaveCriticalSection(&stm->lock);
163     if (stm->free_buffers == NBUFS) {
164       stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
165     }
166     SetEvent(stm->event);
167     return;
168   }
169 
170   if (stm->shutdown) {
171     LeaveCriticalSection(&stm->lock);
172     SetEvent(stm->event);
173     return;
174   }
175 
176   hdr = winmm_get_next_buffer(stm);
177 
178   wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
179 
180   /* It is assumed that the caller is holding this lock.  It must be dropped
181      during the callback to avoid deadlocks. */
182   LeaveCriticalSection(&stm->lock);
183   got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
184   EnterCriticalSection(&stm->lock);
185   if (got < 0) {
186     LeaveCriticalSection(&stm->lock);
187     /* XXX handle this case */
188     XASSERT(0);
189     return;
190   } else if (got < wanted) {
191     stm->draining = 1;
192   }
193   stm->written += got;
194 
195   XASSERT(hdr->dwFlags & WHDR_PREPARED);
196 
197   hdr->dwBufferLength = got * bytes_per_frame(stm->params);
198   XASSERT(hdr->dwBufferLength <= stm->buffer_size);
199 
200   if (stm->soft_volume != -1.0) {
201     if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
202       float * b = (float *) hdr->lpData;
203       uint32_t i;
204       for (i = 0; i < got * stm->params.channels; i++) {
205         b[i] *= stm->soft_volume;
206       }
207     } else {
208       short * b = (short *) hdr->lpData;
209       uint32_t i;
210       for (i = 0; i < got * stm->params.channels; i++) {
211         b[i] = (short) (b[i] * stm->soft_volume);
212       }
213     }
214   }
215 
216   r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
217   if (r != MMSYSERR_NOERROR) {
218     LeaveCriticalSection(&stm->lock);
219     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
220     return;
221   }
222 
223   LeaveCriticalSection(&stm->lock);
224 }
225 
226 static unsigned __stdcall
winmm_buffer_thread(void * user_ptr)227 winmm_buffer_thread(void * user_ptr)
228 {
229   cubeb * ctx = (cubeb *) user_ptr;
230   XASSERT(ctx);
231 
232   for (;;) {
233     DWORD r;
234     PSLIST_ENTRY item;
235 
236     r = WaitForSingleObject(ctx->event, INFINITE);
237     XASSERT(r == WAIT_OBJECT_0);
238 
239     /* Process work items in batches so that a single stream can't
240        starve the others by continuously adding new work to the top of
241        the work item stack. */
242     item = InterlockedFlushSList(ctx->work);
243     while (item != NULL) {
244       PSLIST_ENTRY tmp = item;
245       winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream);
246       item = item->Next;
247       _aligned_free(tmp);
248     }
249 
250     if (ctx->shutdown) {
251       break;
252     }
253   }
254 
255   return 0;
256 }
257 
258 static void CALLBACK
winmm_buffer_callback(HWAVEOUT waveout,UINT msg,DWORD_PTR user_ptr,DWORD_PTR p1,DWORD_PTR p2)259 winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
260 {
261   cubeb_stream * stm = (cubeb_stream *) user_ptr;
262   struct cubeb_stream_item * item;
263 
264   if (msg != WOM_DONE) {
265     return;
266   }
267 
268   item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
269   XASSERT(item);
270   item->stream = stm;
271   InterlockedPushEntrySList(stm->context->work, &item->head);
272 
273   SetEvent(stm->context->event);
274 }
275 
276 static unsigned int
calculate_minimum_latency(void)277 calculate_minimum_latency(void)
278 {
279   OSVERSIONINFOEX osvi;
280   DWORDLONG mask;
281 
282   /* Running under Terminal Services results in underruns with low latency. */
283   if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
284     return 500;
285   }
286 
287   /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
288   memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
289   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
290   osvi.dwMajorVersion = 6;
291   osvi.dwMinorVersion = 0;
292 
293   mask = 0;
294   VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
295   VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
296 
297   if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
298     return 200;
299   }
300 
301   return 100;
302 }
303 
304 static void winmm_destroy(cubeb * ctx);
305 
306 /*static*/ int
winmm_init(cubeb ** context,char const * context_name)307 winmm_init(cubeb ** context, char const * context_name)
308 {
309   cubeb * ctx;
310 
311   XASSERT(context);
312   *context = NULL;
313 
314   /* Don't initialize a context if there are no devices available. */
315   if (waveOutGetNumDevs() == 0) {
316     return CUBEB_ERROR;
317   }
318 
319   ctx = calloc(1, sizeof(*ctx));
320   XASSERT(ctx);
321 
322   ctx->ops = &winmm_ops;
323 
324   ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
325   XASSERT(ctx->work);
326   InitializeSListHead(ctx->work);
327 
328   ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
329   if (!ctx->event) {
330     winmm_destroy(ctx);
331     return CUBEB_ERROR;
332   }
333 
334   ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
335   if (!ctx->thread) {
336     winmm_destroy(ctx);
337     return CUBEB_ERROR;
338   }
339 
340   SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
341 
342   InitializeCriticalSection(&ctx->lock);
343   ctx->active_streams = 0;
344 
345   ctx->minimum_latency_ms = calculate_minimum_latency();
346 
347   *context = ctx;
348 
349   return CUBEB_OK;
350 }
351 
352 static char const *
winmm_get_backend_id(cubeb * ctx)353 winmm_get_backend_id(cubeb * ctx)
354 {
355   return "winmm";
356 }
357 
358 static void
winmm_destroy(cubeb * ctx)359 winmm_destroy(cubeb * ctx)
360 {
361   DWORD r;
362 
363   XASSERT(ctx->active_streams == 0);
364   XASSERT(!InterlockedPopEntrySList(ctx->work));
365 
366   DeleteCriticalSection(&ctx->lock);
367 
368   if (ctx->thread) {
369     ctx->shutdown = 1;
370     SetEvent(ctx->event);
371     r = WaitForSingleObject(ctx->thread, INFINITE);
372     XASSERT(r == WAIT_OBJECT_0);
373     CloseHandle(ctx->thread);
374   }
375 
376   if (ctx->event) {
377     CloseHandle(ctx->event);
378   }
379 
380   _aligned_free(ctx->work);
381 
382   free(ctx);
383 }
384 
385 static void winmm_stream_destroy(cubeb_stream * stm);
386 
387 static int
winmm_stream_init(cubeb * context,cubeb_stream ** stream,char const * stream_name,cubeb_devid input_device,cubeb_stream_params * input_stream_params,cubeb_devid output_device,cubeb_stream_params * output_stream_params,unsigned int latency_frames,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)388 winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
389                   cubeb_devid input_device,
390                   cubeb_stream_params * input_stream_params,
391                   cubeb_devid output_device,
392                   cubeb_stream_params * output_stream_params,
393                   unsigned int latency_frames,
394                   cubeb_data_callback data_callback,
395                   cubeb_state_callback state_callback,
396                   void * user_ptr)
397 {
398   MMRESULT r;
399   WAVEFORMATEXTENSIBLE wfx;
400   cubeb_stream * stm;
401   int i;
402   size_t bufsz;
403 
404   XASSERT(context);
405   XASSERT(stream);
406   XASSERT(output_stream_params);
407 
408   if (input_stream_params) {
409     /* Capture support not yet implemented. */
410     return CUBEB_ERROR_NOT_SUPPORTED;
411   }
412 
413   if (input_device || output_device) {
414     /* Device selection not yet implemented. */
415     return CUBEB_ERROR_DEVICE_UNAVAILABLE;
416   }
417 
418   if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
419     /* Loopback is not supported */
420     return CUBEB_ERROR_NOT_SUPPORTED;
421   }
422 
423   *stream = NULL;
424 
425   memset(&wfx, 0, sizeof(wfx));
426   if (output_stream_params->channels > 2) {
427     wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
428     wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
429   } else {
430     wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
431     if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
432       wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
433     }
434     wfx.Format.cbSize = 0;
435   }
436   wfx.Format.nChannels = output_stream_params->channels;
437   wfx.Format.nSamplesPerSec = output_stream_params->rate;
438 
439   /* XXX fix channel mappings */
440   wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
441 
442   switch (output_stream_params->format) {
443   case CUBEB_SAMPLE_S16LE:
444     wfx.Format.wBitsPerSample = 16;
445     wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
446     break;
447   case CUBEB_SAMPLE_FLOAT32LE:
448     wfx.Format.wBitsPerSample = 32;
449     wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
450     break;
451   default:
452     return CUBEB_ERROR_INVALID_FORMAT;
453   }
454 
455   wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
456   wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
457   wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
458 
459   EnterCriticalSection(&context->lock);
460   /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
461      many streams are active at once, a subset of them will not consume (via
462      playback) or release (via waveOutReset) their buffers. */
463   if (context->active_streams >= CUBEB_STREAM_MAX) {
464     LeaveCriticalSection(&context->lock);
465     return CUBEB_ERROR;
466   }
467   context->active_streams += 1;
468   LeaveCriticalSection(&context->lock);
469 
470   stm = calloc(1, sizeof(*stm));
471   XASSERT(stm);
472 
473   stm->context = context;
474 
475   stm->params = *output_stream_params;
476 
477   stm->data_callback = data_callback;
478   stm->state_callback = state_callback;
479   stm->user_ptr = user_ptr;
480   stm->written = 0;
481 
482   uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate;
483 
484   if (latency_ms < context->minimum_latency_ms) {
485     latency_ms = context->minimum_latency_ms;
486   }
487 
488   bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS);
489   if (bufsz % bytes_per_frame(stm->params) != 0) {
490     bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
491   }
492   XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
493 
494   stm->buffer_size = bufsz;
495 
496   InitializeCriticalSection(&stm->lock);
497 
498   stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
499   if (!stm->event) {
500     winmm_stream_destroy(stm);
501     return CUBEB_ERROR;
502   }
503 
504   stm->soft_volume = -1.0;
505 
506   /* winmm_buffer_callback will be called during waveOutOpen, so all
507      other initialization must be complete before calling it. */
508   r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
509                   (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm,
510                   CALLBACK_FUNCTION);
511   if (r != MMSYSERR_NOERROR) {
512     winmm_stream_destroy(stm);
513     return CUBEB_ERROR;
514   }
515 
516   r = waveOutPause(stm->waveout);
517   if (r != MMSYSERR_NOERROR) {
518     winmm_stream_destroy(stm);
519     return CUBEB_ERROR;
520   }
521 
522   for (i = 0; i < NBUFS; ++i) {
523     WAVEHDR * hdr = &stm->buffers[i];
524 
525     hdr->lpData = calloc(1, bufsz);
526     XASSERT(hdr->lpData);
527     hdr->dwBufferLength = bufsz;
528     hdr->dwFlags = 0;
529 
530     r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
531     if (r != MMSYSERR_NOERROR) {
532       winmm_stream_destroy(stm);
533       return CUBEB_ERROR;
534     }
535 
536     winmm_refill_stream(stm);
537   }
538 
539   *stream = stm;
540 
541   return CUBEB_OK;
542 }
543 
544 static void
winmm_stream_destroy(cubeb_stream * stm)545 winmm_stream_destroy(cubeb_stream * stm)
546 {
547   int i;
548 
549   if (stm->waveout) {
550     MMTIME time;
551     MMRESULT r;
552     int device_valid;
553     int enqueued;
554 
555     EnterCriticalSection(&stm->lock);
556     stm->shutdown = 1;
557 
558     waveOutReset(stm->waveout);
559 
560     /* Don't need this value, we just want the result to detect invalid
561        handle/no device errors than waveOutReset doesn't seem to report. */
562     time.wType = TIME_SAMPLES;
563     r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
564     device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER);
565 
566     enqueued = NBUFS - stm->free_buffers;
567     LeaveCriticalSection(&stm->lock);
568 
569     /* Wait for all blocks to complete. */
570     while (device_valid && enqueued > 0) {
571       DWORD rv = WaitForSingleObject(stm->event, INFINITE);
572       XASSERT(rv == WAIT_OBJECT_0);
573 
574       EnterCriticalSection(&stm->lock);
575       enqueued = NBUFS - stm->free_buffers;
576       LeaveCriticalSection(&stm->lock);
577     }
578 
579     EnterCriticalSection(&stm->lock);
580 
581     for (i = 0; i < NBUFS; ++i) {
582       if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
583         waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
584       }
585     }
586 
587     waveOutClose(stm->waveout);
588 
589     LeaveCriticalSection(&stm->lock);
590   }
591 
592   if (stm->event) {
593     CloseHandle(stm->event);
594   }
595 
596   DeleteCriticalSection(&stm->lock);
597 
598   for (i = 0; i < NBUFS; ++i) {
599     free(stm->buffers[i].lpData);
600   }
601 
602   EnterCriticalSection(&stm->context->lock);
603   XASSERT(stm->context->active_streams >= 1);
604   stm->context->active_streams -= 1;
605   LeaveCriticalSection(&stm->context->lock);
606 
607   free(stm);
608 }
609 
610 static int
winmm_get_max_channel_count(cubeb * ctx,uint32_t * max_channels)611 winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
612 {
613   XASSERT(ctx && max_channels);
614 
615   /* We don't support more than two channels in this backend. */
616   *max_channels = 2;
617 
618   return CUBEB_OK;
619 }
620 
621 static int
winmm_get_min_latency(cubeb * ctx,cubeb_stream_params params,uint32_t * latency)622 winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
623 {
624   // 100ms minimum, if we are not in a bizarre configuration.
625   *latency = ctx->minimum_latency_ms * params.rate / 1000;
626 
627   return CUBEB_OK;
628 }
629 
630 static int
winmm_get_preferred_sample_rate(cubeb * ctx,uint32_t * rate)631 winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
632 {
633   WAVEOUTCAPS woc;
634   MMRESULT r;
635 
636   r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
637   if (r != MMSYSERR_NOERROR) {
638     return CUBEB_ERROR;
639   }
640 
641   /* Check if we support 48kHz, but not 44.1kHz. */
642   if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
643       woc.dwFormats & WAVE_FORMAT_48S16) {
644     *rate = 48000;
645     return CUBEB_OK;
646   }
647   /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
648   *rate = 44100;
649 
650   return CUBEB_OK;
651 }
652 
653 static int
winmm_stream_start(cubeb_stream * stm)654 winmm_stream_start(cubeb_stream * stm)
655 {
656   MMRESULT r;
657 
658   EnterCriticalSection(&stm->lock);
659   r = waveOutRestart(stm->waveout);
660   LeaveCriticalSection(&stm->lock);
661 
662   if (r != MMSYSERR_NOERROR) {
663     return CUBEB_ERROR;
664   }
665 
666   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
667 
668   return CUBEB_OK;
669 }
670 
671 static int
winmm_stream_stop(cubeb_stream * stm)672 winmm_stream_stop(cubeb_stream * stm)
673 {
674   MMRESULT r;
675 
676   EnterCriticalSection(&stm->lock);
677   r = waveOutPause(stm->waveout);
678   LeaveCriticalSection(&stm->lock);
679 
680   if (r != MMSYSERR_NOERROR) {
681     return CUBEB_ERROR;
682   }
683 
684   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
685 
686   return CUBEB_OK;
687 }
688 
689 static int
winmm_stream_get_position(cubeb_stream * stm,uint64_t * position)690 winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
691 {
692   MMRESULT r;
693   MMTIME time;
694 
695   EnterCriticalSection(&stm->lock);
696   time.wType = TIME_SAMPLES;
697   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
698   LeaveCriticalSection(&stm->lock);
699 
700   if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
701     return CUBEB_ERROR;
702   }
703 
704   *position = time.u.sample;
705 
706   return CUBEB_OK;
707 }
708 
709 static int
winmm_stream_get_latency(cubeb_stream * stm,uint32_t * latency)710 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
711 {
712   MMRESULT r;
713   MMTIME time;
714   uint64_t written;
715 
716   EnterCriticalSection(&stm->lock);
717   time.wType = TIME_SAMPLES;
718   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
719   written = stm->written;
720   LeaveCriticalSection(&stm->lock);
721 
722   if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
723     return CUBEB_ERROR;
724   }
725 
726   XASSERT(written - time.u.sample <= UINT32_MAX);
727   *latency = (uint32_t) (written - time.u.sample);
728 
729   return CUBEB_OK;
730 }
731 
732 static int
winmm_stream_set_volume(cubeb_stream * stm,float volume)733 winmm_stream_set_volume(cubeb_stream * stm, float volume)
734 {
735   EnterCriticalSection(&stm->lock);
736   stm->soft_volume = volume;
737   LeaveCriticalSection(&stm->lock);
738   return CUBEB_OK;
739 }
740 
741 #define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
742 #define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
743 #define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
744 #define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16)
745 #define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16)
746 static void
winmm_calculate_device_rate(cubeb_device_info * info,DWORD formats)747 winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
748 {
749   if (formats & MM_11025HZ_MASK) {
750     info->min_rate = 11025;
751     info->default_rate = 11025;
752     info->max_rate = 11025;
753   }
754   if (formats & MM_22050HZ_MASK) {
755     if (info->min_rate == 0) info->min_rate = 22050;
756     info->max_rate = 22050;
757     info->default_rate = 22050;
758   }
759   if (formats & MM_44100HZ_MASK) {
760     if (info->min_rate == 0) info->min_rate = 44100;
761     info->max_rate = 44100;
762     info->default_rate = 44100;
763   }
764   if (formats & MM_48000HZ_MASK) {
765     if (info->min_rate == 0) info->min_rate = 48000;
766     info->max_rate = 48000;
767     info->default_rate = 48000;
768   }
769   if (formats & MM_96000HZ_MASK) {
770     if (info->min_rate == 0) {
771       info->min_rate = 96000;
772       info->default_rate = 96000;
773     }
774     info->max_rate = 96000;
775   }
776 }
777 
778 #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
779     WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
780 static int
winmm_query_supported_formats(UINT devid,DWORD formats,cubeb_device_fmt * supfmt,cubeb_device_fmt * deffmt)781 winmm_query_supported_formats(UINT devid, DWORD formats,
782     cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
783 {
784   WAVEFORMATEXTENSIBLE wfx;
785 
786   if (formats & MM_S16_MASK)
787     *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
788   else
789     *deffmt = *supfmt = 0;
790 
791   ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
792   wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
793   wfx.Format.nChannels = 2;
794   wfx.Format.nSamplesPerSec = 44100;
795   wfx.Format.wBitsPerSample = 32;
796   wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
797   wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
798   wfx.Format.cbSize = 22;
799   wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
800   wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
801   wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
802   if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
803     *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
804 
805   return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
806 }
807 
808 static char *
guid_to_cstr(LPGUID guid)809 guid_to_cstr(LPGUID guid)
810 {
811   char * ret = malloc(40);
812   if (!ret) {
813     return NULL;
814   }
815   _snprintf_s(ret, 40, _TRUNCATE,
816       "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
817       guid->Data1, guid->Data2, guid->Data3,
818       guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
819       guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
820   return ret;
821 }
822 
823 static cubeb_device_pref
winmm_query_preferred_out_device(UINT devid)824 winmm_query_preferred_out_device(UINT devid)
825 {
826   DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
827   cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
828 
829   if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
830         (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
831       devid == mmpref)
832     ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
833 
834   if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
835         (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
836       devid == compref)
837     ret |= CUBEB_DEVICE_PREF_VOICE;
838 
839   return ret;
840 }
841 
842 static char *
device_id_idx(UINT devid)843 device_id_idx(UINT devid)
844 {
845   char * ret = malloc(16);
846   if (!ret) {
847     return NULL;
848   }
849   _snprintf_s(ret, 16, _TRUNCATE, "%u", devid);
850   return ret;
851 }
852 
853 static void
winmm_create_device_from_outcaps2(cubeb_device_info * ret,LPWAVEOUTCAPS2A caps,UINT devid)854 winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid)
855 {
856   XASSERT(ret);
857   ret->devid = (cubeb_devid) devid;
858   ret->device_id = device_id_idx(devid);
859   ret->friendly_name = _strdup(caps->szPname);
860   ret->group_id = guid_to_cstr(&caps->ProductGuid);
861   ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
862 
863   ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
864   ret->state = CUBEB_DEVICE_STATE_ENABLED;
865   ret->preferred = winmm_query_preferred_out_device(devid);
866 
867   ret->max_channels = caps->wChannels;
868   winmm_calculate_device_rate(ret, caps->dwFormats);
869   winmm_query_supported_formats(devid, caps->dwFormats,
870       &ret->format, &ret->default_format);
871 
872   /* Hardcoded latency estimates... */
873   ret->latency_lo = 100 * ret->default_rate / 1000;
874   ret->latency_hi = 200 * ret->default_rate / 1000;
875 }
876 
877 static void
winmm_create_device_from_outcaps(cubeb_device_info * ret,LPWAVEOUTCAPSA caps,UINT devid)878 winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid)
879 {
880   XASSERT(ret);
881   ret->devid = (cubeb_devid) devid;
882   ret->device_id = device_id_idx(devid);
883   ret->friendly_name = _strdup(caps->szPname);
884   ret->group_id = NULL;
885   ret->vendor_name = NULL;
886 
887   ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
888   ret->state = CUBEB_DEVICE_STATE_ENABLED;
889   ret->preferred = winmm_query_preferred_out_device(devid);
890 
891   ret->max_channels = caps->wChannels;
892   winmm_calculate_device_rate(ret, caps->dwFormats);
893   winmm_query_supported_formats(devid, caps->dwFormats,
894       &ret->format, &ret->default_format);
895 
896   /* Hardcoded latency estimates... */
897   ret->latency_lo = 100 * ret->default_rate / 1000;
898   ret->latency_hi = 200 * ret->default_rate / 1000;
899 }
900 
901 static cubeb_device_pref
winmm_query_preferred_in_device(UINT devid)902 winmm_query_preferred_in_device(UINT devid)
903 {
904   DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
905   cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
906 
907   if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
908         (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
909       devid == mmpref)
910     ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
911 
912   if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
913         (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
914       devid == compref)
915     ret |= CUBEB_DEVICE_PREF_VOICE;
916 
917   return ret;
918 }
919 
920 static void
winmm_create_device_from_incaps2(cubeb_device_info * ret,LPWAVEINCAPS2A caps,UINT devid)921 winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid)
922 {
923   XASSERT(ret);
924   ret->devid = (cubeb_devid) devid;
925   ret->device_id = device_id_idx(devid);
926   ret->friendly_name = _strdup(caps->szPname);
927   ret->group_id = guid_to_cstr(&caps->ProductGuid);
928   ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
929 
930   ret->type = CUBEB_DEVICE_TYPE_INPUT;
931   ret->state = CUBEB_DEVICE_STATE_ENABLED;
932   ret->preferred = winmm_query_preferred_in_device(devid);
933 
934   ret->max_channels = caps->wChannels;
935   winmm_calculate_device_rate(ret, caps->dwFormats);
936   winmm_query_supported_formats(devid, caps->dwFormats,
937       &ret->format, &ret->default_format);
938 
939   /* Hardcoded latency estimates... */
940   ret->latency_lo = 100 * ret->default_rate / 1000;
941   ret->latency_hi = 200 * ret->default_rate / 1000;
942 }
943 
944 static void
winmm_create_device_from_incaps(cubeb_device_info * ret,LPWAVEINCAPSA caps,UINT devid)945 winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid)
946 {
947   XASSERT(ret);
948   ret->devid = (cubeb_devid) devid;
949   ret->device_id = device_id_idx(devid);
950   ret->friendly_name = _strdup(caps->szPname);
951   ret->group_id = NULL;
952   ret->vendor_name = NULL;
953 
954   ret->type = CUBEB_DEVICE_TYPE_INPUT;
955   ret->state = CUBEB_DEVICE_STATE_ENABLED;
956   ret->preferred = winmm_query_preferred_in_device(devid);
957 
958   ret->max_channels = caps->wChannels;
959   winmm_calculate_device_rate(ret, caps->dwFormats);
960   winmm_query_supported_formats(devid, caps->dwFormats,
961       &ret->format, &ret->default_format);
962 
963   /* Hardcoded latency estimates... */
964   ret->latency_lo = 100 * ret->default_rate / 1000;
965   ret->latency_hi = 200 * ret->default_rate / 1000;
966 }
967 
968 static int
winmm_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection * collection)969 winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
970                         cubeb_device_collection * collection)
971 {
972   UINT i, incount, outcount, total;
973   cubeb_device_info * devices;
974   cubeb_device_info * dev;
975 
976   outcount = waveOutGetNumDevs();
977   incount = waveInGetNumDevs();
978   total = outcount + incount;
979 
980   devices = calloc(total, sizeof(cubeb_device_info));
981   collection->count = 0;
982 
983   if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
984     WAVEOUTCAPSA woc;
985     WAVEOUTCAPS2A woc2;
986 
987     ZeroMemory(&woc, sizeof(woc));
988     ZeroMemory(&woc2, sizeof(woc2));
989 
990     for (i = 0; i < outcount; i++) {
991       dev = &devices[collection->count];
992       if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) {
993         winmm_create_device_from_outcaps2(dev, &woc2, i);
994         collection->count += 1;
995       } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) {
996         winmm_create_device_from_outcaps(dev, &woc, i);
997         collection->count += 1;
998       }
999     }
1000   }
1001 
1002   if (type & CUBEB_DEVICE_TYPE_INPUT) {
1003     WAVEINCAPSA wic;
1004     WAVEINCAPS2A wic2;
1005 
1006     ZeroMemory(&wic, sizeof(wic));
1007     ZeroMemory(&wic2, sizeof(wic2));
1008 
1009     for (i = 0; i < incount; i++) {
1010       dev = &devices[collection->count];
1011       if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) {
1012         winmm_create_device_from_incaps2(dev, &wic2, i);
1013         collection->count += 1;
1014       } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) {
1015         winmm_create_device_from_incaps(dev, &wic, i);
1016         collection->count += 1;
1017       }
1018     }
1019   }
1020 
1021   collection->device = devices;
1022 
1023   return CUBEB_OK;
1024 }
1025 
1026 static int
winmm_device_collection_destroy(cubeb * ctx,cubeb_device_collection * collection)1027 winmm_device_collection_destroy(cubeb * ctx,
1028                                 cubeb_device_collection * collection)
1029 {
1030   uint32_t i;
1031   XASSERT(collection);
1032 
1033   (void) ctx;
1034 
1035   for (i = 0; i < collection->count; i++) {
1036     free((void *) collection->device[i].device_id);
1037     free((void *) collection->device[i].friendly_name);
1038     free((void *) collection->device[i].group_id);
1039     free((void *) collection->device[i].vendor_name);
1040   }
1041 
1042   free(collection->device);
1043   return CUBEB_OK;
1044 }
1045 
1046 static struct cubeb_ops const winmm_ops = {
1047   /*.init =*/ winmm_init,
1048   /*.get_backend_id =*/ winmm_get_backend_id,
1049   /*.get_max_channel_count=*/ winmm_get_max_channel_count,
1050   /*.get_min_latency=*/ winmm_get_min_latency,
1051   /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
1052   /*.enumerate_devices =*/ winmm_enumerate_devices,
1053   /*.device_collection_destroy =*/ winmm_device_collection_destroy,
1054   /*.destroy =*/ winmm_destroy,
1055   /*.stream_init =*/ winmm_stream_init,
1056   /*.stream_destroy =*/ winmm_stream_destroy,
1057   /*.stream_start =*/ winmm_stream_start,
1058   /*.stream_stop =*/ winmm_stream_stop,
1059   /*.stream_reset_default_device =*/ NULL,
1060   /*.stream_get_position =*/ winmm_stream_get_position,
1061   /*.stream_get_latency = */ winmm_stream_get_latency,
1062   /*.stream_set_volume =*/ winmm_stream_set_volume,
1063   /*.stream_set_panning =*/ NULL,
1064   /*.stream_get_current_device =*/ NULL,
1065   /*.stream_device_destroy =*/ NULL,
1066   /*.stream_register_device_changed_callback=*/ NULL,
1067   /*.register_device_collection_changed =*/ NULL
1068 };
1069