media/libcubeb/src/cubeb_winmm.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial