1 /* 2 * Copyright © 2013 Mozilla Foundation 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 #define _WIN32_WINNT 0x0600 8 #define NOMINMAX 9 10 #include <initguid.h> 11 #include <windows.h> 12 #include <mmdeviceapi.h> 13 #include <windef.h> 14 #include <audioclient.h> 15 #include <devicetopology.h> 16 #include <process.h> 17 #include <avrt.h> 18 #include <stdint.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <stdint.h> 22 #include <cmath> 23 #include <algorithm> 24 #include <memory> 25 #include <limits> 26 #include <atomic> 27 #include <vector> 28 29 #include "cubeb/cubeb.h" 30 #include "cubeb-internal.h" 31 #include "cubeb_mixer.h" 32 #include "cubeb_resampler.h" 33 #include "cubeb_strings.h" 34 #include "cubeb_utils.h" 35 36 // Windows 10 exposes the IAudioClient3 interface to create low-latency streams. 37 // Copy the interface definition from audioclient.h here to make the code simpler 38 // and so that we can still access IAudioClient3 via COM if cubeb was compiled 39 // against an older SDK. 40 #ifndef __IAudioClient3_INTERFACE_DEFINED__ 41 #define __IAudioClient3_INTERFACE_DEFINED__ 42 MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42") 43 IAudioClient3 : public IAudioClient 44 { 45 public: 46 virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod( 47 /* [annotation][in] */ 48 _In_ const WAVEFORMATEX *pFormat, 49 /* [annotation][out] */ 50 _Out_ UINT32 *pDefaultPeriodInFrames, 51 /* [annotation][out] */ 52 _Out_ UINT32 *pFundamentalPeriodInFrames, 53 /* [annotation][out] */ 54 _Out_ UINT32 *pMinPeriodInFrames, 55 /* [annotation][out] */ 56 _Out_ UINT32 *pMaxPeriodInFrames) = 0; 57 58 virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod( 59 /* [unique][annotation][out] */ 60 _Out_ WAVEFORMATEX **ppFormat, 61 /* [annotation][out] */ 62 _Out_ UINT32 *pCurrentPeriodInFrames) = 0; 63 64 virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream( 65 /* [annotation][in] */ 66 _In_ DWORD StreamFlags, 67 /* [annotation][in] */ 68 _In_ UINT32 PeriodInFrames, 69 /* [annotation][in] */ 70 _In_ const WAVEFORMATEX *pFormat, 71 /* [annotation][in] */ 72 _In_opt_ LPCGUID AudioSessionGuid) = 0; 73 }; 74 #ifdef __CRT_UUID_DECL 75 // Required for MinGW 76 __CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42) 77 #endif 78 #endif 79 // Copied from audioclient.h in the Windows 10 SDK 80 #ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED 81 #define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028) 82 #endif 83 84 #ifndef PKEY_Device_FriendlyName 85 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING 86 #endif 87 #ifndef PKEY_Device_InstanceId 88 DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR 89 #endif 90 91 namespace { 92 93 const int64_t LATENCY_NOT_AVAILABLE_YET = -1; 94 95 struct com_heap_ptr_deleter { 96 void operator()(void * ptr) const noexcept { 97 CoTaskMemFree(ptr); 98 } 99 }; 100 101 template <typename T> 102 using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>; 103 104 template<typename T, size_t N> 105 constexpr size_t 106 ARRAY_LENGTH(T(&)[N]) 107 { 108 return N; 109 } 110 111 template <typename T> 112 class no_addref_release : public T { 113 ULONG STDMETHODCALLTYPE AddRef() = 0; 114 ULONG STDMETHODCALLTYPE Release() = 0; 115 }; 116 117 template <typename T> 118 class com_ptr { 119 public: 120 com_ptr() noexcept = default; 121 122 com_ptr(com_ptr const & other) noexcept = delete; 123 com_ptr & operator=(com_ptr const & other) noexcept = delete; 124 T ** operator&() const noexcept = delete; 125 126 ~com_ptr() noexcept { 127 release(); 128 } 129 130 com_ptr(com_ptr && other) noexcept 131 : ptr(other.ptr) 132 { 133 other.ptr = nullptr; 134 } 135 136 com_ptr & operator=(com_ptr && other) noexcept { 137 if (ptr != other.ptr) { 138 release(); 139 ptr = other.ptr; 140 other.ptr = nullptr; 141 } 142 return *this; 143 } 144 145 explicit operator bool() const noexcept { 146 return nullptr != ptr; 147 } 148 149 no_addref_release<T> * operator->() const noexcept { 150 return static_cast<no_addref_release<T> *>(ptr); 151 } 152 153 T * get() const noexcept { 154 return ptr; 155 } 156 157 T ** receive() noexcept { 158 XASSERT(ptr == nullptr); 159 return &ptr; 160 } 161 162 void ** receive_vpp() noexcept { 163 return reinterpret_cast<void **>(receive()); 164 } 165 166 com_ptr & operator=(std::nullptr_t) noexcept { 167 release(); 168 return *this; 169 } 170 171 void reset(T * p = nullptr) noexcept { 172 release(); 173 ptr = p; 174 } 175 176 private: 177 void release() noexcept { 178 T * temp = ptr; 179 180 if (temp) { 181 ptr = nullptr; 182 temp->Release(); 183 } 184 } 185 186 T * ptr = nullptr; 187 }; 188 189 extern cubeb_ops const wasapi_ops; 190 191 int wasapi_stream_stop(cubeb_stream * stm); 192 int wasapi_stream_start(cubeb_stream * stm); 193 void close_wasapi_stream(cubeb_stream * stm); 194 int setup_wasapi_stream(cubeb_stream * stm); 195 ERole pref_to_role(cubeb_stream_prefs param); 196 static char const * wstr_to_utf8(wchar_t const * str); 197 static std::unique_ptr<wchar_t const []> utf8_to_wstr(char const * str); 198 199 } 200 201 class wasapi_collection_notification_client; 202 class monitor_device_notifications; 203 204 struct cubeb { 205 cubeb_ops const * ops = &wasapi_ops; 206 cubeb_strings * device_ids; 207 /* Device enumerator to get notifications when the 208 device collection change. */ 209 com_ptr<IMMDeviceEnumerator> device_collection_enumerator; 210 com_ptr<wasapi_collection_notification_client> collection_notification_client; 211 /* Collection changed for input (capture) devices. */ 212 cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr; 213 void * input_collection_changed_user_ptr = nullptr; 214 /* Collection changed for output (render) devices. */ 215 cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr; 216 void * output_collection_changed_user_ptr = nullptr; 217 UINT64 performance_counter_frequency; 218 }; 219 220 class wasapi_endpoint_notification_client; 221 222 /* We have three possible callbacks we can use with a stream: 223 * - input only 224 * - output only 225 * - synchronized input and output 226 * 227 * Returns true when we should continue to play, false otherwise. 228 */ 229 typedef bool (*wasapi_refill_callback)(cubeb_stream * stm); 230 231 struct cubeb_stream { 232 /* Note: Must match cubeb_stream layout in cubeb.c. */ 233 cubeb * context = nullptr; 234 void * user_ptr = nullptr; 235 /**/ 236 237 /* Mixer pameters. We need to convert the input stream to this 238 samplerate/channel layout, as WASAPI does not resample nor upmix 239 itself. */ 240 cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; 241 cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; 242 /* Stream parameters. This is what the client requested, 243 * and what will be presented in the callback. */ 244 cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; 245 cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE }; 246 /* A MMDevice role for this stream: either communication or console here. */ 247 ERole role; 248 /* The input and output device, or NULL for default. */ 249 std::unique_ptr<const wchar_t[]> input_device; 250 std::unique_ptr<const wchar_t[]> output_device; 251 /* The latency initially requested for this stream, in frames. */ 252 unsigned latency = 0; 253 cubeb_state_callback state_callback = nullptr; 254 cubeb_data_callback data_callback = nullptr; 255 wasapi_refill_callback refill_callback = nullptr; 256 /* True when a loopback device is requested with no output device. In this 257 case a dummy output device is opened to drive the loopback, but should not 258 be exposed. */ 259 bool has_dummy_output = false; 260 /* Lifetime considerations: 261 - client, render_client, audio_clock and audio_stream_volume are interface 262 pointer to the IAudioClient. 263 - The lifetime for device_enumerator and notification_client, resampler, 264 mix_buffer are the same as the cubeb_stream instance. */ 265 266 /* Main handle on the WASAPI stream. */ 267 com_ptr<IAudioClient> output_client; 268 /* Interface pointer to use the event-driven interface. */ 269 com_ptr<IAudioRenderClient> render_client; 270 /* Interface pointer to use the volume facilities. */ 271 com_ptr<IAudioStreamVolume> audio_stream_volume; 272 /* Interface pointer to use the stream audio clock. */ 273 com_ptr<IAudioClock> audio_clock; 274 /* Frames written to the stream since it was opened. Reset on device 275 change. Uses mix_params.rate. */ 276 UINT64 frames_written = 0; 277 /* Frames written to the (logical) stream since it was first 278 created. Updated on device change. Uses stream_params.rate. */ 279 UINT64 total_frames_written = 0; 280 /* Last valid reported stream position. Used to ensure the position 281 reported by stream_get_position increases monotonically. */ 282 UINT64 prev_position = 0; 283 /* Device enumerator to be able to be notified when the default 284 device change. */ 285 com_ptr<IMMDeviceEnumerator> device_enumerator; 286 /* Device notification client, to be able to be notified when the default 287 audio device changes and route the audio to the new default audio output 288 device */ 289 com_ptr<wasapi_endpoint_notification_client> notification_client; 290 /* Main andle to the WASAPI capture stream. */ 291 com_ptr<IAudioClient> input_client; 292 /* Interface to use the event driven capture interface */ 293 com_ptr<IAudioCaptureClient> capture_client; 294 /* This event is set by the stream_stop and stream_destroy 295 function, so the render loop can exit properly. */ 296 HANDLE shutdown_event = 0; 297 /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. 298 The reconfiguration is handled by the render loop thread. */ 299 HANDLE reconfigure_event = 0; 300 /* This is set by WASAPI when we should refill the stream. */ 301 HANDLE refill_event = 0; 302 /* This is set by WASAPI when we should read from the input stream. In 303 * practice, we read from the input stream in the output callback, so 304 * this is not used, but it is necessary to start getting input data. */ 305 HANDLE input_available_event = 0; 306 /* Each cubeb_stream has its own thread. */ 307 HANDLE thread = 0; 308 /* The lock protects all members that are touched by the render thread or 309 change during a device reset, including: audio_clock, audio_stream_volume, 310 client, frames_written, mix_params, total_frames_written, prev_position. */ 311 owned_critical_section stream_reset_lock; 312 /* Maximum number of frames that can be passed down in a callback. */ 313 uint32_t input_buffer_frame_count = 0; 314 /* Maximum number of frames that can be requested in a callback. */ 315 uint32_t output_buffer_frame_count = 0; 316 /* Resampler instance. Resampling will only happen if necessary. */ 317 std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler = { nullptr, cubeb_resampler_destroy }; 318 /* Mixer interfaces */ 319 std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> output_mixer = { nullptr, cubeb_mixer_destroy }; 320 std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> input_mixer = { nullptr, cubeb_mixer_destroy }; 321 /* A buffer for up/down mixing multi-channel audio output. */ 322 std::vector<BYTE> mix_buffer; 323 /* WASAPI input works in "packets". We re-linearize the audio packets 324 * into this buffer before handing it to the resampler. */ 325 std::unique_ptr<auto_array_wrapper> linear_input_buffer; 326 /* Bytes per sample. This multiplied by the number of channels is the number 327 * of bytes per frame. */ 328 size_t bytes_per_sample = 0; 329 /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */ 330 GUID waveformatextensible_sub_format = GUID_NULL; 331 /* Stream volume. Set via stream_set_volume and used to reset volume on 332 device changes. */ 333 float volume = 1.0; 334 /* True if the stream is draining. */ 335 bool draining = false; 336 /* True when we've destroyed the stream. This pointer is leaked on stream 337 * destruction if we could not join the thread. */ 338 std::atomic<std::atomic<bool>*> emergency_bailout { nullptr }; 339 /* Synchronizes render thread start to ensure safe access to emergency_bailout. */ 340 HANDLE thread_ready_event = 0; 341 /* This needs an active audio input stream to be known, and is updated in the 342 * first audio input callback. */ 343 std::atomic<int64_t> input_latency_hns { LATENCY_NOT_AVAILABLE_YET }; 344 }; 345 346 class monitor_device_notifications { 347 public: 348 monitor_device_notifications(cubeb * context) 349 : cubeb_context(context) 350 { 351 create_thread(); 352 } 353 354 ~monitor_device_notifications() 355 { 356 SetEvent(begin_shutdown); 357 WaitForSingleObject(shutdown_complete, INFINITE); 358 CloseHandle(thread); 359 360 CloseHandle(input_changed); 361 CloseHandle(output_changed); 362 CloseHandle(begin_shutdown); 363 CloseHandle(shutdown_complete); 364 } 365 366 void notify(EDataFlow flow) 367 { 368 XASSERT(cubeb_context); 369 if (flow == eCapture && cubeb_context->input_collection_changed_callback) { 370 bool res = SetEvent(input_changed); 371 if (!res) { 372 LOG("Failed to set input changed event"); 373 } 374 return; 375 } 376 if (flow == eRender && cubeb_context->output_collection_changed_callback) { 377 bool res = SetEvent(output_changed); 378 if (!res) { 379 LOG("Failed to set output changed event"); 380 } 381 } 382 } 383 private: 384 static unsigned int __stdcall 385 thread_proc(LPVOID args) 386 { 387 XASSERT(args); 388 auto mdn = static_cast<monitor_device_notifications*>(args); 389 mdn->notification_thread_loop(); 390 SetEvent(mdn->shutdown_complete); 391 return 0; 392 } 393 394 void notification_thread_loop() 395 { 396 struct auto_com { 397 auto_com() { 398 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); 399 XASSERT(SUCCEEDED(hr)); 400 } 401 ~auto_com() { 402 CoUninitialize(); 403 } 404 } com; 405 406 HANDLE wait_array[3] = { 407 input_changed, 408 output_changed, 409 begin_shutdown, 410 }; 411 412 while (true) { 413 Sleep(200); 414 415 DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), 416 wait_array, 417 FALSE, 418 INFINITE); 419 if (wait_result == WAIT_OBJECT_0) { // input changed 420 cubeb_context->input_collection_changed_callback(cubeb_context, 421 cubeb_context->input_collection_changed_user_ptr); 422 } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed 423 cubeb_context->output_collection_changed_callback(cubeb_context, 424 cubeb_context->output_collection_changed_user_ptr); 425 } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown 426 break; 427 } else { 428 LOG("Unexpected result %lu", wait_result); 429 } 430 } // loop 431 } 432 433 void create_thread() 434 { 435 output_changed = CreateEvent(nullptr, 0, 0, nullptr); 436 if (!output_changed) { 437 LOG("Failed to create output changed event."); 438 return; 439 } 440 441 input_changed = CreateEvent(nullptr, 0, 0, nullptr); 442 if (!input_changed) { 443 LOG("Failed to create input changed event."); 444 return; 445 } 446 447 begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr); 448 if (!begin_shutdown) { 449 LOG("Failed to create begin_shutdown event."); 450 return; 451 } 452 453 shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr); 454 if (!shutdown_complete) { 455 LOG("Failed to create shutdown_complete event."); 456 return; 457 } 458 459 thread = (HANDLE) _beginthreadex(nullptr, 460 256 * 1024, 461 thread_proc, 462 this, 463 STACK_SIZE_PARAM_IS_A_RESERVATION, 464 nullptr); 465 if (!thread) { 466 LOG("Failed to create thread."); 467 return; 468 } 469 } 470 471 HANDLE thread = INVALID_HANDLE_VALUE; 472 HANDLE output_changed = INVALID_HANDLE_VALUE; 473 HANDLE input_changed = INVALID_HANDLE_VALUE; 474 HANDLE begin_shutdown = INVALID_HANDLE_VALUE; 475 HANDLE shutdown_complete = INVALID_HANDLE_VALUE; 476 477 cubeb * cubeb_context = nullptr; 478 }; 479 480 class wasapi_collection_notification_client : public IMMNotificationClient 481 { 482 public: 483 /* The implementation of MSCOM was copied from MSDN. */ 484 ULONG STDMETHODCALLTYPE 485 AddRef() 486 { 487 return InterlockedIncrement(&ref_count); 488 } 489 490 ULONG STDMETHODCALLTYPE 491 Release() 492 { 493 ULONG ulRef = InterlockedDecrement(&ref_count); 494 if (0 == ulRef) { 495 delete this; 496 } 497 return ulRef; 498 } 499 500 HRESULT STDMETHODCALLTYPE 501 QueryInterface(REFIID riid, VOID **ppvInterface) 502 { 503 if (__uuidof(IUnknown) == riid) { 504 AddRef(); 505 *ppvInterface = (IUnknown*)this; 506 } else if (__uuidof(IMMNotificationClient) == riid) { 507 AddRef(); 508 *ppvInterface = (IMMNotificationClient*)this; 509 } else { 510 *ppvInterface = NULL; 511 return E_NOINTERFACE; 512 } 513 return S_OK; 514 } 515 516 wasapi_collection_notification_client(cubeb * context) 517 : ref_count(1) 518 , cubeb_context(context) 519 , monitor_notifications(context) 520 { 521 XASSERT(cubeb_context); 522 } 523 524 virtual ~wasapi_collection_notification_client() 525 { } 526 527 HRESULT STDMETHODCALLTYPE 528 OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) 529 { 530 LOG("collection: Audio device default changed, id = %S.", device_id); 531 return S_OK; 532 } 533 534 /* The remaining methods are not implemented, they simply log when called (if 535 log is enabled), for debugging. */ 536 HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) 537 { 538 LOG("collection: Audio device added."); 539 return S_OK; 540 }; 541 542 HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) 543 { 544 LOG("collection: Audio device removed."); 545 return S_OK; 546 } 547 548 HRESULT STDMETHODCALLTYPE 549 OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) 550 { 551 XASSERT(cubeb_context->output_collection_changed_callback || 552 cubeb_context->input_collection_changed_callback); 553 LOG("collection: Audio device state changed, id = %S, state = %lu.", device_id, new_state); 554 EDataFlow flow; 555 HRESULT hr = GetDataFlow(device_id, &flow); 556 if (FAILED(hr)) { 557 return hr; 558 } 559 monitor_notifications.notify(flow); 560 return S_OK; 561 } 562 563 HRESULT STDMETHODCALLTYPE 564 OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) 565 { 566 //Audio device property value changed. 567 return S_OK; 568 } 569 570 private: 571 HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow) 572 { 573 com_ptr<IMMDevice> device; 574 com_ptr<IMMEndpoint> endpoint; 575 576 HRESULT hr = cubeb_context->device_collection_enumerator 577 ->GetDevice(device_id, device.receive()); 578 if (FAILED(hr)) { 579 LOG("collection: Could not get device: %lx", hr); 580 return hr; 581 } 582 583 hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive())); 584 if (FAILED(hr)) { 585 LOG("collection: Could not get endpoint: %lx", hr); 586 return hr; 587 } 588 589 return endpoint->GetDataFlow(flow); 590 } 591 592 /* refcount for this instance, necessary to implement MSCOM semantics. */ 593 LONG ref_count; 594 595 cubeb * cubeb_context = nullptr; 596 monitor_device_notifications monitor_notifications; 597 }; 598 599 class wasapi_endpoint_notification_client : public IMMNotificationClient 600 { 601 public: 602 /* The implementation of MSCOM was copied from MSDN. */ 603 ULONG STDMETHODCALLTYPE 604 AddRef() 605 { 606 return InterlockedIncrement(&ref_count); 607 } 608 609 ULONG STDMETHODCALLTYPE 610 Release() 611 { 612 ULONG ulRef = InterlockedDecrement(&ref_count); 613 if (0 == ulRef) { 614 delete this; 615 } 616 return ulRef; 617 } 618 619 HRESULT STDMETHODCALLTYPE 620 QueryInterface(REFIID riid, VOID **ppvInterface) 621 { 622 if (__uuidof(IUnknown) == riid) { 623 AddRef(); 624 *ppvInterface = (IUnknown*)this; 625 } else if (__uuidof(IMMNotificationClient) == riid) { 626 AddRef(); 627 *ppvInterface = (IMMNotificationClient*)this; 628 } else { 629 *ppvInterface = NULL; 630 return E_NOINTERFACE; 631 } 632 return S_OK; 633 } 634 635 wasapi_endpoint_notification_client(HANDLE event, ERole role) 636 : ref_count(1) 637 , reconfigure_event(event) 638 , role(role) 639 { } 640 641 virtual ~wasapi_endpoint_notification_client() 642 { } 643 644 HRESULT STDMETHODCALLTYPE 645 OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) 646 { 647 LOG("endpoint: Audio device default changed."); 648 649 /* we only support a single stream type for now. */ 650 if (flow != eRender && role != this->role) { 651 return S_OK; 652 } 653 654 BOOL ok = SetEvent(reconfigure_event); 655 if (!ok) { 656 LOG("endpoint: SetEvent on reconfigure_event failed: %lx", GetLastError()); 657 } 658 659 return S_OK; 660 } 661 662 /* The remaining methods are not implemented, they simply log when called (if 663 log is enabled), for debugging. */ 664 HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) 665 { 666 LOG("endpoint: Audio device added."); 667 return S_OK; 668 }; 669 670 HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) 671 { 672 LOG("endpoint: Audio device removed."); 673 return S_OK; 674 } 675 676 HRESULT STDMETHODCALLTYPE 677 OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) 678 { 679 LOG("endpoint: Audio device state changed."); 680 return S_OK; 681 } 682 683 HRESULT STDMETHODCALLTYPE 684 OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) 685 { 686 //Audio device property value changed. 687 return S_OK; 688 } 689 private: 690 /* refcount for this instance, necessary to implement MSCOM semantics. */ 691 LONG ref_count; 692 HANDLE reconfigure_event; 693 ERole role; 694 }; 695 696 namespace { 697 698 char const * 699 intern_device_id(cubeb * ctx, wchar_t const * id) 700 { 701 XASSERT(id); 702 703 char const * tmp = wstr_to_utf8(id); 704 if (!tmp) 705 return nullptr; 706 707 char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); 708 709 free((void *) tmp); 710 711 return interned; 712 } 713 714 bool has_input(cubeb_stream * stm) 715 { 716 return stm->input_stream_params.rate != 0; 717 } 718 719 bool has_output(cubeb_stream * stm) 720 { 721 return stm->output_stream_params.rate != 0; 722 } 723 724 double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer) 725 { 726 return double(stream.rate) / mixer.rate; 727 } 728 729 /* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG. 730 See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */ 731 732 cubeb_channel_layout 733 mask_to_channel_layout(WAVEFORMATEX const * fmt) 734 { 735 cubeb_channel_layout mask = 0; 736 737 if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 738 WAVEFORMATEXTENSIBLE const * ext = reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(fmt); 739 mask = ext->dwChannelMask; 740 } else if (fmt->wFormatTag == WAVE_FORMAT_PCM || 741 fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { 742 if (fmt->nChannels == 1) { 743 mask = CHANNEL_FRONT_CENTER; 744 } else if (fmt->nChannels == 2) { 745 mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT; 746 } 747 } 748 return mask; 749 } 750 751 uint32_t 752 get_rate(cubeb_stream * stm) 753 { 754 return has_input(stm) ? stm->input_stream_params.rate 755 : stm->output_stream_params.rate; 756 } 757 758 uint32_t 759 hns_to_frames(uint32_t rate, REFERENCE_TIME hns) 760 { 761 return std::ceil((hns - 1) / 10000000.0 * rate); 762 } 763 764 uint32_t 765 hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns) 766 { 767 return hns_to_frames(get_rate(stm), hns); 768 } 769 770 REFERENCE_TIME 771 frames_to_hns(cubeb_stream * stm, uint32_t frames) 772 { 773 return std::ceil(frames * 10000000.0 / get_rate(stm)); 774 } 775 776 /* This returns the size of a frame in the stream, before the eventual upmix 777 occurs. */ 778 static size_t 779 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) 780 { 781 // This is called only when we has a output client. 782 XASSERT(has_output(stm)); 783 return stm->output_stream_params.channels * stm->bytes_per_sample * frames; 784 } 785 786 /* This function handles the processing of the input and output audio, 787 * converting it to rate and channel layout specified at initialization. 788 * It then calls the data callback, via the resampler. */ 789 long 790 refill(cubeb_stream * stm, void * input_buffer, long input_frames_count, 791 void * output_buffer, long output_frames_needed) 792 { 793 XASSERT(!stm->draining); 794 /* If we need to upmix after resampling, resample into the mix buffer to 795 avoid a copy. Avoid exposing output if it is a dummy stream. */ 796 void * dest = nullptr; 797 if (has_output(stm) && !stm->has_dummy_output) { 798 if (stm->output_mixer) { 799 dest = stm->mix_buffer.data(); 800 } else { 801 dest = output_buffer; 802 } 803 } 804 805 long out_frames = cubeb_resampler_fill(stm->resampler.get(), 806 input_buffer, 807 &input_frames_count, 808 dest, 809 output_frames_needed); 810 /* TODO: Report out_frames < 0 as an error via the API. */ 811 XASSERT(out_frames >= 0); 812 813 { 814 auto_lock lock(stm->stream_reset_lock); 815 stm->frames_written += out_frames; 816 } 817 818 /* Go in draining mode if we got fewer frames than requested. If the stream 819 has no output we still expect the callback to return number of frames read 820 from input, otherwise we stop. */ 821 if ((out_frames < output_frames_needed) || 822 (!has_output(stm) && out_frames < input_frames_count)) { 823 LOG("start draining."); 824 stm->draining = true; 825 } 826 827 /* If this is not true, there will be glitches. 828 It is alright to have produced less frames if we are draining, though. */ 829 XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm) || stm->has_dummy_output); 830 831 // We don't bother mixing dummy output as it will be silenced, otherwise mix output if needed 832 if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) { 833 XASSERT(dest == stm->mix_buffer.data()); 834 size_t dest_size = 835 out_frames * stm->output_stream_params.channels * stm->bytes_per_sample; 836 XASSERT(dest_size <= stm->mix_buffer.size()); 837 size_t output_buffer_size = 838 out_frames * stm->output_mix_params.channels * stm->bytes_per_sample; 839 int ret = cubeb_mixer_mix(stm->output_mixer.get(), 840 out_frames, 841 dest, 842 dest_size, 843 output_buffer, 844 output_buffer_size); 845 if (ret < 0) { 846 LOG("Error remixing content (%d)", ret); 847 } 848 } 849 850 return out_frames; 851 } 852 853 int wasapi_stream_reset_default_device(cubeb_stream * stm); 854 855 /* This helper grabs all the frames available from a capture client, put them in 856 * linear_input_buffer. linear_input_buffer should be cleared before the 857 * callback exits. This helper does not work with exclusive mode streams. */ 858 bool get_input_buffer(cubeb_stream * stm) 859 { 860 XASSERT(has_input(stm)); 861 862 HRESULT hr; 863 BYTE * input_packet = NULL; 864 DWORD flags; 865 UINT64 dev_pos; 866 UINT64 pc_position; 867 UINT32 next; 868 /* Get input packets until we have captured enough frames, and put them in a 869 * contiguous buffer. */ 870 uint32_t offset = 0; 871 // If the input stream is event driven we should only ever expect to read a 872 // single packet each time. However, if we're pulling from the stream we may 873 // need to grab multiple packets worth of frames that have accumulated (so 874 // need a loop). 875 for (hr = stm->capture_client->GetNextPacketSize(&next); 876 next > 0; 877 hr = stm->capture_client->GetNextPacketSize(&next)) { 878 if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { 879 // Application can recover from this error. More info 880 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx 881 LOG("Device invalidated error, reset default device"); 882 wasapi_stream_reset_default_device(stm); 883 return true; 884 } 885 886 if (FAILED(hr)) { 887 LOG("cannot get next packet size: %lx", hr); 888 return false; 889 } 890 891 UINT32 frames; 892 hr = stm->capture_client->GetBuffer(&input_packet, 893 &frames, 894 &flags, 895 &dev_pos, 896 &pc_position); 897 898 if (FAILED(hr)) { 899 LOG("GetBuffer failed for capture: %lx", hr); 900 return false; 901 } 902 XASSERT(frames == next); 903 904 if (stm->context->performance_counter_frequency) { 905 LARGE_INTEGER now; 906 UINT64 now_hns; 907 // See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer, section "Remarks". 908 QueryPerformanceCounter(&now); 909 now_hns = 10000000 * now.QuadPart / stm->context->performance_counter_frequency; 910 if (now_hns >= pc_position) { 911 stm->input_latency_hns = now_hns - pc_position; 912 } 913 } 914 915 UINT32 input_stream_samples = frames * stm->input_stream_params.channels; 916 // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY 917 // flag. There a two primary (non exhaustive) scenarios we anticipate this 918 // flag being set in: 919 // - The first GetBuffer after Start has this flag undefined. In this 920 // case the flag may be set but is meaningless and can be ignored. 921 // - If a glitch is introduced into the input. This should not happen 922 // for event based inputs, and should be mitigated by using a dummy 923 // stream to drive input in the case of input only loopback. Without 924 // a dummy output, input only loopback would glitch on silence. However, 925 // the dummy input should push silence to the loopback and prevent 926 // discontinuities. See https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/ 927 // As the first scenario can be ignored, and we anticipate the second 928 // scenario is mitigated, we ignore the flag. 929 // For more info: https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx, 930 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx 931 if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { 932 LOG("insert silence: ps=%u", frames); 933 stm->linear_input_buffer->push_silence(input_stream_samples); 934 } else { 935 if (stm->input_mixer) { 936 bool ok = stm->linear_input_buffer->reserve( 937 stm->linear_input_buffer->length() + input_stream_samples); 938 XASSERT(ok); 939 size_t input_packet_size = 940 frames * stm->input_mix_params.channels * 941 cubeb_sample_size(stm->input_mix_params.format); 942 size_t linear_input_buffer_size = 943 input_stream_samples * 944 cubeb_sample_size(stm->input_stream_params.format); 945 cubeb_mixer_mix(stm->input_mixer.get(), 946 frames, 947 input_packet, 948 input_packet_size, 949 stm->linear_input_buffer->end(), 950 linear_input_buffer_size); 951 stm->linear_input_buffer->set_length( 952 stm->linear_input_buffer->length() + input_stream_samples); 953 } else { 954 stm->linear_input_buffer->push( 955 input_packet, input_stream_samples); 956 } 957 } 958 hr = stm->capture_client->ReleaseBuffer(frames); 959 if (FAILED(hr)) { 960 LOG("FAILED to release intput buffer"); 961 return false; 962 } 963 offset += input_stream_samples; 964 } 965 966 XASSERT(stm->linear_input_buffer->length() >= offset); 967 968 return true; 969 } 970 971 /* Get an output buffer from the render_client. It has to be released before 972 * exiting the callback. */ 973 bool get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count) 974 { 975 UINT32 padding_out; 976 HRESULT hr; 977 978 XASSERT(has_output(stm)); 979 980 hr = stm->output_client->GetCurrentPadding(&padding_out); 981 if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { 982 // Application can recover from this error. More info 983 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx 984 LOG("Device invalidated error, reset default device"); 985 wasapi_stream_reset_default_device(stm); 986 return true; 987 } 988 989 if (FAILED(hr)) { 990 LOG("Failed to get padding: %lx", hr); 991 return false; 992 } 993 994 XASSERT(padding_out <= stm->output_buffer_frame_count); 995 996 if (stm->draining) { 997 if (padding_out == 0) { 998 LOG("Draining finished."); 999 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1000 return false; 1001 } 1002 LOG("Draining."); 1003 return true; 1004 } 1005 1006 frame_count = stm->output_buffer_frame_count - padding_out; 1007 BYTE * output_buffer; 1008 1009 hr = stm->render_client->GetBuffer(frame_count, &output_buffer); 1010 if (FAILED(hr)) { 1011 LOG("cannot get render buffer"); 1012 return false; 1013 } 1014 1015 buffer = output_buffer; 1016 1017 return true; 1018 } 1019 1020 /** 1021 * This function gets input data from a input device, and pass it along with an 1022 * output buffer to the resamplers. */ 1023 bool 1024 refill_callback_duplex(cubeb_stream * stm) 1025 { 1026 HRESULT hr; 1027 void * output_buffer = nullptr; 1028 size_t output_frames = 0; 1029 size_t input_frames; 1030 bool rv; 1031 1032 XASSERT(has_input(stm) && has_output(stm)); 1033 1034 rv = get_input_buffer(stm); 1035 if (!rv) { 1036 return rv; 1037 } 1038 1039 input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; 1040 if (!input_frames) { 1041 return true; 1042 } 1043 1044 rv = get_output_buffer(stm, output_buffer, output_frames); 1045 if (!rv) { 1046 hr = stm->render_client->ReleaseBuffer(output_frames, 0); 1047 return rv; 1048 } 1049 1050 /* This can only happen when debugging, and having breakpoints set in the 1051 * callback in a way that it makes the stream underrun. */ 1052 if (output_frames == 0) { 1053 return true; 1054 } 1055 1056 /* Wait for draining is not important on duplex. */ 1057 if (stm->draining) { 1058 return false; 1059 } 1060 1061 if (stm->has_dummy_output) { 1062 ALOGV("Duplex callback (dummy output): input frames: %Iu, output frames: %Iu", 1063 input_frames, output_frames); 1064 1065 // We don't want to expose the dummy output to the callback so don't pass 1066 // the output buffer (it will be released later with silence in it) 1067 refill(stm, 1068 stm->linear_input_buffer->data(), 1069 input_frames, 1070 nullptr, 1071 0); 1072 } else { 1073 ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu", 1074 input_frames, output_frames); 1075 1076 refill(stm, 1077 stm->linear_input_buffer->data(), 1078 input_frames, 1079 output_buffer, 1080 output_frames); 1081 } 1082 1083 stm->linear_input_buffer->clear(); 1084 1085 if (stm->has_dummy_output) { 1086 // If output is a dummy output, make sure it's silent 1087 hr = stm->render_client->ReleaseBuffer(output_frames, AUDCLNT_BUFFERFLAGS_SILENT); 1088 } else { 1089 hr = stm->render_client->ReleaseBuffer(output_frames, 0); 1090 } 1091 if (FAILED(hr)) { 1092 LOG("failed to release buffer: %lx", hr); 1093 return false; 1094 } 1095 return true; 1096 } 1097 1098 bool 1099 refill_callback_input(cubeb_stream * stm) 1100 { 1101 bool rv; 1102 size_t input_frames; 1103 1104 XASSERT(has_input(stm) && !has_output(stm)); 1105 1106 rv = get_input_buffer(stm); 1107 if (!rv) { 1108 return rv; 1109 } 1110 1111 input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels; 1112 if (!input_frames) { 1113 return true; 1114 } 1115 1116 ALOGV("Input callback: input frames: %Iu", input_frames); 1117 1118 long read = refill(stm, 1119 stm->linear_input_buffer->data(), 1120 input_frames, 1121 nullptr, 1122 0); 1123 1124 XASSERT(read >= 0); 1125 1126 stm->linear_input_buffer->clear(); 1127 1128 return !stm->draining; 1129 } 1130 1131 bool 1132 refill_callback_output(cubeb_stream * stm) 1133 { 1134 bool rv; 1135 HRESULT hr; 1136 void * output_buffer = nullptr; 1137 size_t output_frames = 0; 1138 1139 XASSERT(!has_input(stm) && has_output(stm)); 1140 1141 rv = get_output_buffer(stm, output_buffer, output_frames); 1142 if (!rv) { 1143 return rv; 1144 } 1145 1146 if (stm->draining || output_frames == 0) { 1147 return true; 1148 } 1149 1150 long got = refill(stm, 1151 nullptr, 1152 0, 1153 output_buffer, 1154 output_frames); 1155 1156 ALOGV("Output callback: output frames requested: %Iu, got %ld", 1157 output_frames, got); 1158 1159 XASSERT(got >= 0); 1160 XASSERT(size_t(got) == output_frames || stm->draining); 1161 1162 hr = stm->render_client->ReleaseBuffer(got, 0); 1163 if (FAILED(hr)) { 1164 LOG("failed to release buffer: %lx", hr); 1165 return false; 1166 } 1167 1168 return size_t(got) == output_frames || stm->draining; 1169 } 1170 1171 static unsigned int __stdcall 1172 wasapi_stream_render_loop(LPVOID stream) 1173 { 1174 cubeb_stream * stm = static_cast<cubeb_stream *>(stream); 1175 std::atomic<bool> * emergency_bailout = stm->emergency_bailout; 1176 1177 // Signal wasapi_stream_start that we've copied emergency_bailout. 1178 BOOL ok = SetEvent(stm->thread_ready_event); 1179 if (!ok) { 1180 LOG("thread_ready SetEvent failed: %lx", GetLastError()); 1181 return 0; 1182 } 1183 1184 bool is_playing = true; 1185 HANDLE wait_array[4] = { 1186 stm->shutdown_event, 1187 stm->reconfigure_event, 1188 stm->refill_event, 1189 stm->input_available_event 1190 }; 1191 HANDLE mmcss_handle = NULL; 1192 HRESULT hr = 0; 1193 DWORD mmcss_task_index = 0; 1194 struct auto_com { 1195 auto_com() { 1196 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); 1197 XASSERT(SUCCEEDED(hr)); 1198 } 1199 ~auto_com() { 1200 CoUninitialize(); 1201 } 1202 } com; 1203 1204 /* We could consider using "Pro Audio" here for WebAudio and 1205 maybe WebRTC. */ 1206 mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); 1207 if (!mmcss_handle) { 1208 /* This is not fatal, but we might glitch under heavy load. */ 1209 LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError()); 1210 } 1211 1212 /* WaitForMultipleObjects timeout can trigger in cases where we don't want to 1213 treat it as a timeout, such as across a system sleep/wake cycle. Trigger 1214 the timeout error handling only when the timeout_limit is reached, which is 1215 reset on each successful loop. */ 1216 unsigned timeout_count = 0; 1217 const unsigned timeout_limit = 3; 1218 while (is_playing) { 1219 // We want to check the emergency bailout variable before a 1220 // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects 1221 // is going to wait on might have been closed already. 1222 if (*emergency_bailout) { 1223 delete emergency_bailout; 1224 return 0; 1225 } 1226 DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), 1227 wait_array, 1228 FALSE, 1229 1000); 1230 if (*emergency_bailout) { 1231 delete emergency_bailout; 1232 return 0; 1233 } 1234 if (waitResult != WAIT_TIMEOUT) { 1235 timeout_count = 0; 1236 } 1237 switch (waitResult) { 1238 case WAIT_OBJECT_0: { /* shutdown */ 1239 is_playing = false; 1240 /* We don't check if the drain is actually finished here, we just want to 1241 shutdown. */ 1242 if (stm->draining) { 1243 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1244 } 1245 continue; 1246 } 1247 case WAIT_OBJECT_0 + 1: { /* reconfigure */ 1248 XASSERT(stm->output_client || stm->input_client); 1249 LOG("Reconfiguring the stream"); 1250 /* Close the stream */ 1251 if (stm->output_client) { 1252 stm->output_client->Stop(); 1253 LOG("Output stopped."); 1254 } 1255 if (stm->input_client) { 1256 stm->input_client->Stop(); 1257 LOG("Input stopped."); 1258 } 1259 { 1260 auto_lock lock(stm->stream_reset_lock); 1261 close_wasapi_stream(stm); 1262 LOG("Stream closed."); 1263 /* Reopen a stream and start it immediately. This will automatically pick the 1264 new default device for this role. */ 1265 int r = setup_wasapi_stream(stm); 1266 if (r != CUBEB_OK) { 1267 LOG("Error setting up the stream during reconfigure."); 1268 /* Don't destroy the stream here, since we expect the caller to do 1269 so after the error has propagated via the state callback. */ 1270 is_playing = false; 1271 hr = E_FAIL; 1272 continue; 1273 } 1274 LOG("Stream setup successfuly."); 1275 } 1276 XASSERT(stm->output_client || stm->input_client); 1277 if (stm->output_client) { 1278 hr = stm->output_client->Start(); 1279 if (FAILED(hr)) { 1280 LOG("Error starting output after reconfigure, error: %lx", hr); 1281 is_playing = false; 1282 continue; 1283 } 1284 LOG("Output started after reconfigure."); 1285 } 1286 if (stm->input_client) { 1287 hr = stm->input_client->Start(); 1288 if (FAILED(hr)) { 1289 LOG("Error starting input after reconfiguring, error: %lx", hr); 1290 is_playing = false; 1291 continue; 1292 } 1293 LOG("Input started after reconfigure."); 1294 } 1295 break; 1296 } 1297 case WAIT_OBJECT_0 + 2: /* refill */ 1298 XASSERT((has_input(stm) && has_output(stm)) || 1299 (!has_input(stm) && has_output(stm))); 1300 is_playing = stm->refill_callback(stm); 1301 break; 1302 case WAIT_OBJECT_0 + 3: /* input available */ 1303 if (has_input(stm) && has_output(stm)) { continue; } 1304 is_playing = stm->refill_callback(stm); 1305 break; 1306 case WAIT_TIMEOUT: 1307 XASSERT(stm->shutdown_event == wait_array[0]); 1308 if (++timeout_count >= timeout_limit) { 1309 LOG("Render loop reached the timeout limit."); 1310 is_playing = false; 1311 hr = E_FAIL; 1312 } 1313 break; 1314 default: 1315 LOG("case %lu not handled in render loop.", waitResult); 1316 abort(); 1317 } 1318 } 1319 1320 if (FAILED(hr)) { 1321 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); 1322 } 1323 1324 if (mmcss_handle) { 1325 AvRevertMmThreadCharacteristics(mmcss_handle); 1326 } 1327 1328 return 0; 1329 } 1330 1331 void wasapi_destroy(cubeb * context); 1332 1333 HRESULT register_notification_client(cubeb_stream * stm) 1334 { 1335 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 1336 NULL, CLSCTX_INPROC_SERVER, 1337 IID_PPV_ARGS(stm->device_enumerator.receive())); 1338 if (FAILED(hr)) { 1339 LOG("Could not get device enumerator: %lx", hr); 1340 return hr; 1341 } 1342 1343 stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event, stm->role)); 1344 1345 hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get()); 1346 if (FAILED(hr)) { 1347 LOG("Could not register endpoint notification callback: %lx", hr); 1348 stm->notification_client = nullptr; 1349 stm->device_enumerator = nullptr; 1350 } 1351 1352 return hr; 1353 } 1354 1355 HRESULT unregister_notification_client(cubeb_stream * stm) 1356 { 1357 XASSERT(stm); 1358 HRESULT hr; 1359 1360 if (!stm->device_enumerator) { 1361 return S_OK; 1362 } 1363 1364 hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client.get()); 1365 if (FAILED(hr)) { 1366 // We can't really do anything here, we'll probably leak the 1367 // notification client, but we can at least release the enumerator. 1368 stm->device_enumerator = nullptr; 1369 return S_OK; 1370 } 1371 1372 stm->notification_client = nullptr; 1373 stm->device_enumerator = nullptr; 1374 1375 return S_OK; 1376 } 1377 1378 HRESULT get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid) 1379 { 1380 com_ptr<IMMDeviceEnumerator> enumerator; 1381 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 1382 NULL, CLSCTX_INPROC_SERVER, 1383 IID_PPV_ARGS(enumerator.receive())); 1384 if (FAILED(hr)) { 1385 LOG("Could not get device enumerator: %lx", hr); 1386 return hr; 1387 } 1388 1389 hr = enumerator->GetDevice(devid, device.receive()); 1390 if (FAILED(hr)) { 1391 LOG("Could not get device: %lx", hr); 1392 return hr; 1393 } 1394 1395 return S_OK; 1396 } 1397 1398 HRESULT register_collection_notification_client(cubeb * context) 1399 { 1400 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 1401 NULL, CLSCTX_INPROC_SERVER, 1402 IID_PPV_ARGS(context->device_collection_enumerator.receive())); 1403 if (FAILED(hr)) { 1404 LOG("Could not get device enumerator: %lx", hr); 1405 return hr; 1406 } 1407 1408 context->collection_notification_client.reset(new wasapi_collection_notification_client(context)); 1409 1410 hr = context->device_collection_enumerator->RegisterEndpointNotificationCallback( 1411 context->collection_notification_client.get()); 1412 if (FAILED(hr)) { 1413 LOG("Could not register endpoint notification callback: %lx", hr); 1414 context->collection_notification_client.reset(); 1415 context->device_collection_enumerator.reset(); 1416 } 1417 1418 return hr; 1419 } 1420 1421 HRESULT unregister_collection_notification_client(cubeb * context) 1422 { 1423 HRESULT hr = context->device_collection_enumerator-> 1424 UnregisterEndpointNotificationCallback(context->collection_notification_client.get()); 1425 if (FAILED(hr)) { 1426 return hr; 1427 } 1428 1429 context->collection_notification_client = nullptr; 1430 context->device_collection_enumerator = nullptr; 1431 1432 return hr; 1433 } 1434 1435 HRESULT get_default_endpoint(com_ptr<IMMDevice> & device, EDataFlow direction, ERole role) 1436 { 1437 com_ptr<IMMDeviceEnumerator> enumerator; 1438 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 1439 NULL, CLSCTX_INPROC_SERVER, 1440 IID_PPV_ARGS(enumerator.receive())); 1441 if (FAILED(hr)) { 1442 LOG("Could not get device enumerator: %lx", hr); 1443 return hr; 1444 } 1445 hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive()); 1446 if (FAILED(hr)) { 1447 LOG("Could not get default audio endpoint: %lx", hr); 1448 return hr; 1449 } 1450 1451 return ERROR_SUCCESS; 1452 } 1453 1454 double 1455 current_stream_delay(cubeb_stream * stm) 1456 { 1457 stm->stream_reset_lock.assert_current_thread_owns(); 1458 1459 /* If the default audio endpoint went away during playback and we weren't 1460 able to configure a new one, it's possible the caller may call this 1461 before the error callback has propogated back. */ 1462 if (!stm->audio_clock) { 1463 return 0; 1464 } 1465 1466 UINT64 freq; 1467 HRESULT hr = stm->audio_clock->GetFrequency(&freq); 1468 if (FAILED(hr)) { 1469 LOG("GetFrequency failed: %lx", hr); 1470 return 0; 1471 } 1472 1473 UINT64 pos; 1474 hr = stm->audio_clock->GetPosition(&pos, NULL); 1475 if (FAILED(hr)) { 1476 LOG("GetPosition failed: %lx", hr); 1477 return 0; 1478 } 1479 1480 double cur_pos = static_cast<double>(pos) / freq; 1481 double max_pos = static_cast<double>(stm->frames_written) / stm->output_mix_params.rate; 1482 double delay = max_pos - cur_pos; 1483 XASSERT(delay >= 0); 1484 1485 return delay; 1486 } 1487 1488 int 1489 stream_set_volume(cubeb_stream * stm, float volume) 1490 { 1491 stm->stream_reset_lock.assert_current_thread_owns(); 1492 1493 if (!stm->audio_stream_volume) { 1494 return CUBEB_ERROR; 1495 } 1496 1497 uint32_t channels; 1498 HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels); 1499 if (FAILED(hr)) { 1500 LOG("could not get the channel count: %lx", hr); 1501 return CUBEB_ERROR; 1502 } 1503 1504 /* up to 9.1 for now */ 1505 if (channels > 10) { 1506 return CUBEB_ERROR_NOT_SUPPORTED; 1507 } 1508 1509 float volumes[10]; 1510 for (uint32_t i = 0; i < channels; i++) { 1511 volumes[i] = volume; 1512 } 1513 1514 hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes); 1515 if (FAILED(hr)) { 1516 LOG("could not set the channels volume: %lx", hr); 1517 return CUBEB_ERROR; 1518 } 1519 1520 return CUBEB_OK; 1521 } 1522 } // namespace anonymous 1523 1524 extern "C" { 1525 int wasapi_init(cubeb ** context, char const * context_name) 1526 { 1527 /* We don't use the device yet, but need to make sure we can initialize one 1528 so that this backend is not incorrectly enabled on platforms that don't 1529 support WASAPI. */ 1530 com_ptr<IMMDevice> device; 1531 HRESULT hr = get_default_endpoint(device, eRender, eConsole); 1532 if (FAILED(hr)) { 1533 XASSERT(hr != CO_E_NOTINITIALIZED); 1534 LOG("It wasn't able to find a default rendering device: %lx", hr); 1535 hr = get_default_endpoint(device, eCapture, eConsole); 1536 if (FAILED(hr)) { 1537 LOG("It wasn't able to find a default capture device: %lx", hr); 1538 return CUBEB_ERROR; 1539 } 1540 } 1541 1542 cubeb * ctx = new cubeb(); 1543 1544 ctx->ops = &wasapi_ops; 1545 if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { 1546 delete ctx; 1547 return CUBEB_ERROR; 1548 } 1549 1550 LARGE_INTEGER frequency; 1551 if (QueryPerformanceFrequency(&frequency)) { 1552 LOG("Failed getting performance counter frequency, latency reporting will be inacurate"); 1553 ctx->performance_counter_frequency = 0; 1554 } else { 1555 ctx->performance_counter_frequency = frequency.QuadPart; 1556 } 1557 1558 *context = ctx; 1559 1560 return CUBEB_OK; 1561 } 1562 } 1563 1564 namespace { 1565 bool stop_and_join_render_thread(cubeb_stream * stm) 1566 { 1567 bool rv = true; 1568 LOG("Stop and join render thread."); 1569 if (!stm->thread) { 1570 LOG("No thread present."); 1571 return true; 1572 } 1573 1574 // If we've already leaked the thread, just return, 1575 // there is not much we can do. 1576 if (!stm->emergency_bailout.load()) { 1577 return false; 1578 } 1579 1580 BOOL ok = SetEvent(stm->shutdown_event); 1581 if (!ok) { 1582 LOG("Destroy SetEvent failed: %lx", GetLastError()); 1583 } 1584 1585 /* Wait five seconds for the rendering thread to return. It's supposed to 1586 * check its event loop very often, five seconds is rather conservative. */ 1587 DWORD r = WaitForSingleObject(stm->thread, 5000); 1588 if (r != WAIT_OBJECT_0) { 1589 /* Something weird happened, leak the thread and continue the shutdown 1590 * process. */ 1591 *(stm->emergency_bailout) = true; 1592 // We give the ownership to the rendering thread. 1593 stm->emergency_bailout = nullptr; 1594 LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r, GetLastError()); 1595 rv = false; 1596 } 1597 1598 // Only attempts to close and null out the thread and event if the 1599 // WaitForSingleObject above succeeded, so that calling this function again 1600 // attemps to clean up the thread and event each time. 1601 if (rv) { 1602 LOG("Closing thread."); 1603 CloseHandle(stm->thread); 1604 stm->thread = NULL; 1605 1606 CloseHandle(stm->shutdown_event); 1607 stm->shutdown_event = 0; 1608 } 1609 1610 return rv; 1611 } 1612 1613 void wasapi_destroy(cubeb * context) 1614 { 1615 if (context->device_ids) { 1616 cubeb_strings_destroy(context->device_ids); 1617 } 1618 1619 delete context; 1620 } 1621 1622 char const * wasapi_get_backend_id(cubeb * context) 1623 { 1624 return "wasapi"; 1625 } 1626 1627 int 1628 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1629 { 1630 XASSERT(ctx && max_channels); 1631 1632 com_ptr<IMMDevice> device; 1633 HRESULT hr = get_default_endpoint(device, eRender, eConsole); 1634 if (FAILED(hr)) { 1635 return CUBEB_ERROR; 1636 } 1637 1638 com_ptr<IAudioClient> client; 1639 hr = device->Activate(__uuidof(IAudioClient), 1640 CLSCTX_INPROC_SERVER, 1641 NULL, client.receive_vpp()); 1642 if (FAILED(hr)) { 1643 return CUBEB_ERROR; 1644 } 1645 1646 WAVEFORMATEX * tmp = nullptr; 1647 hr = client->GetMixFormat(&tmp); 1648 if (FAILED(hr)) { 1649 return CUBEB_ERROR; 1650 } 1651 com_heap_ptr<WAVEFORMATEX> mix_format(tmp); 1652 1653 *max_channels = mix_format->nChannels; 1654 1655 return CUBEB_OK; 1656 } 1657 1658 int 1659 wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) 1660 { 1661 if (params.format != CUBEB_SAMPLE_FLOAT32NE && params.format != CUBEB_SAMPLE_S16NE) { 1662 return CUBEB_ERROR_INVALID_FORMAT; 1663 } 1664 1665 ERole role = pref_to_role(params.prefs); 1666 1667 com_ptr<IMMDevice> device; 1668 HRESULT hr = get_default_endpoint(device, eRender, role); 1669 if (FAILED(hr)) { 1670 LOG("Could not get default endpoint: %lx", hr); 1671 return CUBEB_ERROR; 1672 } 1673 1674 com_ptr<IAudioClient> client; 1675 hr = device->Activate(__uuidof(IAudioClient), 1676 CLSCTX_INPROC_SERVER, 1677 NULL, client.receive_vpp()); 1678 if (FAILED(hr)) { 1679 LOG("Could not activate device for latency: %lx", hr); 1680 return CUBEB_ERROR; 1681 } 1682 1683 REFERENCE_TIME minimum_period; 1684 REFERENCE_TIME default_period; 1685 hr = client->GetDevicePeriod(&default_period, &minimum_period); 1686 if (FAILED(hr)) { 1687 LOG("Could not get device period: %lx", hr); 1688 return CUBEB_ERROR; 1689 } 1690 1691 LOG("default device period: %I64d, minimum device period: %I64d", default_period, minimum_period); 1692 1693 /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency. 1694 Otherwise, according to the docs, the best latency we can achieve is by 1695 synchronizing the stream and the engine. 1696 http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ 1697 1698 #ifdef _WIN32_WINNT_WIN10 1699 *latency_frames = hns_to_frames(params.rate, minimum_period); 1700 #else 1701 *latency_frames = hns_to_frames(params.rate, default_period); 1702 #endif 1703 1704 LOG("Minimum latency in frames: %u", *latency_frames); 1705 1706 return CUBEB_OK; 1707 } 1708 1709 int 1710 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1711 { 1712 com_ptr<IMMDevice> device; 1713 HRESULT hr = get_default_endpoint(device, eRender, eConsole); 1714 if (FAILED(hr)) { 1715 return CUBEB_ERROR; 1716 } 1717 1718 com_ptr<IAudioClient> client; 1719 hr = device->Activate(__uuidof(IAudioClient), 1720 CLSCTX_INPROC_SERVER, 1721 NULL, client.receive_vpp()); 1722 if (FAILED(hr)) { 1723 return CUBEB_ERROR; 1724 } 1725 1726 WAVEFORMATEX * tmp = nullptr; 1727 hr = client->GetMixFormat(&tmp); 1728 if (FAILED(hr)) { 1729 return CUBEB_ERROR; 1730 } 1731 com_heap_ptr<WAVEFORMATEX> mix_format(tmp); 1732 1733 *rate = mix_format->nSamplesPerSec; 1734 1735 LOG("Preferred sample rate for output: %u", *rate); 1736 1737 return CUBEB_OK; 1738 } 1739 1740 void wasapi_stream_destroy(cubeb_stream * stm); 1741 1742 static void 1743 waveformatex_update_derived_properties(WAVEFORMATEX * format) 1744 { 1745 format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8; 1746 format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; 1747 if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 1748 WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format); 1749 format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample; 1750 } 1751 } 1752 1753 /* Based on the mix format and the stream format, try to find a way to play 1754 what the user requested. */ 1755 static void 1756 handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAVEFORMATEX> & mix_format, const cubeb_stream_params * stream_params) 1757 { 1758 com_ptr<IAudioClient> & audio_client = (direction == eRender) ? stm->output_client : stm->input_client; 1759 XASSERT(audio_client); 1760 /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], 1761 so the reinterpret_cast below should be safe. In practice, this is not 1762 true, and we just want to bail out and let the rest of the code find a good 1763 conversion path instead of trying to make WASAPI do it by itself. 1764 [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ 1765 if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { 1766 return; 1767 } 1768 1769 WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()); 1770 1771 /* Stash a copy of the original mix format in case we need to restore it later. */ 1772 WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; 1773 1774 /* Get the channel mask by the channel layout. 1775 If the layout is not supported, we will get a closest settings below. */ 1776 format_pcm->dwChannelMask = stream_params->layout; 1777 mix_format->nChannels = stream_params->channels; 1778 waveformatex_update_derived_properties(mix_format.get()); 1779 1780 /* Check if wasapi will accept our channel layout request. */ 1781 WAVEFORMATEX * closest; 1782 HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, 1783 mix_format.get(), 1784 &closest); 1785 if (hr == S_FALSE) { 1786 /* Channel layout not supported, but WASAPI gives us a suggestion. Use it, 1787 and handle the eventual upmix/downmix ourselves. Ignore the subformat of 1788 the suggestion, since it seems to always be IEEE_FLOAT. */ 1789 LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); 1790 XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); 1791 WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest); 1792 format_pcm->dwChannelMask = closest_pcm->dwChannelMask; 1793 mix_format->nChannels = closest->nChannels; 1794 waveformatex_update_derived_properties(mix_format.get()); 1795 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1796 /* Not supported, no suggestion. This should not happen, but it does in the 1797 field with some sound cards. We restore the mix format, and let the rest 1798 of the code figure out the right conversion path. */ 1799 XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE); 1800 *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()) = hw_mix_format; 1801 } else if (hr == S_OK) { 1802 LOG("Requested format accepted by WASAPI."); 1803 } else { 1804 LOG("IsFormatSupported unhandled error: %lx", hr); 1805 } 1806 } 1807 1808 static bool 1809 initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client, 1810 cubeb_stream * stm, 1811 const com_heap_ptr<WAVEFORMATEX> & mix_format, 1812 DWORD flags, 1813 EDataFlow direction) 1814 { 1815 com_ptr<IAudioClient3> audio_client3; 1816 audio_client->QueryInterface<IAudioClient3>(audio_client3.receive()); 1817 if (!audio_client3) { 1818 LOG("Could not get IAudioClient3 interface"); 1819 return false; 1820 } 1821 1822 if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) { 1823 // IAudioClient3 doesn't work with loopback streams, and will return error 1824 // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG 1825 LOG("Audio stream is loopback, not using IAudioClient3"); 1826 return false; 1827 } 1828 1829 // IAudioClient3 doesn't support AUDCLNT_STREAMFLAGS_NOPERSIST, and will return 1830 // AUDCLNT_E_INVALID_STREAM_FLAG. This is undocumented. 1831 flags = flags ^ AUDCLNT_STREAMFLAGS_NOPERSIST; 1832 1833 // Some people have reported glitches with capture streams: 1834 // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html 1835 if (direction == eCapture) { 1836 LOG("Audio stream is capture, not using IAudioClient3"); 1837 return false; 1838 } 1839 1840 // Possibly initialize a shared-mode stream using IAudioClient3. Initializing 1841 // a stream this way lets you request lower latencies, but also locks the global 1842 // WASAPI engine at that latency. 1843 // - If we request a shared-mode stream, streams created with IAudioClient will 1844 // have their latency adjusted to match. When the shared-mode stream is 1845 // closed, they'll go back to normal. 1846 // - If there's already a shared-mode stream running, then we cannot request 1847 // the engine change to a different latency - we have to match it. 1848 // - It's antisocial to lock the WASAPI engine at its default latency. If we 1849 // would do this, then stop and use IAudioClient instead. 1850 1851 HRESULT hr; 1852 uint32_t default_period = 0, fundamental_period = 0, min_period = 0, max_period = 0; 1853 hr = audio_client3->GetSharedModeEnginePeriod(mix_format.get(), &default_period, &fundamental_period, &min_period, &max_period); 1854 if (FAILED(hr)) { 1855 LOG("Could not get shared mode engine period: error: %lx", hr); 1856 return false; 1857 } 1858 uint32_t requested_latency = stm->latency; 1859 if (requested_latency >= default_period) { 1860 LOG("Requested latency %i greater than default latency %i, not using IAudioClient3", requested_latency, default_period); 1861 return false; 1862 } 1863 LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i", default_period, fundamental_period, min_period, max_period); 1864 // Snap requested latency to a valid value 1865 uint32_t old_requested_latency = requested_latency; 1866 if (requested_latency < min_period) { 1867 requested_latency = min_period; 1868 } 1869 requested_latency -= (requested_latency - min_period) % fundamental_period; 1870 if (requested_latency != old_requested_latency) { 1871 LOG("Requested latency %i was adjusted to %i", old_requested_latency, requested_latency); 1872 } 1873 1874 hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency, mix_format.get(), NULL); 1875 if (SUCCEEDED(hr)) { 1876 return true; 1877 } 1878 else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { 1879 LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request"); 1880 } else { 1881 LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); 1882 return false; 1883 } 1884 1885 uint32_t current_period = 0; 1886 WAVEFORMATEX* current_format = nullptr; 1887 // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise 1888 // GetCurrentSharedModeEnginePeriod will return E_POINTER 1889 hr = audio_client3->GetCurrentSharedModeEnginePeriod(¤t_format, ¤t_period); 1890 CoTaskMemFree(current_format); 1891 if (FAILED(hr)) { 1892 LOG("Could not get current shared mode engine period: error: %lx", hr); 1893 return false; 1894 } 1895 1896 if (current_period >= default_period) { 1897 LOG("Current shared mode engine period %i too high, not using IAudioClient", current_period); 1898 return false; 1899 } 1900 1901 hr = audio_client3->InitializeSharedAudioStream(flags, current_period, mix_format.get(), NULL); 1902 if (SUCCEEDED(hr)) { 1903 LOG("Current shared mode engine period is %i instead of requested %i", current_period, requested_latency); 1904 return true; 1905 } 1906 1907 LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr); 1908 return false; 1909 } 1910 1911 #define DIRECTION_NAME (direction == eCapture ? "capture" : "render") 1912 1913 template<typename T> 1914 int setup_wasapi_stream_one_side(cubeb_stream * stm, 1915 cubeb_stream_params * stream_params, 1916 wchar_t const * devid, 1917 EDataFlow direction, 1918 REFIID riid, 1919 com_ptr<IAudioClient> & audio_client, 1920 uint32_t * buffer_frame_count, 1921 HANDLE & event, 1922 T & render_or_capture_client, 1923 cubeb_stream_params * mix_params) 1924 { 1925 com_ptr<IMMDevice> device; 1926 HRESULT hr; 1927 bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK; 1928 if (is_loopback && direction != eCapture) { 1929 LOG("Loopback pref can only be used with capture streams!\n"); 1930 return CUBEB_ERROR; 1931 } 1932 1933 stm->stream_reset_lock.assert_current_thread_owns(); 1934 bool try_again = false; 1935 // This loops until we find a device that works, or we've exhausted all 1936 // possibilities. 1937 do { 1938 if (devid) { 1939 hr = get_endpoint(device, devid); 1940 if (FAILED(hr)) { 1941 LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr); 1942 return CUBEB_ERROR; 1943 } 1944 } else { 1945 // If caller has requested loopback but not specified a device, look for 1946 // the default render device. Otherwise look for the default device 1947 // appropriate to the direction. 1948 hr = get_default_endpoint(device, is_loopback ? eRender : direction, pref_to_role(stream_params->prefs)); 1949 if (FAILED(hr)) { 1950 if (is_loopback) { 1951 LOG("Could not get default render endpoint for loopback, error: %lx\n", hr); 1952 } else { 1953 LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME, hr); 1954 } 1955 return CUBEB_ERROR; 1956 } 1957 } 1958 1959 /* Get a client. We will get all other interfaces we need from 1960 * this pointer. */ 1961 #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 1962 hr = device->Activate(__uuidof(IAudioClient3), 1963 CLSCTX_INPROC_SERVER, 1964 NULL, audio_client.receive_vpp()); 1965 if (hr == E_NOINTERFACE) { 1966 #endif 1967 hr = device->Activate(__uuidof(IAudioClient), 1968 CLSCTX_INPROC_SERVER, 1969 NULL, audio_client.receive_vpp()); 1970 #if 0 1971 } 1972 #endif 1973 1974 if (FAILED(hr)) { 1975 LOG("Could not activate the device to get an audio" 1976 " client for %s: error: %lx\n", DIRECTION_NAME, hr); 1977 // A particular device can't be activated because it has been 1978 // unplugged, try fall back to the default audio device. 1979 if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) { 1980 LOG("Trying again with the default %s audio device.", DIRECTION_NAME); 1981 devid = nullptr; 1982 device = nullptr; 1983 try_again = true; 1984 } else { 1985 return CUBEB_ERROR; 1986 } 1987 } else { 1988 try_again = false; 1989 } 1990 } while (try_again); 1991 1992 /* We have to distinguish between the format the mixer uses, 1993 * and the format the stream we want to play uses. */ 1994 WAVEFORMATEX * tmp = nullptr; 1995 hr = audio_client->GetMixFormat(&tmp); 1996 if (FAILED(hr)) { 1997 LOG("Could not fetch current mix format from the audio" 1998 " client for %s: error: %lx", DIRECTION_NAME, hr); 1999 return CUBEB_ERROR; 2000 } 2001 com_heap_ptr<WAVEFORMATEX> mix_format(tmp); 2002 2003 mix_format->wBitsPerSample = stm->bytes_per_sample * 8; 2004 if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { 2005 WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()); 2006 format_pcm->SubFormat = stm->waveformatextensible_sub_format; 2007 } 2008 waveformatex_update_derived_properties(mix_format.get()); 2009 2010 /* Set channel layout only when there're more than two channels. Otherwise, 2011 * use the default setting retrieved from the stream format of the audio 2012 * engine's internal processing by GetMixFormat. */ 2013 if (mix_format->nChannels > 2) { 2014 handle_channel_layout(stm, direction, mix_format, stream_params); 2015 } 2016 2017 mix_params->format = stream_params->format; 2018 mix_params->rate = mix_format->nSamplesPerSec; 2019 mix_params->channels = mix_format->nChannels; 2020 mix_params->layout = mask_to_channel_layout(mix_format.get()); 2021 2022 LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]", 2023 stream_params->format, stream_params->rate, stream_params->channels, 2024 stream_params->layout, 2025 mix_params->format, mix_params->rate, mix_params->channels, 2026 mix_params->layout); 2027 2028 DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST; 2029 2030 // Check if a loopback device should be requested. Note that event callbacks 2031 // do not work with loopback devices, so only request these if not looping. 2032 if (is_loopback) { 2033 flags |= AUDCLNT_STREAMFLAGS_LOOPBACK; 2034 } else { 2035 flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 2036 } 2037 2038 #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902 2039 if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) { 2040 LOG("Initialized with IAudioClient3"); 2041 } else { 2042 #endif 2043 hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 2044 flags, 2045 frames_to_hns(stm, stm->latency), 2046 0, 2047 mix_format.get(), 2048 NULL); 2049 #if 0 2050 } 2051 #endif 2052 if (FAILED(hr)) { 2053 LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr); 2054 return CUBEB_ERROR; 2055 } 2056 2057 hr = audio_client->GetBufferSize(buffer_frame_count); 2058 if (FAILED(hr)) { 2059 LOG("Could not get the buffer size from the client" 2060 " for %s %lx.", DIRECTION_NAME, hr); 2061 return CUBEB_ERROR; 2062 } 2063 2064 // Events are used if not looping back 2065 if (!is_loopback) { 2066 hr = audio_client->SetEventHandle(event); 2067 if (FAILED(hr)) { 2068 LOG("Could set the event handle for the %s client %lx.", 2069 DIRECTION_NAME, hr); 2070 return CUBEB_ERROR; 2071 } 2072 } 2073 2074 hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp()); 2075 if (FAILED(hr)) { 2076 LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr); 2077 return CUBEB_ERROR; 2078 } 2079 2080 return CUBEB_OK; 2081 } 2082 2083 #undef DIRECTION_NAME 2084 2085 int setup_wasapi_stream(cubeb_stream * stm) 2086 { 2087 int rv; 2088 2089 stm->stream_reset_lock.assert_current_thread_owns(); 2090 2091 XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); 2092 2093 if (has_input(stm)) { 2094 LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get()); 2095 rv = setup_wasapi_stream_one_side(stm, 2096 &stm->input_stream_params, 2097 stm->input_device.get(), 2098 eCapture, 2099 __uuidof(IAudioCaptureClient), 2100 stm->input_client, 2101 &stm->input_buffer_frame_count, 2102 stm->input_available_event, 2103 stm->capture_client, 2104 &stm->input_mix_params); 2105 if (rv != CUBEB_OK) { 2106 LOG("Failure to open the input side."); 2107 return rv; 2108 } 2109 2110 // We initializing an input stream, buffer ahead two buffers worth of silence. 2111 // This delays the input side slightly, but allow to not glitch when no input 2112 // is available when calling into the resampler to call the callback: the input 2113 // refill event will be set shortly after to compensate for this lack of data. 2114 // In debug, four buffers are used, to avoid tripping up assertions down the line. 2115 #if !defined(DEBUG) 2116 const int silent_buffer_count = 2; 2117 #else 2118 const int silent_buffer_count = 6; 2119 #endif 2120 stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count * 2121 stm->input_stream_params.channels * 2122 silent_buffer_count); 2123 } 2124 2125 // If we don't have an output device but are requesting a loopback device, 2126 // we attempt to open that same device in output mode in order to drive the 2127 // loopback via the output events. 2128 stm->has_dummy_output = false; 2129 if (!has_output(stm) && stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) { 2130 stm->output_stream_params.rate = stm->input_stream_params.rate; 2131 stm->output_stream_params.channels = stm->input_stream_params.channels; 2132 stm->output_stream_params.layout = stm->input_stream_params.layout; 2133 if (stm->input_device) { 2134 size_t len = wcslen(stm->input_device.get()); 2135 std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]); 2136 if (wcsncpy_s(tmp.get(), len + 1, stm->input_device.get(), len) != 0) { 2137 LOG("Failed to copy device identifier while copying input stream" 2138 " configuration to output stream configuration to drive loopback."); 2139 return CUBEB_ERROR; 2140 } 2141 stm->output_device = move(tmp); 2142 } 2143 stm->has_dummy_output = true; 2144 } 2145 2146 if (has_output(stm)) { 2147 LOG("(%p) Setup render: device=%p", stm, stm->output_device.get()); 2148 rv = setup_wasapi_stream_one_side(stm, 2149 &stm->output_stream_params, 2150 stm->output_device.get(), 2151 eRender, 2152 __uuidof(IAudioRenderClient), 2153 stm->output_client, 2154 &stm->output_buffer_frame_count, 2155 stm->refill_event, 2156 stm->render_client, 2157 &stm->output_mix_params); 2158 if (rv != CUBEB_OK) { 2159 LOG("Failure to open the output side."); 2160 return rv; 2161 } 2162 2163 HRESULT hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume), 2164 stm->audio_stream_volume.receive_vpp()); 2165 if (FAILED(hr)) { 2166 LOG("Could not get the IAudioStreamVolume: %lx", hr); 2167 return CUBEB_ERROR; 2168 } 2169 2170 XASSERT(stm->frames_written == 0); 2171 hr = stm->output_client->GetService(__uuidof(IAudioClock), 2172 stm->audio_clock.receive_vpp()); 2173 if (FAILED(hr)) { 2174 LOG("Could not get the IAudioClock: %lx", hr); 2175 return CUBEB_ERROR; 2176 } 2177 2178 /* Restore the stream volume over a device change. */ 2179 if (stream_set_volume(stm, stm->volume) != CUBEB_OK) { 2180 LOG("Could not set the volume."); 2181 return CUBEB_ERROR; 2182 } 2183 } 2184 2185 /* If we have both input and output, we resample to 2186 * the highest sample rate available. */ 2187 int32_t target_sample_rate; 2188 if (has_input(stm) && has_output(stm)) { 2189 XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate); 2190 target_sample_rate = stm->input_stream_params.rate; 2191 } else if (has_input(stm)) { 2192 target_sample_rate = stm->input_stream_params.rate; 2193 } else { 2194 XASSERT(has_output(stm)); 2195 target_sample_rate = stm->output_stream_params.rate; 2196 } 2197 2198 LOG("Target sample rate: %d", target_sample_rate); 2199 2200 /* If we are playing/capturing a mono stream, we only resample one channel, 2201 and copy it over, so we are always resampling the number 2202 of channels of the stream, not the number of channels 2203 that WASAPI wants. */ 2204 cubeb_stream_params input_params = stm->input_mix_params; 2205 input_params.channels = stm->input_stream_params.channels; 2206 cubeb_stream_params output_params = stm->output_mix_params; 2207 output_params.channels = stm->output_stream_params.channels; 2208 2209 stm->resampler.reset( 2210 cubeb_resampler_create(stm, 2211 has_input(stm) ? &input_params : nullptr, 2212 has_output(stm) ? &output_params : nullptr, 2213 target_sample_rate, 2214 stm->data_callback, 2215 stm->user_ptr, 2216 CUBEB_RESAMPLER_QUALITY_DESKTOP)); 2217 if (!stm->resampler) { 2218 LOG("Could not get a resampler"); 2219 return CUBEB_ERROR; 2220 } 2221 2222 XASSERT(has_input(stm) || has_output(stm)); 2223 2224 if (has_input(stm) && has_output(stm)) { 2225 stm->refill_callback = refill_callback_duplex; 2226 } else if (has_input(stm)) { 2227 stm->refill_callback = refill_callback_input; 2228 } else if (has_output(stm)) { 2229 stm->refill_callback = refill_callback_output; 2230 } 2231 2232 // Create input mixer. 2233 if (has_input(stm) && 2234 ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED && 2235 stm->input_mix_params.layout != stm->input_stream_params.layout) || 2236 (stm->input_mix_params.channels != stm->input_stream_params.channels))) { 2237 if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { 2238 LOG("Input stream using undefined layout! Any mixing may be " 2239 "unpredictable!\n"); 2240 } 2241 stm->input_mixer.reset(cubeb_mixer_create(stm->input_stream_params.format, 2242 stm->input_mix_params.channels, 2243 stm->input_mix_params.layout, 2244 stm->input_stream_params.channels, 2245 stm->input_stream_params.layout)); 2246 assert(stm->input_mixer); 2247 } 2248 2249 // Create output mixer. 2250 if (has_output(stm) && stm->output_mix_params.layout != stm->output_stream_params.layout) { 2251 if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) { 2252 LOG("Output stream using undefined layout! Any mixing may be unpredictable!\n"); 2253 } 2254 stm->output_mixer.reset(cubeb_mixer_create(stm->output_stream_params.format, 2255 stm->output_stream_params.channels, 2256 stm->output_stream_params.layout, 2257 stm->output_mix_params.channels, 2258 stm->output_mix_params.layout)); 2259 assert(stm->output_mixer); 2260 // Input is up/down mixed when depacketized in get_input_buffer. 2261 stm->mix_buffer.resize( 2262 frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count)); 2263 } 2264 2265 return CUBEB_OK; 2266 } 2267 2268 ERole 2269 pref_to_role(cubeb_stream_prefs prefs) 2270 { 2271 if (prefs & CUBEB_STREAM_PREF_VOICE) { 2272 return eCommunications; 2273 } 2274 2275 return eConsole; 2276 } 2277 2278 int 2279 wasapi_stream_init(cubeb * context, cubeb_stream ** stream, 2280 char const * stream_name, 2281 cubeb_devid input_device, 2282 cubeb_stream_params * input_stream_params, 2283 cubeb_devid output_device, 2284 cubeb_stream_params * output_stream_params, 2285 unsigned int latency_frames, cubeb_data_callback data_callback, 2286 cubeb_state_callback state_callback, void * user_ptr) 2287 { 2288 int rv; 2289 2290 XASSERT(context && stream && (input_stream_params || output_stream_params)); 2291 2292 if (output_stream_params && input_stream_params && 2293 output_stream_params->format != input_stream_params->format) { 2294 return CUBEB_ERROR_INVALID_FORMAT; 2295 } 2296 2297 std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm(new cubeb_stream(), wasapi_stream_destroy); 2298 2299 stm->context = context; 2300 stm->data_callback = data_callback; 2301 stm->state_callback = state_callback; 2302 stm->user_ptr = user_ptr; 2303 2304 if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE || 2305 stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) { 2306 stm->role = eCommunications; 2307 } else { 2308 stm->role = eConsole; 2309 } 2310 2311 if (input_stream_params) { 2312 stm->input_stream_params = *input_stream_params; 2313 stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device)); 2314 } 2315 if (output_stream_params) { 2316 stm->output_stream_params = *output_stream_params; 2317 stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device)); 2318 } 2319 2320 switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { 2321 case CUBEB_SAMPLE_S16NE: 2322 stm->bytes_per_sample = sizeof(short); 2323 stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM; 2324 stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>); 2325 break; 2326 case CUBEB_SAMPLE_FLOAT32NE: 2327 stm->bytes_per_sample = sizeof(float); 2328 stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; 2329 stm->linear_input_buffer.reset(new auto_array_wrapper_impl<float>); 2330 break; 2331 default: 2332 return CUBEB_ERROR_INVALID_FORMAT; 2333 } 2334 2335 stm->latency = latency_frames; 2336 2337 stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL); 2338 if (!stm->reconfigure_event) { 2339 LOG("Can't create the reconfigure event, error: %lx", GetLastError()); 2340 return CUBEB_ERROR; 2341 } 2342 2343 /* Unconditionally create the two events so that the wait logic is simpler. */ 2344 stm->refill_event = CreateEvent(NULL, 0, 0, NULL); 2345 if (!stm->refill_event) { 2346 LOG("Can't create the refill event, error: %lx", GetLastError()); 2347 return CUBEB_ERROR; 2348 } 2349 2350 stm->input_available_event = CreateEvent(NULL, 0, 0, NULL); 2351 if (!stm->input_available_event) { 2352 LOG("Can't create the input available event , error: %lx", GetLastError()); 2353 return CUBEB_ERROR; 2354 } 2355 2356 { 2357 /* Locking here is not strictly necessary, because we don't have a 2358 notification client that can reset the stream yet, but it lets us 2359 assert that the lock is held in the function. */ 2360 auto_lock lock(stm->stream_reset_lock); 2361 rv = setup_wasapi_stream(stm.get()); 2362 } 2363 if (rv != CUBEB_OK) { 2364 return rv; 2365 } 2366 2367 if (!((input_stream_params ? 2368 (input_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0) || 2369 (output_stream_params ? 2370 (output_stream_params->prefs & CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) : 0))) { 2371 HRESULT hr = register_notification_client(stm.get()); 2372 if (FAILED(hr)) { 2373 /* this is not fatal, we can still play audio, but we won't be able 2374 to keep using the default audio endpoint if it changes. */ 2375 LOG("failed to register notification client, %lx", hr); 2376 } 2377 } 2378 2379 *stream = stm.release(); 2380 2381 LOG("Stream init succesfull (%p)", *stream); 2382 return CUBEB_OK; 2383 } 2384 2385 void close_wasapi_stream(cubeb_stream * stm) 2386 { 2387 XASSERT(stm); 2388 2389 stm->stream_reset_lock.assert_current_thread_owns(); 2390 2391 stm->output_client = nullptr; 2392 stm->render_client = nullptr; 2393 2394 stm->input_client = nullptr; 2395 stm->capture_client = nullptr; 2396 2397 stm->audio_stream_volume = nullptr; 2398 2399 stm->audio_clock = nullptr; 2400 stm->total_frames_written += static_cast<UINT64>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); 2401 stm->frames_written = 0; 2402 2403 stm->resampler.reset(); 2404 stm->output_mixer.reset(); 2405 stm->input_mixer.reset(); 2406 stm->mix_buffer.clear(); 2407 } 2408 2409 void wasapi_stream_destroy(cubeb_stream * stm) 2410 { 2411 XASSERT(stm); 2412 LOG("Stream destroy (%p)", stm); 2413 2414 // Only free stm->emergency_bailout if we could join the thread. 2415 // If we could not join the thread, stm->emergency_bailout is true 2416 // and is still alive until the thread wakes up and exits cleanly. 2417 if (stop_and_join_render_thread(stm)) { 2418 delete stm->emergency_bailout.load(); 2419 stm->emergency_bailout = nullptr; 2420 } 2421 2422 if (stm->notification_client) { 2423 unregister_notification_client(stm); 2424 } 2425 2426 CloseHandle(stm->reconfigure_event); 2427 CloseHandle(stm->refill_event); 2428 CloseHandle(stm->input_available_event); 2429 2430 // The variables intialized in wasapi_stream_init, 2431 // must be destroyed in wasapi_stream_destroy. 2432 stm->linear_input_buffer.reset(); 2433 2434 { 2435 auto_lock lock(stm->stream_reset_lock); 2436 close_wasapi_stream(stm); 2437 } 2438 2439 delete stm; 2440 } 2441 2442 enum StreamDirection { 2443 OUTPUT, 2444 INPUT 2445 }; 2446 2447 int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) 2448 { 2449 XASSERT((dir == OUTPUT && stm->output_client) || 2450 (dir == INPUT && stm->input_client)); 2451 2452 HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); 2453 if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { 2454 LOG("audioclient invalidated for %s device, reconfiguring", 2455 dir == OUTPUT ? "output" : "input"); 2456 2457 BOOL ok = ResetEvent(stm->reconfigure_event); 2458 if (!ok) { 2459 LOG("resetting reconfig event failed for %s stream: %lx", 2460 dir == OUTPUT ? "output" : "input", GetLastError()); 2461 } 2462 2463 close_wasapi_stream(stm); 2464 int r = setup_wasapi_stream(stm); 2465 if (r != CUBEB_OK) { 2466 LOG("reconfigure failed"); 2467 return r; 2468 } 2469 2470 HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); 2471 if (FAILED(hr2)) { 2472 LOG("could not start the %s stream after reconfig: %lx", 2473 dir == OUTPUT ? "output" : "input", hr); 2474 return CUBEB_ERROR; 2475 } 2476 } else if (FAILED(hr)) { 2477 LOG("could not start the %s stream: %lx.", 2478 dir == OUTPUT ? "output" : "input", hr); 2479 return CUBEB_ERROR; 2480 } 2481 2482 return CUBEB_OK; 2483 } 2484 2485 int wasapi_stream_start(cubeb_stream * stm) 2486 { 2487 auto_lock lock(stm->stream_reset_lock); 2488 2489 XASSERT(stm && !stm->thread && !stm->shutdown_event); 2490 XASSERT(stm->output_client || stm->input_client); 2491 2492 stm->emergency_bailout = new std::atomic<bool>(false); 2493 2494 if (stm->output_client) { 2495 int rv = stream_start_one_side(stm, OUTPUT); 2496 if (rv != CUBEB_OK) { 2497 return rv; 2498 } 2499 } 2500 2501 if (stm->input_client) { 2502 int rv = stream_start_one_side(stm, INPUT); 2503 if (rv != CUBEB_OK) { 2504 return rv; 2505 } 2506 } 2507 2508 stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); 2509 if (!stm->shutdown_event) { 2510 LOG("Can't create the shutdown event, error: %lx", GetLastError()); 2511 return CUBEB_ERROR; 2512 } 2513 2514 stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL); 2515 if (!stm->thread_ready_event) { 2516 LOG("Can't create the thread_ready event, error: %lx", GetLastError()); 2517 return CUBEB_ERROR; 2518 } 2519 2520 cubeb_async_log_reset_threads(); 2521 stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); 2522 if (stm->thread == NULL) { 2523 LOG("could not create WASAPI render thread."); 2524 return CUBEB_ERROR; 2525 } 2526 2527 // Wait for wasapi_stream_render_loop to signal that emergency_bailout has 2528 // been read, avoiding a bailout situation where we could free `stm` 2529 // before wasapi_stream_render_loop had a chance to run. 2530 HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE); 2531 XASSERT(hr == WAIT_OBJECT_0); 2532 CloseHandle(stm->thread_ready_event); 2533 stm->thread_ready_event = 0; 2534 2535 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); 2536 2537 return CUBEB_OK; 2538 } 2539 2540 int wasapi_stream_stop(cubeb_stream * stm) 2541 { 2542 XASSERT(stm); 2543 HRESULT hr; 2544 2545 { 2546 auto_lock lock(stm->stream_reset_lock); 2547 2548 if (stm->output_client) { 2549 hr = stm->output_client->Stop(); 2550 if (FAILED(hr)) { 2551 LOG("could not stop AudioClient (output)"); 2552 return CUBEB_ERROR; 2553 } 2554 } 2555 2556 if (stm->input_client) { 2557 hr = stm->input_client->Stop(); 2558 if (FAILED(hr)) { 2559 LOG("could not stop AudioClient (input)"); 2560 return CUBEB_ERROR; 2561 } 2562 } 2563 2564 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 2565 } 2566 2567 if (stop_and_join_render_thread(stm)) { 2568 delete stm->emergency_bailout.load(); 2569 stm->emergency_bailout = nullptr; 2570 } else { 2571 // If we could not join the thread, put the stream in error. 2572 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); 2573 return CUBEB_ERROR; 2574 } 2575 2576 return CUBEB_OK; 2577 } 2578 2579 int wasapi_stream_reset_default_device(cubeb_stream * stm) 2580 { 2581 XASSERT(stm && stm->reconfigure_event); 2582 BOOL ok = SetEvent(stm->reconfigure_event); 2583 if (!ok) { 2584 LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); 2585 return CUBEB_ERROR; 2586 } 2587 return CUBEB_OK; 2588 } 2589 2590 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) 2591 { 2592 XASSERT(stm && position); 2593 auto_lock lock(stm->stream_reset_lock); 2594 2595 if (!has_output(stm)) { 2596 return CUBEB_ERROR; 2597 } 2598 2599 /* Calculate how far behind the current stream head the playback cursor is. */ 2600 uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) * stm->output_stream_params.rate); 2601 2602 /* Calculate the logical stream head in frames at the stream sample rate. */ 2603 uint64_t max_pos = stm->total_frames_written + 2604 static_cast<uint64_t>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params))); 2605 2606 *position = max_pos; 2607 if (stream_delay <= *position) { 2608 *position -= stream_delay; 2609 } 2610 2611 if (*position < stm->prev_position) { 2612 *position = stm->prev_position; 2613 } 2614 stm->prev_position = *position; 2615 2616 return CUBEB_OK; 2617 } 2618 2619 int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 2620 { 2621 XASSERT(stm && latency); 2622 2623 if (!has_output(stm)) { 2624 return CUBEB_ERROR; 2625 } 2626 2627 auto_lock lock(stm->stream_reset_lock); 2628 2629 /* The GetStreamLatency method only works if the 2630 AudioClient has been initialized. */ 2631 if (!stm->output_client) { 2632 return CUBEB_ERROR; 2633 } 2634 2635 REFERENCE_TIME latency_hns; 2636 HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns); 2637 if (FAILED(hr)) { 2638 return CUBEB_ERROR; 2639 } 2640 *latency = hns_to_frames(stm, latency_hns); 2641 2642 return CUBEB_OK; 2643 } 2644 2645 2646 int wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency) 2647 { 2648 XASSERT(stm && latency); 2649 2650 if (!has_input(stm)) { 2651 return CUBEB_ERROR; 2652 } 2653 2654 auto_lock lock(stm->stream_reset_lock); 2655 2656 if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) { 2657 return CUBEB_ERROR; 2658 } 2659 2660 *latency = hns_to_frames(stm, stm->input_latency_hns); 2661 2662 return CUBEB_OK; 2663 } 2664 2665 int wasapi_stream_set_volume(cubeb_stream * stm, float volume) 2666 { 2667 auto_lock lock(stm->stream_reset_lock); 2668 2669 if (!has_output(stm)) { 2670 return CUBEB_ERROR; 2671 } 2672 2673 if (stream_set_volume(stm, volume) != CUBEB_OK) { 2674 return CUBEB_ERROR; 2675 } 2676 2677 stm->volume = volume; 2678 2679 return CUBEB_OK; 2680 } 2681 2682 static char const * 2683 wstr_to_utf8(LPCWSTR str) 2684 { 2685 int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL); 2686 if (size <= 0) { 2687 return nullptr; 2688 } 2689 2690 char * ret = static_cast<char *>(malloc(size)); 2691 ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL); 2692 return ret; 2693 } 2694 2695 static std::unique_ptr<wchar_t const []> 2696 utf8_to_wstr(char const * str) 2697 { 2698 int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); 2699 if (size <= 0) { 2700 return nullptr; 2701 } 2702 2703 std::unique_ptr<wchar_t []> ret(new wchar_t[size]); 2704 ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size); 2705 return ret; 2706 } 2707 2708 static com_ptr<IMMDevice> 2709 wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev) 2710 { 2711 com_ptr<IMMDevice> ret; 2712 com_ptr<IDeviceTopology> devtopo; 2713 com_ptr<IConnector> connector; 2714 2715 if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, devtopo.receive_vpp())) && 2716 SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) { 2717 wchar_t * tmp = nullptr; 2718 if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) { 2719 com_heap_ptr<wchar_t> filterid(tmp); 2720 if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive()))) 2721 ret = NULL; 2722 } 2723 } 2724 2725 return ret; 2726 } 2727 2728 static BOOL 2729 wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, 2730 IMMDeviceEnumerator * enumerator) 2731 { 2732 BOOL ret = FALSE; 2733 com_ptr<IMMDevice> dev; 2734 HRESULT hr; 2735 2736 hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive()); 2737 if (SUCCEEDED(hr)) { 2738 wchar_t * tmp = nullptr; 2739 if (SUCCEEDED(dev->GetId(&tmp))) { 2740 com_heap_ptr<wchar_t> defdevid(tmp); 2741 ret = (wcscmp(defdevid.get(), device_id) == 0); 2742 } 2743 } 2744 2745 return ret; 2746 } 2747 2748 int 2749 wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) 2750 { 2751 com_ptr<IMMEndpoint> endpoint; 2752 com_ptr<IMMDevice> devnode; 2753 com_ptr<IAudioClient> client; 2754 EDataFlow flow; 2755 DWORD state = DEVICE_STATE_NOTPRESENT; 2756 com_ptr<IPropertyStore> propstore; 2757 REFERENCE_TIME def_period, min_period; 2758 HRESULT hr; 2759 2760 struct prop_variant : public PROPVARIANT { 2761 prop_variant() { PropVariantInit(this); } 2762 ~prop_variant() { PropVariantClear(this); } 2763 prop_variant(prop_variant const &) = delete; 2764 prop_variant & operator=(prop_variant const &) = delete; 2765 }; 2766 2767 hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive())); 2768 if (FAILED(hr)) return CUBEB_ERROR; 2769 2770 hr = endpoint->GetDataFlow(&flow); 2771 if (FAILED(hr)) return CUBEB_ERROR; 2772 2773 wchar_t * tmp = nullptr; 2774 hr = dev->GetId(&tmp); 2775 if (FAILED(hr)) return CUBEB_ERROR; 2776 com_heap_ptr<wchar_t> device_id(tmp); 2777 2778 char const * device_id_intern = intern_device_id(ctx, device_id.get()); 2779 if (!device_id_intern) { 2780 return CUBEB_ERROR; 2781 } 2782 2783 hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); 2784 if (FAILED(hr)) return CUBEB_ERROR; 2785 2786 hr = dev->GetState(&state); 2787 if (FAILED(hr)) return CUBEB_ERROR; 2788 2789 ret.device_id = device_id_intern; 2790 ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id); 2791 prop_variant namevar; 2792 hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); 2793 if (SUCCEEDED(hr)) 2794 ret.friendly_name = wstr_to_utf8(namevar.pwszVal); 2795 2796 devnode = wasapi_get_device_node(enumerator, dev); 2797 if (devnode) { 2798 com_ptr<IPropertyStore> ps; 2799 hr = devnode->OpenPropertyStore(STGM_READ, ps.receive()); 2800 if (FAILED(hr)) return CUBEB_ERROR; 2801 2802 prop_variant instancevar; 2803 hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); 2804 if (SUCCEEDED(hr)) { 2805 ret.group_id = wstr_to_utf8(instancevar.pwszVal); 2806 } 2807 } 2808 2809 ret.preferred = CUBEB_DEVICE_PREF_NONE; 2810 if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) 2811 ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); 2812 if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) 2813 ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); 2814 if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) 2815 ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); 2816 2817 if (flow == eRender) ret.type = CUBEB_DEVICE_TYPE_OUTPUT; 2818 else if (flow == eCapture) ret.type = CUBEB_DEVICE_TYPE_INPUT; 2819 switch (state) { 2820 case DEVICE_STATE_ACTIVE: 2821 ret.state = CUBEB_DEVICE_STATE_ENABLED; 2822 break; 2823 case DEVICE_STATE_UNPLUGGED: 2824 ret.state = CUBEB_DEVICE_STATE_UNPLUGGED; 2825 break; 2826 default: 2827 ret.state = CUBEB_DEVICE_STATE_DISABLED; 2828 break; 2829 }; 2830 2831 ret.format = static_cast<cubeb_device_fmt>(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); 2832 ret.default_format = CUBEB_DEVICE_FMT_F32NE; 2833 prop_variant fmtvar; 2834 hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); 2835 if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { 2836 if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { 2837 const PCMWAVEFORMAT * pcm = reinterpret_cast<const PCMWAVEFORMAT *>(fmtvar.blob.pBlobData); 2838 2839 ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec; 2840 ret.max_channels = pcm->wf.nChannels; 2841 } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { 2842 WAVEFORMATEX* wfx = reinterpret_cast<WAVEFORMATEX*>(fmtvar.blob.pBlobData); 2843 2844 if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || 2845 wfx->wFormatTag == WAVE_FORMAT_PCM) { 2846 ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec; 2847 ret.max_channels = wfx->nChannels; 2848 } 2849 } 2850 } 2851 2852 if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp())) && 2853 SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { 2854 ret.latency_lo = hns_to_frames(ret.default_rate, min_period); 2855 ret.latency_hi = hns_to_frames(ret.default_rate, def_period); 2856 } else { 2857 ret.latency_lo = 0; 2858 ret.latency_hi = 0; 2859 } 2860 2861 return CUBEB_OK; 2862 } 2863 2864 static int 2865 wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, 2866 cubeb_device_collection * out) 2867 { 2868 com_ptr<IMMDeviceEnumerator> enumerator; 2869 com_ptr<IMMDeviceCollection> collection; 2870 HRESULT hr; 2871 UINT cc, i; 2872 EDataFlow flow; 2873 2874 hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, 2875 CLSCTX_INPROC_SERVER, IID_PPV_ARGS(enumerator.receive())); 2876 if (FAILED(hr)) { 2877 LOG("Could not get device enumerator: %lx", hr); 2878 return CUBEB_ERROR; 2879 } 2880 2881 if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender; 2882 else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture; 2883 else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) flow = eAll; 2884 else return CUBEB_ERROR; 2885 2886 hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, collection.receive()); 2887 if (FAILED(hr)) { 2888 LOG("Could not enumerate audio endpoints: %lx", hr); 2889 return CUBEB_ERROR; 2890 } 2891 2892 hr = collection->GetCount(&cc); 2893 if (FAILED(hr)) { 2894 LOG("IMMDeviceCollection::GetCount() failed: %lx", hr); 2895 return CUBEB_ERROR; 2896 } 2897 cubeb_device_info * devices = new cubeb_device_info[cc]; 2898 if (!devices) 2899 return CUBEB_ERROR; 2900 2901 PodZero(devices, cc); 2902 out->count = 0; 2903 for (i = 0; i < cc; i++) { 2904 com_ptr<IMMDevice> dev; 2905 hr = collection->Item(i, dev.receive()); 2906 if (FAILED(hr)) { 2907 LOG("IMMDeviceCollection::Item(%u) failed: %lx", i-1, hr); 2908 continue; 2909 } 2910 if (wasapi_create_device(context, devices[out->count], 2911 enumerator.get(), dev.get()) == CUBEB_OK) { 2912 out->count += 1; 2913 } 2914 } 2915 2916 out->device = devices; 2917 return CUBEB_OK; 2918 } 2919 2920 static int 2921 wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * collection) 2922 { 2923 XASSERT(collection); 2924 2925 for (size_t n = 0; n < collection->count; n++) { 2926 cubeb_device_info& dev = collection->device[n]; 2927 delete [] dev.friendly_name; 2928 delete [] dev.group_id; 2929 } 2930 2931 delete [] collection->device; 2932 return CUBEB_OK; 2933 } 2934 2935 static int 2936 wasapi_register_device_collection_changed(cubeb * context, 2937 cubeb_device_type devtype, 2938 cubeb_device_collection_changed_callback collection_changed_callback, 2939 void * user_ptr) 2940 { 2941 if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { 2942 return CUBEB_ERROR_INVALID_PARAMETER; 2943 } 2944 2945 if (collection_changed_callback) { 2946 // Make sure it has been unregistered first. 2947 XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) && 2948 !context->input_collection_changed_callback) || 2949 ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) && 2950 !context->output_collection_changed_callback)); 2951 2952 // Stop the notification client. Notifications arrive on 2953 // a separate thread. We stop them here to avoid 2954 // synchronization issues during the update. 2955 if (context->device_collection_enumerator.get()) { 2956 HRESULT hr = unregister_collection_notification_client(context); 2957 if (FAILED(hr)) { 2958 return CUBEB_ERROR; 2959 } 2960 } 2961 2962 if (devtype & CUBEB_DEVICE_TYPE_INPUT) { 2963 context->input_collection_changed_callback = collection_changed_callback; 2964 context->input_collection_changed_user_ptr = user_ptr; 2965 } 2966 if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { 2967 context->output_collection_changed_callback = collection_changed_callback; 2968 context->output_collection_changed_user_ptr = user_ptr; 2969 } 2970 2971 HRESULT hr = register_collection_notification_client(context); 2972 if (FAILED(hr)) { 2973 return CUBEB_ERROR; 2974 } 2975 } else { 2976 if (!context->device_collection_enumerator.get()) { 2977 // Already unregistered, ignore it. 2978 return CUBEB_OK; 2979 } 2980 2981 HRESULT hr = unregister_collection_notification_client(context); 2982 if (FAILED(hr)) { 2983 return CUBEB_ERROR; 2984 } 2985 if (devtype & CUBEB_DEVICE_TYPE_INPUT) { 2986 context->input_collection_changed_callback = nullptr; 2987 context->input_collection_changed_user_ptr = nullptr; 2988 } 2989 if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { 2990 context->output_collection_changed_callback = nullptr; 2991 context->output_collection_changed_user_ptr = nullptr; 2992 } 2993 2994 // If after the updates we still have registered 2995 // callbacks restart the notification client. 2996 if (context->input_collection_changed_callback || 2997 context->output_collection_changed_callback) { 2998 hr = register_collection_notification_client(context); 2999 if (FAILED(hr)) { 3000 return CUBEB_ERROR; 3001 } 3002 } 3003 } 3004 3005 return CUBEB_OK; 3006 } 3007 3008 cubeb_ops const wasapi_ops = { 3009 /*.init =*/ wasapi_init, 3010 /*.get_backend_id =*/ wasapi_get_backend_id, 3011 /*.get_max_channel_count =*/ wasapi_get_max_channel_count, 3012 /*.get_min_latency =*/ wasapi_get_min_latency, 3013 /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, 3014 /*.enumerate_devices =*/ wasapi_enumerate_devices, 3015 /*.device_collection_destroy =*/ wasapi_device_collection_destroy, 3016 /*.destroy =*/ wasapi_destroy, 3017 /*.stream_init =*/ wasapi_stream_init, 3018 /*.stream_destroy =*/ wasapi_stream_destroy, 3019 /*.stream_start =*/ wasapi_stream_start, 3020 /*.stream_stop =*/ wasapi_stream_stop, 3021 /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device, 3022 /*.stream_get_position =*/ wasapi_stream_get_position, 3023 /*.stream_get_latency =*/ wasapi_stream_get_latency, 3024 /*.stream_get_input_latency =*/ wasapi_stream_get_input_latency, 3025 /*.stream_set_volume =*/ wasapi_stream_set_volume, 3026 /*.stream_get_current_device =*/ NULL, 3027 /*.stream_device_destroy =*/ NULL, 3028 /*.stream_register_device_changed_callback =*/ NULL, 3029 /*.register_device_collection_changed =*/ wasapi_register_device_collection_changed, 3030 }; 3031 } // namespace anonymous 3032