1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/libcubeb/src/cubeb_winmm.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,641 @@ 1.4 +/* 1.5 + * Copyright © 2011 Mozilla Foundation 1.6 + * 1.7 + * This program is made available under an ISC-style license. See the 1.8 + * accompanying file LICENSE for details. 1.9 + */ 1.10 +#undef NDEBUG 1.11 +#define __MSVCRT_VERSION__ 0x0700 1.12 +#define WINVER 0x0501 1.13 +#define WIN32_LEAN_AND_MEAN 1.14 +#include <malloc.h> 1.15 +#include <assert.h> 1.16 +#include <windows.h> 1.17 +#include <mmreg.h> 1.18 +#include <mmsystem.h> 1.19 +#include <process.h> 1.20 +#include <stdlib.h> 1.21 +#include "cubeb/cubeb.h" 1.22 +#include "cubeb-internal.h" 1.23 + 1.24 +/* This is missing from the MinGW headers. Use a safe fallback. */ 1.25 +#if !defined(MEMORY_ALLOCATION_ALIGNMENT) 1.26 +#define MEMORY_ALLOCATION_ALIGNMENT 16 1.27 +#endif 1.28 + 1.29 +#define CUBEB_STREAM_MAX 32 1.30 +#define NBUFS 4 1.31 + 1.32 +const GUID KSDATAFORMAT_SUBTYPE_PCM = 1.33 +{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; 1.34 +const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = 1.35 +{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; 1.36 + 1.37 +struct cubeb_stream_item { 1.38 + SLIST_ENTRY head; 1.39 + cubeb_stream * stream; 1.40 +}; 1.41 + 1.42 +static struct cubeb_ops const winmm_ops; 1.43 + 1.44 +struct cubeb { 1.45 + struct cubeb_ops const * ops; 1.46 + HANDLE event; 1.47 + HANDLE thread; 1.48 + int shutdown; 1.49 + PSLIST_HEADER work; 1.50 + CRITICAL_SECTION lock; 1.51 + unsigned int active_streams; 1.52 + unsigned int minimum_latency; 1.53 +}; 1.54 + 1.55 +struct cubeb_stream { 1.56 + cubeb * context; 1.57 + cubeb_stream_params params; 1.58 + cubeb_data_callback data_callback; 1.59 + cubeb_state_callback state_callback; 1.60 + void * user_ptr; 1.61 + WAVEHDR buffers[NBUFS]; 1.62 + size_t buffer_size; 1.63 + int next_buffer; 1.64 + int free_buffers; 1.65 + int shutdown; 1.66 + int draining; 1.67 + HANDLE event; 1.68 + HWAVEOUT waveout; 1.69 + CRITICAL_SECTION lock; 1.70 + uint64_t written; 1.71 +}; 1.72 + 1.73 +static size_t 1.74 +bytes_per_frame(cubeb_stream_params params) 1.75 +{ 1.76 + size_t bytes; 1.77 + 1.78 + switch (params.format) { 1.79 + case CUBEB_SAMPLE_S16LE: 1.80 + bytes = sizeof(signed short); 1.81 + break; 1.82 + case CUBEB_SAMPLE_FLOAT32LE: 1.83 + bytes = sizeof(float); 1.84 + break; 1.85 + default: 1.86 + assert(0); 1.87 + } 1.88 + 1.89 + return bytes * params.channels; 1.90 +} 1.91 + 1.92 +static WAVEHDR * 1.93 +winmm_get_next_buffer(cubeb_stream * stm) 1.94 +{ 1.95 + WAVEHDR * hdr = NULL; 1.96 + 1.97 + assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); 1.98 + hdr = &stm->buffers[stm->next_buffer]; 1.99 + assert(hdr->dwFlags & WHDR_PREPARED || 1.100 + (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE))); 1.101 + stm->next_buffer = (stm->next_buffer + 1) % NBUFS; 1.102 + stm->free_buffers -= 1; 1.103 + 1.104 + return hdr; 1.105 +} 1.106 + 1.107 +static void 1.108 +winmm_refill_stream(cubeb_stream * stm) 1.109 +{ 1.110 + WAVEHDR * hdr; 1.111 + long got; 1.112 + long wanted; 1.113 + MMRESULT r; 1.114 + 1.115 + EnterCriticalSection(&stm->lock); 1.116 + stm->free_buffers += 1; 1.117 + assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); 1.118 + 1.119 + if (stm->draining) { 1.120 + LeaveCriticalSection(&stm->lock); 1.121 + if (stm->free_buffers == NBUFS) { 1.122 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1.123 + } 1.124 + SetEvent(stm->event); 1.125 + return; 1.126 + } 1.127 + 1.128 + if (stm->shutdown) { 1.129 + LeaveCriticalSection(&stm->lock); 1.130 + SetEvent(stm->event); 1.131 + return; 1.132 + } 1.133 + 1.134 + hdr = winmm_get_next_buffer(stm); 1.135 + 1.136 + wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params); 1.137 + 1.138 + /* It is assumed that the caller is holding this lock. It must be dropped 1.139 + during the callback to avoid deadlocks. */ 1.140 + LeaveCriticalSection(&stm->lock); 1.141 + got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted); 1.142 + EnterCriticalSection(&stm->lock); 1.143 + if (got < 0) { 1.144 + LeaveCriticalSection(&stm->lock); 1.145 + /* XXX handle this case */ 1.146 + assert(0); 1.147 + return; 1.148 + } else if (got < wanted) { 1.149 + stm->draining = 1; 1.150 + } 1.151 + stm->written += got; 1.152 + 1.153 + assert(hdr->dwFlags & WHDR_PREPARED); 1.154 + 1.155 + hdr->dwBufferLength = got * bytes_per_frame(stm->params); 1.156 + assert(hdr->dwBufferLength <= stm->buffer_size); 1.157 + 1.158 + r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr)); 1.159 + if (r != MMSYSERR_NOERROR) { 1.160 + LeaveCriticalSection(&stm->lock); 1.161 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); 1.162 + return; 1.163 + } 1.164 + 1.165 + LeaveCriticalSection(&stm->lock); 1.166 +} 1.167 + 1.168 +static unsigned __stdcall 1.169 +winmm_buffer_thread(void * user_ptr) 1.170 +{ 1.171 + cubeb * ctx = (cubeb *) user_ptr; 1.172 + assert(ctx); 1.173 + 1.174 + for (;;) { 1.175 + DWORD rv; 1.176 + PSLIST_ENTRY item; 1.177 + 1.178 + rv = WaitForSingleObject(ctx->event, INFINITE); 1.179 + assert(rv == WAIT_OBJECT_0); 1.180 + 1.181 + /* Process work items in batches so that a single stream can't 1.182 + starve the others by continuously adding new work to the top of 1.183 + the work item stack. */ 1.184 + item = InterlockedFlushSList(ctx->work); 1.185 + while (item != NULL) { 1.186 + PSLIST_ENTRY tmp = item; 1.187 + winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream); 1.188 + item = item->Next; 1.189 + _aligned_free(tmp); 1.190 + } 1.191 + 1.192 + if (ctx->shutdown) { 1.193 + break; 1.194 + } 1.195 + } 1.196 + 1.197 + return 0; 1.198 +} 1.199 + 1.200 +static void CALLBACK 1.201 +winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2) 1.202 +{ 1.203 + cubeb_stream * stm = (cubeb_stream *) user_ptr; 1.204 + struct cubeb_stream_item * item; 1.205 + 1.206 + if (msg != WOM_DONE) { 1.207 + return; 1.208 + } 1.209 + 1.210 + item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT); 1.211 + assert(item); 1.212 + item->stream = stm; 1.213 + InterlockedPushEntrySList(stm->context->work, &item->head); 1.214 + 1.215 + SetEvent(stm->context->event); 1.216 +} 1.217 + 1.218 +static unsigned int 1.219 +calculate_minimum_latency(void) 1.220 +{ 1.221 + OSVERSIONINFOEX osvi; 1.222 + DWORDLONG mask; 1.223 + 1.224 + /* Running under Terminal Services results in underruns with low latency. */ 1.225 + if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) { 1.226 + return 500; 1.227 + } 1.228 + 1.229 + /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */ 1.230 + memset(&osvi, 0, sizeof(OSVERSIONINFOEX)); 1.231 + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); 1.232 + osvi.dwMajorVersion = 6; 1.233 + osvi.dwMinorVersion = 0; 1.234 + 1.235 + mask = 0; 1.236 + VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL); 1.237 + VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL); 1.238 + 1.239 + if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) { 1.240 + return 200; 1.241 + } 1.242 + 1.243 + return 100; 1.244 +} 1.245 + 1.246 +static void winmm_destroy(cubeb * ctx); 1.247 + 1.248 +/*static*/ int 1.249 +winmm_init(cubeb ** context, char const * context_name) 1.250 +{ 1.251 + cubeb * ctx; 1.252 + 1.253 + assert(context); 1.254 + *context = NULL; 1.255 + 1.256 + ctx = calloc(1, sizeof(*ctx)); 1.257 + assert(ctx); 1.258 + 1.259 + ctx->ops = &winmm_ops; 1.260 + 1.261 + ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT); 1.262 + assert(ctx->work); 1.263 + InitializeSListHead(ctx->work); 1.264 + 1.265 + ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); 1.266 + if (!ctx->event) { 1.267 + winmm_destroy(ctx); 1.268 + return CUBEB_ERROR; 1.269 + } 1.270 + 1.271 + ctx->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); 1.272 + if (!ctx->thread) { 1.273 + winmm_destroy(ctx); 1.274 + return CUBEB_ERROR; 1.275 + } 1.276 + 1.277 + SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL); 1.278 + 1.279 + InitializeCriticalSection(&ctx->lock); 1.280 + ctx->active_streams = 0; 1.281 + 1.282 + ctx->minimum_latency = calculate_minimum_latency(); 1.283 + 1.284 + *context = ctx; 1.285 + 1.286 + return CUBEB_OK; 1.287 +} 1.288 + 1.289 +static char const * 1.290 +winmm_get_backend_id(cubeb * ctx) 1.291 +{ 1.292 + return "winmm"; 1.293 +} 1.294 + 1.295 +static void 1.296 +winmm_destroy(cubeb * ctx) 1.297 +{ 1.298 + DWORD rv; 1.299 + 1.300 + assert(ctx->active_streams == 0); 1.301 + assert(!InterlockedPopEntrySList(ctx->work)); 1.302 + 1.303 + DeleteCriticalSection(&ctx->lock); 1.304 + 1.305 + if (ctx->thread) { 1.306 + ctx->shutdown = 1; 1.307 + SetEvent(ctx->event); 1.308 + rv = WaitForSingleObject(ctx->thread, INFINITE); 1.309 + assert(rv == WAIT_OBJECT_0); 1.310 + CloseHandle(ctx->thread); 1.311 + } 1.312 + 1.313 + if (ctx->event) { 1.314 + CloseHandle(ctx->event); 1.315 + } 1.316 + 1.317 + _aligned_free(ctx->work); 1.318 + 1.319 + free(ctx); 1.320 +} 1.321 + 1.322 +static void winmm_stream_destroy(cubeb_stream * stm); 1.323 + 1.324 +static int 1.325 +winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, 1.326 + cubeb_stream_params stream_params, unsigned int latency, 1.327 + cubeb_data_callback data_callback, 1.328 + cubeb_state_callback state_callback, 1.329 + void * user_ptr) 1.330 +{ 1.331 + MMRESULT r; 1.332 + WAVEFORMATEXTENSIBLE wfx; 1.333 + cubeb_stream * stm; 1.334 + int i; 1.335 + size_t bufsz; 1.336 + 1.337 + assert(context); 1.338 + assert(stream); 1.339 + 1.340 + *stream = NULL; 1.341 + 1.342 + memset(&wfx, 0, sizeof(wfx)); 1.343 + if (stream_params.channels > 2) { 1.344 + wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; 1.345 + wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format); 1.346 + } else { 1.347 + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; 1.348 + if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) { 1.349 + wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; 1.350 + } 1.351 + wfx.Format.cbSize = 0; 1.352 + } 1.353 + wfx.Format.nChannels = stream_params.channels; 1.354 + wfx.Format.nSamplesPerSec = stream_params.rate; 1.355 + 1.356 + /* XXX fix channel mappings */ 1.357 + wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; 1.358 + 1.359 + switch (stream_params.format) { 1.360 + case CUBEB_SAMPLE_S16LE: 1.361 + wfx.Format.wBitsPerSample = 16; 1.362 + wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; 1.363 + break; 1.364 + case CUBEB_SAMPLE_FLOAT32LE: 1.365 + wfx.Format.wBitsPerSample = 32; 1.366 + wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; 1.367 + break; 1.368 + default: 1.369 + return CUBEB_ERROR_INVALID_FORMAT; 1.370 + } 1.371 + 1.372 + wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; 1.373 + wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; 1.374 + wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; 1.375 + 1.376 + EnterCriticalSection(&context->lock); 1.377 + /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when 1.378 + many streams are active at once, a subset of them will not consume (via 1.379 + playback) or release (via waveOutReset) their buffers. */ 1.380 + if (context->active_streams >= CUBEB_STREAM_MAX) { 1.381 + LeaveCriticalSection(&context->lock); 1.382 + return CUBEB_ERROR; 1.383 + } 1.384 + context->active_streams += 1; 1.385 + LeaveCriticalSection(&context->lock); 1.386 + 1.387 + stm = calloc(1, sizeof(*stm)); 1.388 + assert(stm); 1.389 + 1.390 + stm->context = context; 1.391 + 1.392 + stm->params = stream_params; 1.393 + 1.394 + stm->data_callback = data_callback; 1.395 + stm->state_callback = state_callback; 1.396 + stm->user_ptr = user_ptr; 1.397 + stm->written = 0; 1.398 + 1.399 + if (latency < context->minimum_latency) { 1.400 + latency = context->minimum_latency; 1.401 + } 1.402 + 1.403 + bufsz = (size_t) (stm->params.rate / 1000.0 * latency * bytes_per_frame(stm->params) / NBUFS); 1.404 + if (bufsz % bytes_per_frame(stm->params) != 0) { 1.405 + bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params)); 1.406 + } 1.407 + assert(bufsz % bytes_per_frame(stm->params) == 0); 1.408 + 1.409 + stm->buffer_size = bufsz; 1.410 + 1.411 + InitializeCriticalSection(&stm->lock); 1.412 + 1.413 + stm->event = CreateEvent(NULL, FALSE, FALSE, NULL); 1.414 + if (!stm->event) { 1.415 + winmm_stream_destroy(stm); 1.416 + return CUBEB_ERROR; 1.417 + } 1.418 + 1.419 + /* winmm_buffer_callback will be called during waveOutOpen, so all 1.420 + other initialization must be complete before calling it. */ 1.421 + r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format, 1.422 + (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm, 1.423 + CALLBACK_FUNCTION); 1.424 + if (r != MMSYSERR_NOERROR) { 1.425 + winmm_stream_destroy(stm); 1.426 + return CUBEB_ERROR; 1.427 + } 1.428 + 1.429 + r = waveOutPause(stm->waveout); 1.430 + if (r != MMSYSERR_NOERROR) { 1.431 + winmm_stream_destroy(stm); 1.432 + return CUBEB_ERROR; 1.433 + } 1.434 + 1.435 + for (i = 0; i < NBUFS; ++i) { 1.436 + WAVEHDR * hdr = &stm->buffers[i]; 1.437 + 1.438 + hdr->lpData = calloc(1, bufsz); 1.439 + assert(hdr->lpData); 1.440 + hdr->dwBufferLength = bufsz; 1.441 + hdr->dwFlags = 0; 1.442 + 1.443 + r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr)); 1.444 + if (r != MMSYSERR_NOERROR) { 1.445 + winmm_stream_destroy(stm); 1.446 + return CUBEB_ERROR; 1.447 + } 1.448 + 1.449 + winmm_refill_stream(stm); 1.450 + } 1.451 + 1.452 + *stream = stm; 1.453 + 1.454 + return CUBEB_OK; 1.455 +} 1.456 + 1.457 +static void 1.458 +winmm_stream_destroy(cubeb_stream * stm) 1.459 +{ 1.460 + DWORD rv; 1.461 + int i; 1.462 + int enqueued; 1.463 + 1.464 + if (stm->waveout) { 1.465 + EnterCriticalSection(&stm->lock); 1.466 + stm->shutdown = 1; 1.467 + 1.468 + waveOutReset(stm->waveout); 1.469 + 1.470 + enqueued = NBUFS - stm->free_buffers; 1.471 + LeaveCriticalSection(&stm->lock); 1.472 + 1.473 + /* Wait for all blocks to complete. */ 1.474 + while (enqueued > 0) { 1.475 + rv = WaitForSingleObject(stm->event, INFINITE); 1.476 + assert(rv == WAIT_OBJECT_0); 1.477 + 1.478 + EnterCriticalSection(&stm->lock); 1.479 + enqueued = NBUFS - stm->free_buffers; 1.480 + LeaveCriticalSection(&stm->lock); 1.481 + } 1.482 + 1.483 + EnterCriticalSection(&stm->lock); 1.484 + 1.485 + for (i = 0; i < NBUFS; ++i) { 1.486 + if (stm->buffers[i].dwFlags & WHDR_PREPARED) { 1.487 + waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i])); 1.488 + } 1.489 + } 1.490 + 1.491 + waveOutClose(stm->waveout); 1.492 + 1.493 + LeaveCriticalSection(&stm->lock); 1.494 + } 1.495 + 1.496 + if (stm->event) { 1.497 + CloseHandle(stm->event); 1.498 + } 1.499 + 1.500 + DeleteCriticalSection(&stm->lock); 1.501 + 1.502 + for (i = 0; i < NBUFS; ++i) { 1.503 + free(stm->buffers[i].lpData); 1.504 + } 1.505 + 1.506 + EnterCriticalSection(&stm->context->lock); 1.507 + assert(stm->context->active_streams >= 1); 1.508 + stm->context->active_streams -= 1; 1.509 + LeaveCriticalSection(&stm->context->lock); 1.510 + 1.511 + free(stm); 1.512 +} 1.513 + 1.514 +static int 1.515 +winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1.516 +{ 1.517 + assert(ctx && max_channels); 1.518 + 1.519 + /* We don't support more than two channels in this backend. */ 1.520 + *max_channels = 2; 1.521 + 1.522 + return CUBEB_OK; 1.523 +} 1.524 + 1.525 +static int 1.526 +winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) 1.527 +{ 1.528 + // 100ms minimum, if we are not in a bizarre configuration. 1.529 + *latency = ctx->minimum_latency; 1.530 + 1.531 + return CUBEB_OK; 1.532 +} 1.533 + 1.534 +static int 1.535 +winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1.536 +{ 1.537 + WAVEOUTCAPS woc; 1.538 + MMRESULT r; 1.539 + 1.540 + r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS)); 1.541 + if (r != MMSYSERR_NOERROR) { 1.542 + return CUBEB_ERROR; 1.543 + } 1.544 + 1.545 + /* Check if we support 48kHz, but not 44.1kHz. */ 1.546 + if (!(woc.dwFormats & WAVE_FORMAT_4S16) && 1.547 + woc.dwFormats & WAVE_FORMAT_48S16) { 1.548 + *rate = 48000; 1.549 + return CUBEB_OK; 1.550 + } 1.551 + /* Prefer 44.1kHz between 44.1kHz and 48kHz. */ 1.552 + *rate = 44100; 1.553 + 1.554 + return CUBEB_OK; 1.555 +} 1.556 + 1.557 +static int 1.558 +winmm_stream_start(cubeb_stream * stm) 1.559 +{ 1.560 + MMRESULT r; 1.561 + 1.562 + EnterCriticalSection(&stm->lock); 1.563 + r = waveOutRestart(stm->waveout); 1.564 + LeaveCriticalSection(&stm->lock); 1.565 + 1.566 + if (r != MMSYSERR_NOERROR) { 1.567 + return CUBEB_ERROR; 1.568 + } 1.569 + 1.570 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); 1.571 + 1.572 + return CUBEB_OK; 1.573 +} 1.574 + 1.575 +static int 1.576 +winmm_stream_stop(cubeb_stream * stm) 1.577 +{ 1.578 + MMRESULT r; 1.579 + 1.580 + EnterCriticalSection(&stm->lock); 1.581 + r = waveOutPause(stm->waveout); 1.582 + LeaveCriticalSection(&stm->lock); 1.583 + 1.584 + if (r != MMSYSERR_NOERROR) { 1.585 + return CUBEB_ERROR; 1.586 + } 1.587 + 1.588 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 1.589 + 1.590 + return CUBEB_OK; 1.591 +} 1.592 + 1.593 +static int 1.594 +winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) 1.595 +{ 1.596 + MMRESULT r; 1.597 + MMTIME time; 1.598 + 1.599 + EnterCriticalSection(&stm->lock); 1.600 + time.wType = TIME_SAMPLES; 1.601 + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); 1.602 + LeaveCriticalSection(&stm->lock); 1.603 + 1.604 + if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { 1.605 + return CUBEB_ERROR; 1.606 + } 1.607 + 1.608 + *position = time.u.sample; 1.609 + 1.610 + return CUBEB_OK; 1.611 +} 1.612 + 1.613 +static int 1.614 +winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 1.615 +{ 1.616 + MMRESULT r; 1.617 + MMTIME time; 1.618 + uint64_t written; 1.619 + 1.620 + EnterCriticalSection(&stm->lock); 1.621 + time.wType = TIME_SAMPLES; 1.622 + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); 1.623 + written = stm->written; 1.624 + LeaveCriticalSection(&stm->lock); 1.625 + 1.626 + *latency = written - time.u.sample; 1.627 + 1.628 + return CUBEB_OK; 1.629 +} 1.630 + 1.631 +static struct cubeb_ops const winmm_ops = { 1.632 + /*.init =*/ winmm_init, 1.633 + /*.get_backend_id =*/ winmm_get_backend_id, 1.634 + /*.get_max_channel_count=*/ winmm_get_max_channel_count, 1.635 + /*.get_min_latency=*/ winmm_get_min_latency, 1.636 + /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, 1.637 + /*.destroy =*/ winmm_destroy, 1.638 + /*.stream_init =*/ winmm_stream_init, 1.639 + /*.stream_destroy =*/ winmm_stream_destroy, 1.640 + /*.stream_start =*/ winmm_stream_start, 1.641 + /*.stream_stop =*/ winmm_stream_stop, 1.642 + /*.stream_get_position =*/ winmm_stream_get_position, 1.643 + /*.stream_get_latency = */ winmm_stream_get_latency 1.644 +};