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