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