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