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