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