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