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 /*.get_preferred_channel_layout =*/ NULL,
1053 /*.enumerate_devices =*/ winmm_enumerate_devices,
1054 /*.device_collection_destroy =*/ winmm_device_collection_destroy,
1055 /*.destroy =*/ winmm_destroy,
1056 /*.stream_init =*/ winmm_stream_init,
1057 /*.stream_destroy =*/ winmm_stream_destroy,
1058 /*.stream_start =*/ winmm_stream_start,
1059 /*.stream_stop =*/ winmm_stream_stop,
1060 /*.stream_reset_default_device =*/ NULL,
1061 /*.stream_get_position =*/ winmm_stream_get_position,
1062 /*.stream_get_latency = */ winmm_stream_get_latency,
1063 /*.stream_set_volume =*/ winmm_stream_set_volume,
1064 /*.stream_set_panning =*/ NULL,
1065 /*.stream_get_current_device =*/ NULL,
1066 /*.stream_device_destroy =*/ NULL,
1067 /*.stream_register_device_changed_callback=*/ NULL,
1068 /*.register_device_collection_changed =*/ NULL
1069 };
1070