|
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 NDEBUG |
|
8 #define __MSVCRT_VERSION__ 0x0700 |
|
9 #define WINVER 0x0501 |
|
10 #define WIN32_LEAN_AND_MEAN |
|
11 #include <malloc.h> |
|
12 #include <assert.h> |
|
13 #include <windows.h> |
|
14 #include <mmreg.h> |
|
15 #include <mmsystem.h> |
|
16 #include <process.h> |
|
17 #include <stdlib.h> |
|
18 #include "cubeb/cubeb.h" |
|
19 #include "cubeb-internal.h" |
|
20 |
|
21 /* This is missing from the MinGW headers. Use a safe fallback. */ |
|
22 #if !defined(MEMORY_ALLOCATION_ALIGNMENT) |
|
23 #define MEMORY_ALLOCATION_ALIGNMENT 16 |
|
24 #endif |
|
25 |
|
26 #define CUBEB_STREAM_MAX 32 |
|
27 #define NBUFS 4 |
|
28 |
|
29 const GUID KSDATAFORMAT_SUBTYPE_PCM = |
|
30 { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; |
|
31 const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = |
|
32 { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; |
|
33 |
|
34 struct cubeb_stream_item { |
|
35 SLIST_ENTRY head; |
|
36 cubeb_stream * stream; |
|
37 }; |
|
38 |
|
39 static struct cubeb_ops const winmm_ops; |
|
40 |
|
41 struct cubeb { |
|
42 struct cubeb_ops const * ops; |
|
43 HANDLE event; |
|
44 HANDLE thread; |
|
45 int shutdown; |
|
46 PSLIST_HEADER work; |
|
47 CRITICAL_SECTION lock; |
|
48 unsigned int active_streams; |
|
49 unsigned int minimum_latency; |
|
50 }; |
|
51 |
|
52 struct cubeb_stream { |
|
53 cubeb * context; |
|
54 cubeb_stream_params params; |
|
55 cubeb_data_callback data_callback; |
|
56 cubeb_state_callback state_callback; |
|
57 void * user_ptr; |
|
58 WAVEHDR buffers[NBUFS]; |
|
59 size_t buffer_size; |
|
60 int next_buffer; |
|
61 int free_buffers; |
|
62 int shutdown; |
|
63 int draining; |
|
64 HANDLE event; |
|
65 HWAVEOUT waveout; |
|
66 CRITICAL_SECTION lock; |
|
67 uint64_t written; |
|
68 }; |
|
69 |
|
70 static size_t |
|
71 bytes_per_frame(cubeb_stream_params params) |
|
72 { |
|
73 size_t bytes; |
|
74 |
|
75 switch (params.format) { |
|
76 case CUBEB_SAMPLE_S16LE: |
|
77 bytes = sizeof(signed short); |
|
78 break; |
|
79 case CUBEB_SAMPLE_FLOAT32LE: |
|
80 bytes = sizeof(float); |
|
81 break; |
|
82 default: |
|
83 assert(0); |
|
84 } |
|
85 |
|
86 return bytes * params.channels; |
|
87 } |
|
88 |
|
89 static WAVEHDR * |
|
90 winmm_get_next_buffer(cubeb_stream * stm) |
|
91 { |
|
92 WAVEHDR * hdr = NULL; |
|
93 |
|
94 assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); |
|
95 hdr = &stm->buffers[stm->next_buffer]; |
|
96 assert(hdr->dwFlags & WHDR_PREPARED || |
|
97 (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE))); |
|
98 stm->next_buffer = (stm->next_buffer + 1) % NBUFS; |
|
99 stm->free_buffers -= 1; |
|
100 |
|
101 return hdr; |
|
102 } |
|
103 |
|
104 static void |
|
105 winmm_refill_stream(cubeb_stream * stm) |
|
106 { |
|
107 WAVEHDR * hdr; |
|
108 long got; |
|
109 long wanted; |
|
110 MMRESULT r; |
|
111 |
|
112 EnterCriticalSection(&stm->lock); |
|
113 stm->free_buffers += 1; |
|
114 assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); |
|
115 |
|
116 if (stm->draining) { |
|
117 LeaveCriticalSection(&stm->lock); |
|
118 if (stm->free_buffers == NBUFS) { |
|
119 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); |
|
120 } |
|
121 SetEvent(stm->event); |
|
122 return; |
|
123 } |
|
124 |
|
125 if (stm->shutdown) { |
|
126 LeaveCriticalSection(&stm->lock); |
|
127 SetEvent(stm->event); |
|
128 return; |
|
129 } |
|
130 |
|
131 hdr = winmm_get_next_buffer(stm); |
|
132 |
|
133 wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params); |
|
134 |
|
135 /* It is assumed that the caller is holding this lock. It must be dropped |
|
136 during the callback to avoid deadlocks. */ |
|
137 LeaveCriticalSection(&stm->lock); |
|
138 got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted); |
|
139 EnterCriticalSection(&stm->lock); |
|
140 if (got < 0) { |
|
141 LeaveCriticalSection(&stm->lock); |
|
142 /* XXX handle this case */ |
|
143 assert(0); |
|
144 return; |
|
145 } else if (got < wanted) { |
|
146 stm->draining = 1; |
|
147 } |
|
148 stm->written += got; |
|
149 |
|
150 assert(hdr->dwFlags & WHDR_PREPARED); |
|
151 |
|
152 hdr->dwBufferLength = got * bytes_per_frame(stm->params); |
|
153 assert(hdr->dwBufferLength <= stm->buffer_size); |
|
154 |
|
155 r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr)); |
|
156 if (r != MMSYSERR_NOERROR) { |
|
157 LeaveCriticalSection(&stm->lock); |
|
158 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); |
|
159 return; |
|
160 } |
|
161 |
|
162 LeaveCriticalSection(&stm->lock); |
|
163 } |
|
164 |
|
165 static unsigned __stdcall |
|
166 winmm_buffer_thread(void * user_ptr) |
|
167 { |
|
168 cubeb * ctx = (cubeb *) user_ptr; |
|
169 assert(ctx); |
|
170 |
|
171 for (;;) { |
|
172 DWORD rv; |
|
173 PSLIST_ENTRY item; |
|
174 |
|
175 rv = WaitForSingleObject(ctx->event, INFINITE); |
|
176 assert(rv == WAIT_OBJECT_0); |
|
177 |
|
178 /* Process work items in batches so that a single stream can't |
|
179 starve the others by continuously adding new work to the top of |
|
180 the work item stack. */ |
|
181 item = InterlockedFlushSList(ctx->work); |
|
182 while (item != NULL) { |
|
183 PSLIST_ENTRY tmp = item; |
|
184 winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream); |
|
185 item = item->Next; |
|
186 _aligned_free(tmp); |
|
187 } |
|
188 |
|
189 if (ctx->shutdown) { |
|
190 break; |
|
191 } |
|
192 } |
|
193 |
|
194 return 0; |
|
195 } |
|
196 |
|
197 static void CALLBACK |
|
198 winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2) |
|
199 { |
|
200 cubeb_stream * stm = (cubeb_stream *) user_ptr; |
|
201 struct cubeb_stream_item * item; |
|
202 |
|
203 if (msg != WOM_DONE) { |
|
204 return; |
|
205 } |
|
206 |
|
207 item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT); |
|
208 assert(item); |
|
209 item->stream = stm; |
|
210 InterlockedPushEntrySList(stm->context->work, &item->head); |
|
211 |
|
212 SetEvent(stm->context->event); |
|
213 } |
|
214 |
|
215 static unsigned int |
|
216 calculate_minimum_latency(void) |
|
217 { |
|
218 OSVERSIONINFOEX osvi; |
|
219 DWORDLONG mask; |
|
220 |
|
221 /* Running under Terminal Services results in underruns with low latency. */ |
|
222 if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) { |
|
223 return 500; |
|
224 } |
|
225 |
|
226 /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */ |
|
227 memset(&osvi, 0, sizeof(OSVERSIONINFOEX)); |
|
228 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); |
|
229 osvi.dwMajorVersion = 6; |
|
230 osvi.dwMinorVersion = 0; |
|
231 |
|
232 mask = 0; |
|
233 VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL); |
|
234 VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL); |
|
235 |
|
236 if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) { |
|
237 return 200; |
|
238 } |
|
239 |
|
240 return 100; |
|
241 } |
|
242 |
|
243 static void winmm_destroy(cubeb * ctx); |
|
244 |
|
245 /*static*/ int |
|
246 winmm_init(cubeb ** context, char const * context_name) |
|
247 { |
|
248 cubeb * ctx; |
|
249 |
|
250 assert(context); |
|
251 *context = NULL; |
|
252 |
|
253 ctx = calloc(1, sizeof(*ctx)); |
|
254 assert(ctx); |
|
255 |
|
256 ctx->ops = &winmm_ops; |
|
257 |
|
258 ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT); |
|
259 assert(ctx->work); |
|
260 InitializeSListHead(ctx->work); |
|
261 |
|
262 ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); |
|
263 if (!ctx->event) { |
|
264 winmm_destroy(ctx); |
|
265 return CUBEB_ERROR; |
|
266 } |
|
267 |
|
268 ctx->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); |
|
269 if (!ctx->thread) { |
|
270 winmm_destroy(ctx); |
|
271 return CUBEB_ERROR; |
|
272 } |
|
273 |
|
274 SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL); |
|
275 |
|
276 InitializeCriticalSection(&ctx->lock); |
|
277 ctx->active_streams = 0; |
|
278 |
|
279 ctx->minimum_latency = calculate_minimum_latency(); |
|
280 |
|
281 *context = ctx; |
|
282 |
|
283 return CUBEB_OK; |
|
284 } |
|
285 |
|
286 static char const * |
|
287 winmm_get_backend_id(cubeb * ctx) |
|
288 { |
|
289 return "winmm"; |
|
290 } |
|
291 |
|
292 static void |
|
293 winmm_destroy(cubeb * ctx) |
|
294 { |
|
295 DWORD rv; |
|
296 |
|
297 assert(ctx->active_streams == 0); |
|
298 assert(!InterlockedPopEntrySList(ctx->work)); |
|
299 |
|
300 DeleteCriticalSection(&ctx->lock); |
|
301 |
|
302 if (ctx->thread) { |
|
303 ctx->shutdown = 1; |
|
304 SetEvent(ctx->event); |
|
305 rv = WaitForSingleObject(ctx->thread, INFINITE); |
|
306 assert(rv == WAIT_OBJECT_0); |
|
307 CloseHandle(ctx->thread); |
|
308 } |
|
309 |
|
310 if (ctx->event) { |
|
311 CloseHandle(ctx->event); |
|
312 } |
|
313 |
|
314 _aligned_free(ctx->work); |
|
315 |
|
316 free(ctx); |
|
317 } |
|
318 |
|
319 static void winmm_stream_destroy(cubeb_stream * stm); |
|
320 |
|
321 static int |
|
322 winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, |
|
323 cubeb_stream_params stream_params, unsigned int latency, |
|
324 cubeb_data_callback data_callback, |
|
325 cubeb_state_callback state_callback, |
|
326 void * user_ptr) |
|
327 { |
|
328 MMRESULT r; |
|
329 WAVEFORMATEXTENSIBLE wfx; |
|
330 cubeb_stream * stm; |
|
331 int i; |
|
332 size_t bufsz; |
|
333 |
|
334 assert(context); |
|
335 assert(stream); |
|
336 |
|
337 *stream = NULL; |
|
338 |
|
339 memset(&wfx, 0, sizeof(wfx)); |
|
340 if (stream_params.channels > 2) { |
|
341 wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; |
|
342 wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format); |
|
343 } else { |
|
344 wfx.Format.wFormatTag = WAVE_FORMAT_PCM; |
|
345 if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) { |
|
346 wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; |
|
347 } |
|
348 wfx.Format.cbSize = 0; |
|
349 } |
|
350 wfx.Format.nChannels = stream_params.channels; |
|
351 wfx.Format.nSamplesPerSec = stream_params.rate; |
|
352 |
|
353 /* XXX fix channel mappings */ |
|
354 wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; |
|
355 |
|
356 switch (stream_params.format) { |
|
357 case CUBEB_SAMPLE_S16LE: |
|
358 wfx.Format.wBitsPerSample = 16; |
|
359 wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; |
|
360 break; |
|
361 case CUBEB_SAMPLE_FLOAT32LE: |
|
362 wfx.Format.wBitsPerSample = 32; |
|
363 wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; |
|
364 break; |
|
365 default: |
|
366 return CUBEB_ERROR_INVALID_FORMAT; |
|
367 } |
|
368 |
|
369 wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; |
|
370 wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; |
|
371 wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; |
|
372 |
|
373 EnterCriticalSection(&context->lock); |
|
374 /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when |
|
375 many streams are active at once, a subset of them will not consume (via |
|
376 playback) or release (via waveOutReset) their buffers. */ |
|
377 if (context->active_streams >= CUBEB_STREAM_MAX) { |
|
378 LeaveCriticalSection(&context->lock); |
|
379 return CUBEB_ERROR; |
|
380 } |
|
381 context->active_streams += 1; |
|
382 LeaveCriticalSection(&context->lock); |
|
383 |
|
384 stm = calloc(1, sizeof(*stm)); |
|
385 assert(stm); |
|
386 |
|
387 stm->context = context; |
|
388 |
|
389 stm->params = stream_params; |
|
390 |
|
391 stm->data_callback = data_callback; |
|
392 stm->state_callback = state_callback; |
|
393 stm->user_ptr = user_ptr; |
|
394 stm->written = 0; |
|
395 |
|
396 if (latency < context->minimum_latency) { |
|
397 latency = context->minimum_latency; |
|
398 } |
|
399 |
|
400 bufsz = (size_t) (stm->params.rate / 1000.0 * latency * bytes_per_frame(stm->params) / NBUFS); |
|
401 if (bufsz % bytes_per_frame(stm->params) != 0) { |
|
402 bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params)); |
|
403 } |
|
404 assert(bufsz % bytes_per_frame(stm->params) == 0); |
|
405 |
|
406 stm->buffer_size = bufsz; |
|
407 |
|
408 InitializeCriticalSection(&stm->lock); |
|
409 |
|
410 stm->event = CreateEvent(NULL, FALSE, FALSE, NULL); |
|
411 if (!stm->event) { |
|
412 winmm_stream_destroy(stm); |
|
413 return CUBEB_ERROR; |
|
414 } |
|
415 |
|
416 /* winmm_buffer_callback will be called during waveOutOpen, so all |
|
417 other initialization must be complete before calling it. */ |
|
418 r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format, |
|
419 (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm, |
|
420 CALLBACK_FUNCTION); |
|
421 if (r != MMSYSERR_NOERROR) { |
|
422 winmm_stream_destroy(stm); |
|
423 return CUBEB_ERROR; |
|
424 } |
|
425 |
|
426 r = waveOutPause(stm->waveout); |
|
427 if (r != MMSYSERR_NOERROR) { |
|
428 winmm_stream_destroy(stm); |
|
429 return CUBEB_ERROR; |
|
430 } |
|
431 |
|
432 for (i = 0; i < NBUFS; ++i) { |
|
433 WAVEHDR * hdr = &stm->buffers[i]; |
|
434 |
|
435 hdr->lpData = calloc(1, bufsz); |
|
436 assert(hdr->lpData); |
|
437 hdr->dwBufferLength = bufsz; |
|
438 hdr->dwFlags = 0; |
|
439 |
|
440 r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr)); |
|
441 if (r != MMSYSERR_NOERROR) { |
|
442 winmm_stream_destroy(stm); |
|
443 return CUBEB_ERROR; |
|
444 } |
|
445 |
|
446 winmm_refill_stream(stm); |
|
447 } |
|
448 |
|
449 *stream = stm; |
|
450 |
|
451 return CUBEB_OK; |
|
452 } |
|
453 |
|
454 static void |
|
455 winmm_stream_destroy(cubeb_stream * stm) |
|
456 { |
|
457 DWORD rv; |
|
458 int i; |
|
459 int enqueued; |
|
460 |
|
461 if (stm->waveout) { |
|
462 EnterCriticalSection(&stm->lock); |
|
463 stm->shutdown = 1; |
|
464 |
|
465 waveOutReset(stm->waveout); |
|
466 |
|
467 enqueued = NBUFS - stm->free_buffers; |
|
468 LeaveCriticalSection(&stm->lock); |
|
469 |
|
470 /* Wait for all blocks to complete. */ |
|
471 while (enqueued > 0) { |
|
472 rv = WaitForSingleObject(stm->event, INFINITE); |
|
473 assert(rv == WAIT_OBJECT_0); |
|
474 |
|
475 EnterCriticalSection(&stm->lock); |
|
476 enqueued = NBUFS - stm->free_buffers; |
|
477 LeaveCriticalSection(&stm->lock); |
|
478 } |
|
479 |
|
480 EnterCriticalSection(&stm->lock); |
|
481 |
|
482 for (i = 0; i < NBUFS; ++i) { |
|
483 if (stm->buffers[i].dwFlags & WHDR_PREPARED) { |
|
484 waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i])); |
|
485 } |
|
486 } |
|
487 |
|
488 waveOutClose(stm->waveout); |
|
489 |
|
490 LeaveCriticalSection(&stm->lock); |
|
491 } |
|
492 |
|
493 if (stm->event) { |
|
494 CloseHandle(stm->event); |
|
495 } |
|
496 |
|
497 DeleteCriticalSection(&stm->lock); |
|
498 |
|
499 for (i = 0; i < NBUFS; ++i) { |
|
500 free(stm->buffers[i].lpData); |
|
501 } |
|
502 |
|
503 EnterCriticalSection(&stm->context->lock); |
|
504 assert(stm->context->active_streams >= 1); |
|
505 stm->context->active_streams -= 1; |
|
506 LeaveCriticalSection(&stm->context->lock); |
|
507 |
|
508 free(stm); |
|
509 } |
|
510 |
|
511 static int |
|
512 winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) |
|
513 { |
|
514 assert(ctx && max_channels); |
|
515 |
|
516 /* We don't support more than two channels in this backend. */ |
|
517 *max_channels = 2; |
|
518 |
|
519 return CUBEB_OK; |
|
520 } |
|
521 |
|
522 static int |
|
523 winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) |
|
524 { |
|
525 // 100ms minimum, if we are not in a bizarre configuration. |
|
526 *latency = ctx->minimum_latency; |
|
527 |
|
528 return CUBEB_OK; |
|
529 } |
|
530 |
|
531 static int |
|
532 winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) |
|
533 { |
|
534 WAVEOUTCAPS woc; |
|
535 MMRESULT r; |
|
536 |
|
537 r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS)); |
|
538 if (r != MMSYSERR_NOERROR) { |
|
539 return CUBEB_ERROR; |
|
540 } |
|
541 |
|
542 /* Check if we support 48kHz, but not 44.1kHz. */ |
|
543 if (!(woc.dwFormats & WAVE_FORMAT_4S16) && |
|
544 woc.dwFormats & WAVE_FORMAT_48S16) { |
|
545 *rate = 48000; |
|
546 return CUBEB_OK; |
|
547 } |
|
548 /* Prefer 44.1kHz between 44.1kHz and 48kHz. */ |
|
549 *rate = 44100; |
|
550 |
|
551 return CUBEB_OK; |
|
552 } |
|
553 |
|
554 static int |
|
555 winmm_stream_start(cubeb_stream * stm) |
|
556 { |
|
557 MMRESULT r; |
|
558 |
|
559 EnterCriticalSection(&stm->lock); |
|
560 r = waveOutRestart(stm->waveout); |
|
561 LeaveCriticalSection(&stm->lock); |
|
562 |
|
563 if (r != MMSYSERR_NOERROR) { |
|
564 return CUBEB_ERROR; |
|
565 } |
|
566 |
|
567 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); |
|
568 |
|
569 return CUBEB_OK; |
|
570 } |
|
571 |
|
572 static int |
|
573 winmm_stream_stop(cubeb_stream * stm) |
|
574 { |
|
575 MMRESULT r; |
|
576 |
|
577 EnterCriticalSection(&stm->lock); |
|
578 r = waveOutPause(stm->waveout); |
|
579 LeaveCriticalSection(&stm->lock); |
|
580 |
|
581 if (r != MMSYSERR_NOERROR) { |
|
582 return CUBEB_ERROR; |
|
583 } |
|
584 |
|
585 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); |
|
586 |
|
587 return CUBEB_OK; |
|
588 } |
|
589 |
|
590 static int |
|
591 winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) |
|
592 { |
|
593 MMRESULT r; |
|
594 MMTIME time; |
|
595 |
|
596 EnterCriticalSection(&stm->lock); |
|
597 time.wType = TIME_SAMPLES; |
|
598 r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); |
|
599 LeaveCriticalSection(&stm->lock); |
|
600 |
|
601 if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { |
|
602 return CUBEB_ERROR; |
|
603 } |
|
604 |
|
605 *position = time.u.sample; |
|
606 |
|
607 return CUBEB_OK; |
|
608 } |
|
609 |
|
610 static int |
|
611 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) |
|
612 { |
|
613 MMRESULT r; |
|
614 MMTIME time; |
|
615 uint64_t written; |
|
616 |
|
617 EnterCriticalSection(&stm->lock); |
|
618 time.wType = TIME_SAMPLES; |
|
619 r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); |
|
620 written = stm->written; |
|
621 LeaveCriticalSection(&stm->lock); |
|
622 |
|
623 *latency = written - time.u.sample; |
|
624 |
|
625 return CUBEB_OK; |
|
626 } |
|
627 |
|
628 static struct cubeb_ops const winmm_ops = { |
|
629 /*.init =*/ winmm_init, |
|
630 /*.get_backend_id =*/ winmm_get_backend_id, |
|
631 /*.get_max_channel_count=*/ winmm_get_max_channel_count, |
|
632 /*.get_min_latency=*/ winmm_get_min_latency, |
|
633 /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, |
|
634 /*.destroy =*/ winmm_destroy, |
|
635 /*.stream_init =*/ winmm_stream_init, |
|
636 /*.stream_destroy =*/ winmm_stream_destroy, |
|
637 /*.stream_start =*/ winmm_stream_start, |
|
638 /*.stream_stop =*/ winmm_stream_stop, |
|
639 /*.stream_get_position =*/ winmm_stream_get_position, |
|
640 /*.stream_get_latency = */ winmm_stream_get_latency |
|
641 }; |