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