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