duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.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};