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