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