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