tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cubeb_winmm.c (34478B)


      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};