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 NOMINMAX
8
9 #include <initguid.h>
10 #include <windows.h>
11 #include <mmdeviceapi.h>
12 #include <windef.h>
13 #include <audioclient.h>
14 #include <devicetopology.h>
15 #include <process.h>
16 #include <avrt.h>
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stdint.h>
21 #include <cmath>
22 #include <algorithm>
23 #include <memory>
24 #include <limits>
25 #include <atomic>
26
27 #include "cubeb/cubeb.h"
28 #include "cubeb-internal.h"
29 #include "cubeb_resampler.h"
30 #include "cubeb_utils.h"
31
32 /* devicetopology.h missing in MinGW. */
33 #ifndef __devicetopology_h__
34 #include "cubeb_devicetopology.h"
35 #endif
36
37 /* Taken from winbase.h, Not in MinGW. */
38 #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
39 #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
40 #endif
41
42 #ifndef PKEY_Device_FriendlyName
43 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
44 #endif
45 #ifndef PKEY_Device_InstanceId
46 DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR
47 #endif
48
49 namespace {
50 template<typename T, size_t N>
51 constexpr size_t
ARRAY_LENGTH(T (&)[N])52 ARRAY_LENGTH(T(&)[N])
53 {
54 return N;
55 }
56
57 void
SafeRelease(HANDLE handle)58 SafeRelease(HANDLE handle)
59 {
60 if (handle) {
61 CloseHandle(handle);
62 }
63 }
64
65 template <typename T>
SafeRelease(T * ptr)66 void SafeRelease(T * ptr)
67 {
68 if (ptr) {
69 ptr->Release();
70 }
71 }
72
73 struct auto_com {
auto_com__anon070327290111::auto_com74 auto_com() {
75 result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
76 }
~auto_com__anon070327290111::auto_com77 ~auto_com() {
78 if (result == RPC_E_CHANGED_MODE) {
79 // This is not an error, COM was not initialized by this function, so it is
80 // not necessary to uninit it.
81 LOG("COM was already initialized in STA.");
82 } else if (result == S_FALSE) {
83 // This is not an error. We are allowed to call CoInitializeEx more than
84 // once, as long as it is matches by an CoUninitialize call.
85 // We do that in the dtor which is guaranteed to be called.
86 LOG("COM was already initialized in MTA");
87 }
88 if (SUCCEEDED(result)) {
89 CoUninitialize();
90 }
91 }
ok__anon070327290111::auto_com92 bool ok() {
93 return result == RPC_E_CHANGED_MODE || SUCCEEDED(result);
94 }
95 private:
96 HRESULT result;
97 };
98
99 typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
100 const char * TaskName, LPDWORD TaskIndex);
101 typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
102
103 extern cubeb_ops const wasapi_ops;
104
105 int wasapi_stream_stop(cubeb_stream * stm);
106 int wasapi_stream_start(cubeb_stream * stm);
107 void close_wasapi_stream(cubeb_stream * stm);
108 int setup_wasapi_stream(cubeb_stream * stm);
109 static char * wstr_to_utf8(const wchar_t * str);
110 static std::unique_ptr<const wchar_t[]> utf8_to_wstr(char* str);
111
112 }
113
114 struct cubeb
115 {
116 cubeb_ops const * ops;
117 /* Library dynamically opened to increase the render thread priority, and
118 the two function pointers we need. */
119 HMODULE mmcss_module;
120 set_mm_thread_characteristics_function set_mm_thread_characteristics;
121 revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
122 };
123
124 class wasapi_endpoint_notification_client;
125
126 /* We have three possible callbacks we can use with a stream:
127 * - input only
128 * - output only
129 * - synchronized input and output
130 *
131 * Returns true when we should continue to play, false otherwise.
132 */
133 typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
134
135 struct cubeb_stream
136 {
137 cubeb * context;
138 /* Mixer pameters. We need to convert the input stream to this
139 samplerate/channel layout, as WASAPI does not resample nor upmix
140 itself. */
141 cubeb_stream_params input_mix_params;
142 cubeb_stream_params output_mix_params;
143 /* Stream parameters. This is what the client requested,
144 * and what will be presented in the callback. */
145 cubeb_stream_params input_stream_params;
146 cubeb_stream_params output_stream_params;
147 /* The input and output device, or NULL for default. */
148 cubeb_devid input_device;
149 cubeb_devid output_device;
150 /* The latency initially requested for this stream, in frames. */
151 unsigned latency;
152 cubeb_state_callback state_callback;
153 cubeb_data_callback data_callback;
154 wasapi_refill_callback refill_callback;
155 void * user_ptr;
156 /* Lifetime considerations:
157 - client, render_client, audio_clock and audio_stream_volume are interface
158 pointer to the IAudioClient.
159 - The lifetime for device_enumerator and notification_client, resampler,
160 mix_buffer are the same as the cubeb_stream instance. */
161
162 /* Main handle on the WASAPI stream. */
163 IAudioClient * output_client;
164 /* Interface pointer to use the event-driven interface. */
165 IAudioRenderClient * render_client;
166 /* Interface pointer to use the volume facilities. */
167 IAudioStreamVolume * audio_stream_volume;
168 /* Interface pointer to use the stream audio clock. */
169 IAudioClock * audio_clock;
170 /* Frames written to the stream since it was opened. Reset on device
171 change. Uses mix_params.rate. */
172 UINT64 frames_written;
173 /* Frames written to the (logical) stream since it was first
174 created. Updated on device change. Uses stream_params.rate. */
175 UINT64 total_frames_written;
176 /* Last valid reported stream position. Used to ensure the position
177 reported by stream_get_position increases monotonically. */
178 UINT64 prev_position;
179 /* Device enumerator to be able to be notified when the default
180 device change. */
181 IMMDeviceEnumerator * device_enumerator;
182 /* Device notification client, to be able to be notified when the default
183 audio device changes and route the audio to the new default audio output
184 device */
185 wasapi_endpoint_notification_client * notification_client;
186 /* Main andle to the WASAPI capture stream. */
187 IAudioClient * input_client;
188 /* Interface to use the event driven capture interface */
189 IAudioCaptureClient * capture_client;
190 /* This event is set by the stream_stop and stream_destroy
191 function, so the render loop can exit properly. */
192 HANDLE shutdown_event;
193 /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
194 The reconfiguration is handled by the render loop thread. */
195 HANDLE reconfigure_event;
196 /* This is set by WASAPI when we should refill the stream. */
197 HANDLE refill_event;
198 /* This is set by WASAPI when we should read from the input stream. In
199 * practice, we read from the input stream in the output callback, so
200 * this is not used, but it is necessary to start getting input data. */
201 HANDLE input_available_event;
202 /* Each cubeb_stream has its own thread. */
203 HANDLE thread;
204 /* The lock protects all members that are touched by the render thread or
205 change during a device reset, including: audio_clock, audio_stream_volume,
206 client, frames_written, mix_params, total_frames_written, prev_position. */
207 owned_critical_section stream_reset_lock;
208 /* Maximum number of frames that can be passed down in a callback. */
209 uint32_t input_buffer_frame_count;
210 /* Maximum number of frames that can be requested in a callback. */
211 uint32_t output_buffer_frame_count;
212 /* Resampler instance. Resampling will only happen if necessary. */
213 cubeb_resampler * resampler;
214 /* A buffer for up/down mixing multi-channel audio. */
215 float * mix_buffer;
216 /* WASAPI input works in "packets". We re-linearize the audio packets
217 * into this buffer before handing it to the resampler. */
218 auto_array<float> linear_input_buffer;
219 /* Stream volume. Set via stream_set_volume and used to reset volume on
220 device changes. */
221 float volume;
222 /* True if the stream is draining. */
223 bool draining;
224 /* True when we've destroyed the stream. This pointer is leaked on stream
225 * destruction if we could not join the thread. */
226 std::atomic<std::atomic<bool>*> emergency_bailout;
227 };
228
229 class wasapi_endpoint_notification_client : public IMMNotificationClient
230 {
231 public:
232 /* The implementation of MSCOM was copied from MSDN. */
233 ULONG STDMETHODCALLTYPE
AddRef()234 AddRef()
235 {
236 return InterlockedIncrement(&ref_count);
237 }
238
239 ULONG STDMETHODCALLTYPE
Release()240 Release()
241 {
242 ULONG ulRef = InterlockedDecrement(&ref_count);
243 if (0 == ulRef) {
244 delete this;
245 }
246 return ulRef;
247 }
248
249 HRESULT STDMETHODCALLTYPE
QueryInterface(REFIID riid,VOID ** ppvInterface)250 QueryInterface(REFIID riid, VOID **ppvInterface)
251 {
252 if (__uuidof(IUnknown) == riid) {
253 AddRef();
254 *ppvInterface = (IUnknown*)this;
255 } else if (__uuidof(IMMNotificationClient) == riid) {
256 AddRef();
257 *ppvInterface = (IMMNotificationClient*)this;
258 } else {
259 *ppvInterface = NULL;
260 return E_NOINTERFACE;
261 }
262 return S_OK;
263 }
264
wasapi_endpoint_notification_client(HANDLE event)265 wasapi_endpoint_notification_client(HANDLE event)
266 : ref_count(1)
267 , reconfigure_event(event)
268 { }
269
~wasapi_endpoint_notification_client()270 virtual ~wasapi_endpoint_notification_client()
271 { }
272
273 HRESULT STDMETHODCALLTYPE
OnDefaultDeviceChanged(EDataFlow flow,ERole role,LPCWSTR device_id)274 OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
275 {
276 LOG("Audio device default changed.");
277
278 /* we only support a single stream type for now. */
279 if (flow != eRender && role != eConsole) {
280 return S_OK;
281 }
282
283 BOOL ok = SetEvent(reconfigure_event);
284 if (!ok) {
285 LOG("SetEvent on reconfigure_event failed: %x", GetLastError());
286 }
287
288 return S_OK;
289 }
290
291 /* The remaining methods are not implemented, they simply log when called (if
292 log is enabled), for debugging. */
OnDeviceAdded(LPCWSTR device_id)293 HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
294 {
295 LOG("Audio device added.");
296 return S_OK;
297 };
298
OnDeviceRemoved(LPCWSTR device_id)299 HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
300 {
301 LOG("Audio device removed.");
302 return S_OK;
303 }
304
305 HRESULT STDMETHODCALLTYPE
OnDeviceStateChanged(LPCWSTR device_id,DWORD new_state)306 OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
307 {
308 LOG("Audio device state changed.");
309 return S_OK;
310 }
311
312 HRESULT STDMETHODCALLTYPE
OnPropertyValueChanged(LPCWSTR device_id,const PROPERTYKEY key)313 OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
314 {
315 LOG("Audio device property value changed.");
316 return S_OK;
317 }
318 private:
319 /* refcount for this instance, necessary to implement MSCOM semantics. */
320 LONG ref_count;
321 HANDLE reconfigure_event;
322 };
323
324 namespace {
has_input(cubeb_stream * stm)325 bool has_input(cubeb_stream * stm)
326 {
327 return stm->input_stream_params.rate != 0;
328 }
329
has_output(cubeb_stream * stm)330 bool has_output(cubeb_stream * stm)
331 {
332 return stm->output_stream_params.rate != 0;
333 }
334
should_upmix(cubeb_stream_params & stream,cubeb_stream_params & mixer)335 bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
336 {
337 return mixer.channels > stream.channels;
338 }
339
should_downmix(cubeb_stream_params & stream,cubeb_stream_params & mixer)340 bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
341 {
342 return mixer.channels < stream.channels;
343 }
344
stream_to_mix_samplerate_ratio(cubeb_stream_params & stream,cubeb_stream_params & mixer)345 double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
346 {
347 return double(stream.rate) / mixer.rate;
348 }
349
350
351 uint32_t
get_rate(cubeb_stream * stm)352 get_rate(cubeb_stream * stm)
353 {
354 return has_input(stm) ? stm->input_stream_params.rate
355 : stm->output_stream_params.rate;
356 }
357
358 uint32_t
ms_to_hns(uint32_t ms)359 ms_to_hns(uint32_t ms)
360 {
361 return ms * 10000;
362 }
363
364 uint32_t
hns_to_ms(REFERENCE_TIME hns)365 hns_to_ms(REFERENCE_TIME hns)
366 {
367 return static_cast<uint32_t>(hns / 10000);
368 }
369
370 double
hns_to_s(REFERENCE_TIME hns)371 hns_to_s(REFERENCE_TIME hns)
372 {
373 return static_cast<double>(hns) / 10000000;
374 }
375
376 uint32_t
hns_to_frames(cubeb_stream * stm,REFERENCE_TIME hns)377 hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns)
378 {
379 return hns_to_ms(hns * get_rate(stm)) / 1000;
380 }
381
382 uint32_t
hns_to_frames(uint32_t rate,REFERENCE_TIME hns)383 hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
384 {
385 return hns_to_ms(hns * rate) / 1000;
386 }
387
388 REFERENCE_TIME
frames_to_hns(cubeb_stream * stm,uint32_t frames)389 frames_to_hns(cubeb_stream * stm, uint32_t frames)
390 {
391 return frames * 1000 / get_rate(stm);
392 }
393
394 /* Upmix function, copies a mono channel into L and R */
395 template<typename T>
396 void
mono_to_stereo(T * in,long insamples,T * out,int32_t out_channels)397 mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
398 {
399 for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
400 out[j] = out[j + 1] = in[i];
401 }
402 }
403
404 template<typename T>
405 void
upmix(T * in,long inframes,T * out,int32_t in_channels,int32_t out_channels)406 upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
407 {
408 XASSERT(out_channels >= in_channels && in_channels > 0);
409
410 /* Either way, if we have 2 or more channels, the first two are L and R. */
411 /* If we are playing a mono stream over stereo speakers, copy the data over. */
412 if (in_channels == 1 && out_channels >= 2) {
413 mono_to_stereo(in, inframes, out, out_channels);
414 } else {
415 /* Copy through. */
416 for (int i = 0, o = 0; i < inframes * in_channels;
417 i += in_channels, o += out_channels) {
418 for (int j = 0; j < in_channels; ++j) {
419 out[o + j] = in[i + j];
420 }
421 }
422 }
423
424 /* Check if more channels. */
425 if (out_channels <= 2) {
426 return;
427 }
428
429 /* Put silence in remaining channels. */
430 for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
431 for (int j = 2; j < out_channels; ++j) {
432 out[o + j] = 0.0;
433 }
434 }
435 }
436
437 template<typename T>
438 void
downmix(T * in,long inframes,T * out,int32_t in_channels,int32_t out_channels)439 downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
440 {
441 XASSERT(in_channels >= out_channels);
442 /* We could use a downmix matrix here, applying mixing weight based on the
443 channel, but directsound and winmm simply drop the channels that cannot be
444 rendered by the hardware, so we do the same for consistency. */
445 long out_index = 0;
446 for (long i = 0; i < inframes * in_channels; i += in_channels) {
447 for (int j = 0; j < out_channels; ++j) {
448 out[out_index + j] = in[i + j];
449 }
450 out_index += out_channels;
451 }
452 }
453
454 /* This returns the size of a frame in the stream, before the eventual upmix
455 occurs. */
456 static size_t
frames_to_bytes_before_mix(cubeb_stream * stm,size_t frames)457 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
458 {
459 size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
460 return stream_frame_size * frames;
461 }
462
463 /* This function handles the processing of the input and output audio,
464 * converting it to rate and channel layout specified at initialization.
465 * It then calls the data callback, via the resampler. */
466 long
refill(cubeb_stream * stm,float * input_buffer,long input_frames_count,float * output_buffer,long output_frames_needed)467 refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
468 float * output_buffer, long output_frames_needed)
469 {
470 /* If we need to upmix after resampling, resample into the mix buffer to
471 avoid a copy. */
472 float * dest = nullptr;
473 if (has_output(stm)) {
474 if (should_upmix(stm->output_stream_params, stm->output_mix_params) ||
475 should_downmix(stm->output_stream_params, stm->output_mix_params)) {
476 dest = stm->mix_buffer;
477 } else {
478 dest = output_buffer;
479 }
480 }
481
482 long out_frames = cubeb_resampler_fill(stm->resampler,
483 input_buffer,
484 &input_frames_count,
485 dest,
486 output_frames_needed);
487 /* TODO: Report out_frames < 0 as an error via the API. */
488 XASSERT(out_frames >= 0);
489
490 {
491 auto_lock lock(stm->stream_reset_lock);
492 stm->frames_written += out_frames;
493 }
494
495 /* Go in draining mode if we got fewer frames than requested. */
496 if (out_frames < output_frames_needed) {
497 LOG("start draining.");
498 stm->draining = true;
499 }
500
501 /* If this is not true, there will be glitches.
502 It is alright to have produced less frames if we are draining, though. */
503 XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
504
505 if (has_output(stm)) {
506 if (should_upmix(stm->output_stream_params, stm->output_mix_params)) {
507 upmix(dest, out_frames, output_buffer,
508 stm->output_stream_params.channels, stm->output_mix_params.channels);
509 } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) {
510 downmix(dest, out_frames, output_buffer,
511 stm->output_stream_params.channels, stm->output_mix_params.channels);
512 }
513 }
514
515 return out_frames;
516 }
517
518 /* This helper grabs all the frames available from a capture client, put them in
519 * linear_input_buffer. linear_input_buffer should be cleared before the
520 * callback exits. */
get_input_buffer(cubeb_stream * stm)521 bool get_input_buffer(cubeb_stream * stm)
522 {
523 HRESULT hr;
524 UINT32 padding_in;
525
526 XASSERT(has_input(stm));
527
528 hr = stm->input_client->GetCurrentPadding(&padding_in);
529 if (FAILED(hr)) {
530 LOG("Failed to get padding");
531 return false;
532 }
533 XASSERT(padding_in <= stm->input_buffer_frame_count);
534 UINT32 total_available_input = padding_in;
535
536 BYTE * input_packet = NULL;
537 DWORD flags;
538 UINT64 dev_pos;
539 UINT32 next;
540 /* Get input packets until we have captured enough frames, and put them in a
541 * contiguous buffer. */
542 uint32_t offset = 0;
543 while (offset != total_available_input) {
544 hr = stm->capture_client->GetNextPacketSize(&next);
545 if (FAILED(hr)) {
546 LOG("cannot get next packet size: %x", hr);
547 return false;
548 }
549 /* This can happen if the capture stream has stopped. Just return in this
550 * case. */
551 if (!next) {
552 break;
553 }
554
555 UINT32 packet_size;
556 hr = stm->capture_client->GetBuffer(&input_packet,
557 &packet_size,
558 &flags,
559 &dev_pos,
560 NULL);
561 if (FAILED(hr)) {
562 LOG("GetBuffer failed for capture: %x", hr);
563 return false;
564 }
565 XASSERT(packet_size == next);
566 if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
567 LOG("insert silence: ps=%u", packet_size);
568 stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels);
569 } else {
570 if (should_upmix(stm->input_mix_params, stm->input_stream_params)) {
571 bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
572 packet_size * stm->input_stream_params.channels);
573 assert(ok);
574 upmix(reinterpret_cast<float*>(input_packet), packet_size,
575 stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
576 stm->input_mix_params.channels,
577 stm->input_stream_params.channels);
578 stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
579 } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
580 bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
581 packet_size * stm->input_stream_params.channels);
582 assert(ok);
583 downmix(reinterpret_cast<float*>(input_packet), packet_size,
584 stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
585 stm->input_mix_params.channels,
586 stm->input_stream_params.channels);
587 stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
588 } else {
589 stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
590 packet_size * stm->input_stream_params.channels);
591 }
592 }
593 hr = stm->capture_client->ReleaseBuffer(packet_size);
594 if (FAILED(hr)) {
595 LOG("FAILED to release intput buffer");
596 return false;
597 }
598 offset += packet_size;
599 }
600
601 assert(stm->linear_input_buffer.length() >= total_available_input &&
602 offset == total_available_input);
603
604 return true;
605 }
606
607 /* Get an output buffer from the render_client. It has to be released before
608 * exiting the callback. */
get_output_buffer(cubeb_stream * stm,float * & buffer,size_t & frame_count)609 bool get_output_buffer(cubeb_stream * stm, float *& buffer, size_t & frame_count)
610 {
611 UINT32 padding_out;
612 HRESULT hr;
613
614 XASSERT(has_output(stm));
615
616 hr = stm->output_client->GetCurrentPadding(&padding_out);
617 if (FAILED(hr)) {
618 LOG("Failed to get padding: %x", hr);
619 return false;
620 }
621 XASSERT(padding_out <= stm->output_buffer_frame_count);
622
623 if (stm->draining) {
624 if (padding_out == 0) {
625 LOG("Draining finished.");
626 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
627 return false;
628 }
629 LOG("Draining.");
630 return true;
631 }
632
633 frame_count = stm->output_buffer_frame_count - padding_out;
634 BYTE * output_buffer;
635
636 hr = stm->render_client->GetBuffer(frame_count, &output_buffer);
637 if (FAILED(hr)) {
638 LOG("cannot get render buffer");
639 return false;
640 }
641
642 buffer = reinterpret_cast<float*>(output_buffer);
643
644 return true;
645 }
646
647 /**
648 * This function gets input data from a input device, and pass it along with an
649 * output buffer to the resamplers. */
650 bool
refill_callback_duplex(cubeb_stream * stm)651 refill_callback_duplex(cubeb_stream * stm)
652 {
653 HRESULT hr;
654 float * output_buffer = nullptr;
655 size_t output_frames = 0;
656 size_t input_frames;
657 bool rv;
658
659 XASSERT(has_input(stm) && has_output(stm));
660
661 rv = get_input_buffer(stm);
662 if (!rv) {
663 return rv;
664 }
665
666 input_frames = stm->linear_input_buffer.length() / stm->input_stream_params.channels;
667 if (!input_frames) {
668 return true;
669 }
670
671 rv = get_output_buffer(stm, output_buffer, output_frames);
672 if (!rv) {
673 hr = stm->render_client->ReleaseBuffer(output_frames, 0);
674 return rv;
675 }
676
677 /* This can only happen when debugging, and having breakpoints set in the
678 * callback in a way that it makes the stream underrun. */
679 if (output_frames == 0) {
680 return true;
681 }
682
683 // When WASAPI has not filled the input buffer yet, send silence.
684 double output_duration = double(output_frames) / stm->output_mix_params.rate;
685 double input_duration = double(stm->linear_input_buffer.length() / stm->input_stream_params.channels) / stm->input_mix_params.rate;
686 if (input_duration < output_duration) {
687 size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate));
688 LOG("padding silence: out=%f in=%f pad=%u", output_duration, input_duration, padding);
689 stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels);
690 }
691
692 LOGV("Duplex callback: input frames: %zu, output frames: %zu",
693 stm->linear_input_buffer.length(), output_frames);
694
695 refill(stm,
696 stm->linear_input_buffer.data(),
697 stm->linear_input_buffer.length(),
698 output_buffer,
699 output_frames);
700
701 stm->linear_input_buffer.clear();
702
703 hr = stm->render_client->ReleaseBuffer(output_frames, 0);
704 if (FAILED(hr)) {
705 LOG("failed to release buffer: %x", hr);
706 return false;
707 }
708 return true;
709 }
710
711 bool
refill_callback_input(cubeb_stream * stm)712 refill_callback_input(cubeb_stream * stm)
713 {
714 bool rv, consumed_all_buffer;
715
716 XASSERT(has_input(stm) && !has_output(stm));
717
718 rv = get_input_buffer(stm);
719 if (!rv) {
720 return rv;
721 }
722
723 // This can happen at the very beginning of the stream.
724 if (!stm->linear_input_buffer.length()) {
725 return true;
726 }
727
728 LOGV("Input callback: input frames: %zu", stm->linear_input_buffer.length());
729
730 long read = refill(stm,
731 stm->linear_input_buffer.data(),
732 stm->linear_input_buffer.length(),
733 nullptr,
734 0);
735
736 consumed_all_buffer = read == stm->linear_input_buffer.length();
737
738 stm->linear_input_buffer.clear();
739
740 return consumed_all_buffer;
741 }
742
743 bool
refill_callback_output(cubeb_stream * stm)744 refill_callback_output(cubeb_stream * stm)
745 {
746 bool rv;
747 HRESULT hr;
748 float * output_buffer = nullptr;
749 size_t output_frames = 0;
750
751 XASSERT(!has_input(stm) && has_output(stm));
752
753 rv = get_output_buffer(stm, output_buffer, output_frames);
754 if (!rv) {
755 return rv;
756 }
757
758 if (stm->draining || output_frames == 0) {
759 return true;
760 }
761
762 long got = refill(stm,
763 nullptr,
764 0,
765 output_buffer,
766 output_frames);
767
768 LOGV("Output callback: output frames requested: %zu, got %ld",
769 output_frames, got);
770
771 XASSERT(got >= 0);
772 XASSERT(got == output_frames || stm->draining);
773
774 hr = stm->render_client->ReleaseBuffer(got, 0);
775 if (FAILED(hr)) {
776 LOG("failed to release buffer: %x", hr);
777 return false;
778 }
779
780 return got == output_frames || stm->draining;
781 }
782
783 static unsigned int __stdcall
wasapi_stream_render_loop(LPVOID stream)784 wasapi_stream_render_loop(LPVOID stream)
785 {
786 cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
787 std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
788
789 bool is_playing = true;
790 HANDLE wait_array[4] = {
791 stm->shutdown_event,
792 stm->reconfigure_event,
793 stm->refill_event,
794 stm->input_available_event
795 };
796 HANDLE mmcss_handle = NULL;
797 HRESULT hr = 0;
798 DWORD mmcss_task_index = 0;
799 auto_com com;
800 if (!com.ok()) {
801 LOG("COM initialization failed on render_loop thread.");
802 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
803 return 0;
804 }
805
806 /* We could consider using "Pro Audio" here for WebAudio and
807 maybe WebRTC. */
808 mmcss_handle =
809 stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
810 if (!mmcss_handle) {
811 /* This is not fatal, but we might glitch under heavy load. */
812 LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
813 }
814
815 // This has already been nulled out, simply exit.
816 if (!emergency_bailout) {
817 is_playing = false;
818 }
819
820 /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
821 treat it as a timeout, such as across a system sleep/wake cycle. Trigger
822 the timeout error handling only when the timeout_limit is reached, which is
823 reset on each successful loop. */
824 unsigned timeout_count = 0;
825 const unsigned timeout_limit = 5;
826 while (is_playing) {
827 // We want to check the emergency bailout variable before a
828 // and after the WaitForMultipleObject, because the handles WaitForMultipleObjects
829 // is going to wait on might have been closed already.
830 if (*emergency_bailout) {
831 delete emergency_bailout;
832 return 0;
833 }
834 DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
835 wait_array,
836 FALSE,
837 1000);
838 if (*emergency_bailout) {
839 delete emergency_bailout;
840 return 0;
841 }
842 if (waitResult != WAIT_TIMEOUT) {
843 timeout_count = 0;
844 }
845 switch (waitResult) {
846 case WAIT_OBJECT_0: { /* shutdown */
847 is_playing = false;
848 /* We don't check if the drain is actually finished here, we just want to
849 shutdown. */
850 if (stm->draining) {
851 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
852 }
853 continue;
854 }
855 case WAIT_OBJECT_0 + 1: { /* reconfigure */
856 XASSERT(stm->output_client || stm->input_client);
857 LOG("Reconfiguring the stream");
858 /* Close the stream */
859 if (stm->output_client) {
860 stm->output_client->Stop();
861 LOG("Output stopped.");
862 }
863 if (stm->input_client) {
864 stm->input_client->Stop();
865 LOG("Input stopped.");
866 }
867 {
868 auto_lock lock(stm->stream_reset_lock);
869 close_wasapi_stream(stm);
870 LOG("Stream closed.");
871 /* Reopen a stream and start it immediately. This will automatically pick the
872 new default device for this role. */
873 int r = setup_wasapi_stream(stm);
874 if (r != CUBEB_OK) {
875 LOG("Error setting up the stream during reconfigure.");
876 /* Don't destroy the stream here, since we expect the caller to do
877 so after the error has propagated via the state callback. */
878 is_playing = false;
879 hr = E_FAIL;
880 continue;
881 }
882 LOG("Stream setup successfuly.");
883 }
884 XASSERT(stm->output_client || stm->input_client);
885 if (stm->output_client) {
886 stm->output_client->Start();
887 LOG("Output started after reconfigure.");
888 }
889 if (stm->input_client) {
890 stm->input_client->Start();
891 LOG("Input started after reconfigure.");
892 }
893 break;
894 }
895 case WAIT_OBJECT_0 + 2: /* refill */
896 XASSERT(has_input(stm) && has_output(stm) ||
897 !has_input(stm) && has_output(stm));
898 is_playing = stm->refill_callback(stm);
899 break;
900 case WAIT_OBJECT_0 + 3: /* input available */
901 if (has_input(stm) && has_output(stm)) { continue; }
902 is_playing = stm->refill_callback(stm);
903 break;
904 case WAIT_TIMEOUT:
905 XASSERT(stm->shutdown_event == wait_array[0]);
906 if (++timeout_count >= timeout_limit) {
907 LOG("Render loop reached the timeout limit.");
908 is_playing = false;
909 hr = E_FAIL;
910 }
911 break;
912 default:
913 LOG("case %d not handled in render loop.", waitResult);
914 abort();
915 }
916 }
917
918 if (FAILED(hr)) {
919 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
920 }
921
922 stm->context->revert_mm_thread_characteristics(mmcss_handle);
923
924 return 0;
925 }
926
927 void wasapi_destroy(cubeb * context);
928
set_mm_thread_characteristics_noop(const char *,LPDWORD mmcss_task_index)929 HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index)
930 {
931 return (HANDLE)1;
932 }
933
revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)934 BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)
935 {
936 return true;
937 }
938
register_notification_client(cubeb_stream * stm)939 HRESULT register_notification_client(cubeb_stream * stm)
940 {
941 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
942 NULL, CLSCTX_INPROC_SERVER,
943 IID_PPV_ARGS(&stm->device_enumerator));
944 if (FAILED(hr)) {
945 LOG("Could not get device enumerator: %x", hr);
946 return hr;
947 }
948
949 stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event);
950
951 hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
952 if (FAILED(hr)) {
953 LOG("Could not register endpoint notification callback: %x", hr);
954 SafeRelease(stm->notification_client);
955 stm->notification_client = nullptr;
956 SafeRelease(stm->device_enumerator);
957 stm->device_enumerator = nullptr;
958 }
959
960 return hr;
961 }
962
unregister_notification_client(cubeb_stream * stm)963 HRESULT unregister_notification_client(cubeb_stream * stm)
964 {
965 XASSERT(stm);
966 HRESULT hr;
967
968 if (!stm->device_enumerator) {
969 return S_OK;
970 }
971
972 hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
973 if (FAILED(hr)) {
974 // We can't really do anything here, we'll probably leak the
975 // notification client, but we can at least release the enumerator.
976 SafeRelease(stm->device_enumerator);
977 return S_OK;
978 }
979
980 SafeRelease(stm->notification_client);
981 SafeRelease(stm->device_enumerator);
982
983 return S_OK;
984 }
985
get_endpoint(IMMDevice ** device,LPCWSTR devid)986 HRESULT get_endpoint(IMMDevice ** device, LPCWSTR devid)
987 {
988 IMMDeviceEnumerator * enumerator;
989 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
990 NULL, CLSCTX_INPROC_SERVER,
991 IID_PPV_ARGS(&enumerator));
992 if (FAILED(hr)) {
993 LOG("Could not get device enumerator: %x", hr);
994 return hr;
995 }
996
997 hr = enumerator->GetDevice(devid, device);
998 if (FAILED(hr)) {
999 LOG("Could not get device: %x", hr);
1000 SafeRelease(enumerator);
1001 return hr;
1002 }
1003
1004 SafeRelease(enumerator);
1005
1006 return S_OK;
1007 }
1008
get_default_endpoint(IMMDevice ** device,EDataFlow direction)1009 HRESULT get_default_endpoint(IMMDevice ** device, EDataFlow direction)
1010 {
1011 IMMDeviceEnumerator * enumerator;
1012 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
1013 NULL, CLSCTX_INPROC_SERVER,
1014 IID_PPV_ARGS(&enumerator));
1015 if (FAILED(hr)) {
1016 LOG("Could not get device enumerator: %x", hr);
1017 return hr;
1018 }
1019 hr = enumerator->GetDefaultAudioEndpoint(direction, eConsole, device);
1020 if (FAILED(hr)) {
1021 LOG("Could not get default audio endpoint: %x", hr);
1022 SafeRelease(enumerator);
1023 return hr;
1024 }
1025
1026 SafeRelease(enumerator);
1027
1028 return ERROR_SUCCESS;
1029 }
1030
1031 double
current_stream_delay(cubeb_stream * stm)1032 current_stream_delay(cubeb_stream * stm)
1033 {
1034 stm->stream_reset_lock.assert_current_thread_owns();
1035
1036 /* If the default audio endpoint went away during playback and we weren't
1037 able to configure a new one, it's possible the caller may call this
1038 before the error callback has propogated back. */
1039 if (!stm->audio_clock) {
1040 return 0;
1041 }
1042
1043 UINT64 freq;
1044 HRESULT hr = stm->audio_clock->GetFrequency(&freq);
1045 if (FAILED(hr)) {
1046 LOG("GetFrequency failed: %x", hr);
1047 return 0;
1048 }
1049
1050 UINT64 pos;
1051 hr = stm->audio_clock->GetPosition(&pos, NULL);
1052 if (FAILED(hr)) {
1053 LOG("GetPosition failed: %x", hr);
1054 return 0;
1055 }
1056
1057 double cur_pos = static_cast<double>(pos) / freq;
1058 double max_pos = static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
1059 double delay = max_pos - cur_pos;
1060 XASSERT(delay >= 0);
1061
1062 return delay;
1063 }
1064
1065 int
stream_set_volume(cubeb_stream * stm,float volume)1066 stream_set_volume(cubeb_stream * stm, float volume)
1067 {
1068 stm->stream_reset_lock.assert_current_thread_owns();
1069
1070 if (!stm->audio_stream_volume) {
1071 return CUBEB_ERROR;
1072 }
1073
1074 uint32_t channels;
1075 HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
1076 if (hr != S_OK) {
1077 LOG("could not get the channel count: %x", hr);
1078 return CUBEB_ERROR;
1079 }
1080
1081 /* up to 9.1 for now */
1082 if (channels > 10) {
1083 return CUBEB_ERROR_NOT_SUPPORTED;
1084 }
1085
1086 float volumes[10];
1087 for (uint32_t i = 0; i < channels; i++) {
1088 volumes[i] = volume;
1089 }
1090
1091 hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
1092 if (hr != S_OK) {
1093 LOG("could not set the channels volume: %x", hr);
1094 return CUBEB_ERROR;
1095 }
1096
1097 return CUBEB_OK;
1098 }
1099 } // namespace anonymous
1100
1101 extern "C" {
wasapi_init(cubeb ** context,char const * context_name)1102 int wasapi_init(cubeb ** context, char const * context_name)
1103 {
1104 HRESULT hr;
1105 auto_com com;
1106 if (!com.ok()) {
1107 return CUBEB_ERROR;
1108 }
1109
1110 /* We don't use the device yet, but need to make sure we can initialize one
1111 so that this backend is not incorrectly enabled on platforms that don't
1112 support WASAPI. */
1113 IMMDevice * device;
1114 hr = get_default_endpoint(&device, eRender);
1115 if (FAILED(hr)) {
1116 LOG("Could not get device: %x", hr);
1117 return CUBEB_ERROR;
1118 }
1119 SafeRelease(device);
1120
1121 cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
1122 if (!ctx) {
1123 return CUBEB_ERROR;
1124 }
1125
1126 ctx->ops = &wasapi_ops;
1127
1128 ctx->mmcss_module = LoadLibraryA("Avrt.dll");
1129
1130 if (ctx->mmcss_module) {
1131 ctx->set_mm_thread_characteristics =
1132 (set_mm_thread_characteristics_function) GetProcAddress(
1133 ctx->mmcss_module, "AvSetMmThreadCharacteristicsA");
1134 ctx->revert_mm_thread_characteristics =
1135 (revert_mm_thread_characteristics_function) GetProcAddress(
1136 ctx->mmcss_module, "AvRevertMmThreadCharacteristics");
1137 if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) {
1138 LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError());
1139 FreeLibrary(ctx->mmcss_module);
1140 }
1141 } else {
1142 // This is not a fatal error, but we might end up glitching when
1143 // the system is under high load.
1144 LOG("Could not load Avrt.dll");
1145 ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop;
1146 ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop;
1147 }
1148
1149 *context = ctx;
1150
1151 return CUBEB_OK;
1152 }
1153 }
1154
1155 namespace {
stop_and_join_render_thread(cubeb_stream * stm)1156 bool stop_and_join_render_thread(cubeb_stream * stm)
1157 {
1158 bool rv = true;
1159 LOG("Stop and join render thread.");
1160 if (!stm->thread) {
1161 LOG("No thread present.");
1162 return true;
1163 }
1164
1165 // If we've already leaked the thread, just return,
1166 // there is not much we can do.
1167 if (!stm->emergency_bailout.load()) {
1168 return false;
1169 }
1170
1171 BOOL ok = SetEvent(stm->shutdown_event);
1172 if (!ok) {
1173 LOG("Destroy SetEvent failed: %d", GetLastError());
1174 }
1175
1176 /* Wait five seconds for the rendering thread to return. It's supposed to
1177 * check its event loop very often, five seconds is rather conservative. */
1178 DWORD r = WaitForSingleObject(stm->thread, 5000);
1179 if (r == WAIT_TIMEOUT) {
1180 /* Something weird happened, leak the thread and continue the shutdown
1181 * process. */
1182 *(stm->emergency_bailout) = true;
1183 // We give the ownership to the rendering thread.
1184 stm->emergency_bailout = nullptr;
1185 LOG("Destroy WaitForSingleObject on thread timed out,"
1186 " leaking the thread: %d", GetLastError());
1187 rv = false;
1188 }
1189 if (r == WAIT_FAILED) {
1190 *(stm->emergency_bailout) = true;
1191 // We give the ownership to the rendering thread.
1192 stm->emergency_bailout = nullptr;
1193 LOG("Destroy WaitForSingleObject on thread failed: %d", GetLastError());
1194 rv = false;
1195 }
1196
1197
1198 // Only attempts to close and null out the thread and event if the
1199 // WaitForSingleObject above succeeded, so that calling this function again
1200 // attemps to clean up the thread and event each time.
1201 if (rv) {
1202 LOG("Closing thread.");
1203 CloseHandle(stm->thread);
1204 stm->thread = NULL;
1205
1206 CloseHandle(stm->shutdown_event);
1207 stm->shutdown_event = 0;
1208 }
1209
1210 return rv;
1211 }
1212
wasapi_destroy(cubeb * context)1213 void wasapi_destroy(cubeb * context)
1214 {
1215 if (context->mmcss_module) {
1216 FreeLibrary(context->mmcss_module);
1217 }
1218 free(context);
1219 }
1220
wasapi_get_backend_id(cubeb * context)1221 char const * wasapi_get_backend_id(cubeb * context)
1222 {
1223 return "wasapi";
1224 }
1225
1226 int
wasapi_get_max_channel_count(cubeb * ctx,uint32_t * max_channels)1227 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
1228 {
1229 HRESULT hr;
1230 IAudioClient * client;
1231 WAVEFORMATEX * mix_format;
1232 auto_com com;
1233 if (!com.ok()) {
1234 return CUBEB_ERROR;
1235 }
1236
1237 XASSERT(ctx && max_channels);
1238
1239 IMMDevice * device;
1240 hr = get_default_endpoint(&device, eRender);
1241 if (FAILED(hr)) {
1242 return CUBEB_ERROR;
1243 }
1244
1245 hr = device->Activate(__uuidof(IAudioClient),
1246 CLSCTX_INPROC_SERVER,
1247 NULL, (void **)&client);
1248 SafeRelease(device);
1249 if (FAILED(hr)) {
1250 return CUBEB_ERROR;
1251 }
1252
1253 hr = client->GetMixFormat(&mix_format);
1254 if (FAILED(hr)) {
1255 SafeRelease(client);
1256 return CUBEB_ERROR;
1257 }
1258
1259 *max_channels = mix_format->nChannels;
1260
1261 CoTaskMemFree(mix_format);
1262 SafeRelease(client);
1263
1264 return CUBEB_OK;
1265 }
1266
1267 int
wasapi_get_min_latency(cubeb * ctx,cubeb_stream_params params,uint32_t * latency_frames)1268 wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
1269 {
1270 HRESULT hr;
1271 IAudioClient * client;
1272 REFERENCE_TIME default_period;
1273 auto_com com;
1274 if (!com.ok()) {
1275 return CUBEB_ERROR;
1276 }
1277
1278 if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
1279 return CUBEB_ERROR_INVALID_FORMAT;
1280 }
1281
1282 IMMDevice * device;
1283 hr = get_default_endpoint(&device, eRender);
1284 if (FAILED(hr)) {
1285 LOG("Could not get default endpoint: %x", hr);
1286 return CUBEB_ERROR;
1287 }
1288
1289 hr = device->Activate(__uuidof(IAudioClient),
1290 CLSCTX_INPROC_SERVER,
1291 NULL, (void **)&client);
1292 SafeRelease(device);
1293 if (FAILED(hr)) {
1294 LOG("Could not activate device for latency: %x", hr);
1295 return CUBEB_ERROR;
1296 }
1297
1298 /* The second parameter is for exclusive mode, that we don't use. */
1299 hr = client->GetDevicePeriod(&default_period, NULL);
1300 if (FAILED(hr)) {
1301 SafeRelease(client);
1302 LOG("Could not get device period: %x", hr);
1303 return CUBEB_ERROR;
1304 }
1305
1306 LOG("default device period: %lld", default_period);
1307
1308 /* According to the docs, the best latency we can achieve is by synchronizing
1309 the stream and the engine.
1310 http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
1311
1312 *latency_frames = hns_to_frames(params.rate, default_period);
1313
1314 LOG("Minimum latency in frames: %u", *latency_frames);
1315
1316 SafeRelease(client);
1317
1318 return CUBEB_OK;
1319 }
1320
1321 int
wasapi_get_preferred_sample_rate(cubeb * ctx,uint32_t * rate)1322 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
1323 {
1324 HRESULT hr;
1325 IAudioClient * client;
1326 WAVEFORMATEX * mix_format;
1327 auto_com com;
1328 if (!com.ok()) {
1329 return CUBEB_ERROR;
1330 }
1331
1332 IMMDevice * device;
1333 hr = get_default_endpoint(&device, eRender);
1334 if (FAILED(hr)) {
1335 return CUBEB_ERROR;
1336 }
1337
1338 hr = device->Activate(__uuidof(IAudioClient),
1339 CLSCTX_INPROC_SERVER,
1340 NULL, (void **)&client);
1341 SafeRelease(device);
1342 if (FAILED(hr)) {
1343 return CUBEB_ERROR;
1344 }
1345
1346 hr = client->GetMixFormat(&mix_format);
1347 if (FAILED(hr)) {
1348 SafeRelease(client);
1349 return CUBEB_ERROR;
1350 }
1351
1352 *rate = mix_format->nSamplesPerSec;
1353
1354 LOG("Preferred sample rate for output: %u", *rate);
1355
1356 CoTaskMemFree(mix_format);
1357 SafeRelease(client);
1358
1359 return CUBEB_OK;
1360 }
1361
1362 void wasapi_stream_destroy(cubeb_stream * stm);
1363
1364 /* Based on the mix format and the stream format, try to find a way to play
1365 what the user requested. */
1366 static void
handle_channel_layout(cubeb_stream * stm,WAVEFORMATEX ** mix_format,const cubeb_stream_params * stream_params)1367 handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
1368 {
1369 /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
1370 handled in the callback. */
1371 if ((*mix_format)->nChannels <= 2) {
1372 return;
1373 }
1374
1375 /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
1376 so the reinterpret_cast below should be safe. In practice, this is not
1377 true, and we just want to bail out and let the rest of the code find a good
1378 conversion path instead of trying to make WASAPI do it by itself.
1379 [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
1380 if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
1381 return;
1382 }
1383
1384 WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format);
1385
1386 /* Stash a copy of the original mix format in case we need to restore it later. */
1387 WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
1388
1389 /* The hardware is in surround mode, we want to only use front left and front
1390 right. Try that, and check if it works. */
1391 switch (stream_params->channels) {
1392 case 1: /* Mono */
1393 format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
1394 break;
1395 case 2: /* Stereo */
1396 format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
1397 break;
1398 default:
1399 XASSERT(false && "Channel layout not supported.");
1400 break;
1401 }
1402 (*mix_format)->nChannels = stream_params->channels;
1403 (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
1404 (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
1405 format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1406 (*mix_format)->wBitsPerSample = 32;
1407 format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
1408
1409 /* Check if wasapi will accept our channel layout request. */
1410 WAVEFORMATEX * closest;
1411 HRESULT hr = stm->output_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
1412 *mix_format,
1413 &closest);
1414 if (hr == S_FALSE) {
1415 /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
1416 eventual upmix/downmix ourselves */
1417 LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
1418 WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
1419 XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat);
1420 CoTaskMemFree(*mix_format);
1421 *mix_format = closest;
1422 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
1423 /* Not supported, no suggestion. This should not happen, but it does in the
1424 field with some sound cards. We restore the mix format, and let the rest
1425 of the code figure out the right conversion path. */
1426 *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format) = hw_mix_format;
1427 } else if (hr == S_OK) {
1428 LOG("Requested format accepted by WASAPI.");
1429 } else {
1430 LOG("IsFormatSupported unhandled error: %x", hr);
1431 }
1432 }
1433
1434 #define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
1435
1436 template<typename T>
setup_wasapi_stream_one_side(cubeb_stream * stm,cubeb_stream_params * stream_params,cubeb_devid devid,EDataFlow direction,REFIID riid,IAudioClient ** audio_client,uint32_t * buffer_frame_count,HANDLE & event,T ** render_or_capture_client,cubeb_stream_params * mix_params)1437 int setup_wasapi_stream_one_side(cubeb_stream * stm,
1438 cubeb_stream_params * stream_params,
1439 cubeb_devid devid,
1440 EDataFlow direction,
1441 REFIID riid,
1442 IAudioClient ** audio_client,
1443 uint32_t * buffer_frame_count,
1444 HANDLE & event,
1445 T ** render_or_capture_client,
1446 cubeb_stream_params * mix_params)
1447 {
1448 IMMDevice * device;
1449 WAVEFORMATEX * mix_format;
1450 HRESULT hr;
1451
1452 stm->stream_reset_lock.assert_current_thread_owns();
1453 bool try_again = false;
1454 // This loops until we find a device that works, or we've exhausted all
1455 // possibilities.
1456 do {
1457 if (devid) {
1458 std::unique_ptr<const wchar_t[]> id(utf8_to_wstr(reinterpret_cast<char*>(devid)));
1459 hr = get_endpoint(&device, id.get());
1460 if (FAILED(hr)) {
1461 LOG("Could not get %s endpoint, error: %x\n", DIRECTION_NAME, hr);
1462 return CUBEB_ERROR;
1463 }
1464 }
1465 else {
1466 hr = get_default_endpoint(&device, direction);
1467 if (FAILED(hr)) {
1468 LOG("Could not get default %s endpoint, error: %x\n", DIRECTION_NAME, hr);
1469 return CUBEB_ERROR;
1470 }
1471 }
1472
1473 /* Get a client. We will get all other interfaces we need from
1474 * this pointer. */
1475 hr = device->Activate(__uuidof(IAudioClient),
1476 CLSCTX_INPROC_SERVER,
1477 NULL, (void **)audio_client);
1478 SafeRelease(device);
1479 if (FAILED(hr)) {
1480 LOG("Could not activate the device to get an audio"
1481 " client for %s: error: %x\n", DIRECTION_NAME, hr);
1482 // A particular device can't be activated because it has been
1483 // unplugged, try fall back to the default audio device.
1484 if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) {
1485 LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
1486 devid = nullptr;
1487 try_again = true;
1488 } else {
1489 return CUBEB_ERROR;
1490 }
1491 } else {
1492 try_again = false;
1493 }
1494 } while (try_again);
1495
1496 /* We have to distinguish between the format the mixer uses,
1497 * and the format the stream we want to play uses. */
1498 hr = (*audio_client)->GetMixFormat(&mix_format);
1499 if (FAILED(hr)) {
1500 LOG("Could not fetch current mix format from the audio"
1501 " client for %s: error: %x", DIRECTION_NAME, hr);
1502 return CUBEB_ERROR;
1503 }
1504
1505 handle_channel_layout(stm, &mix_format, stream_params);
1506
1507 /* Shared mode WASAPI always supports float32 sample format, so this
1508 * is safe. */
1509 mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
1510 mix_params->rate = mix_format->nSamplesPerSec;
1511 mix_params->channels = mix_format->nChannels;
1512 LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
1513 stream_params->format, stream_params->rate, stream_params->channels,
1514 mix_params->format, mix_params->rate, mix_params->channels);
1515
1516 hr = (*audio_client)->Initialize(AUDCLNT_SHAREMODE_SHARED,
1517 AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
1518 AUDCLNT_STREAMFLAGS_NOPERSIST,
1519 frames_to_hns(stm, stm->latency),
1520 0,
1521 mix_format,
1522 NULL);
1523 if (FAILED(hr)) {
1524 LOG("Unable to initialize audio client for %s: %x.", DIRECTION_NAME, hr);
1525 return CUBEB_ERROR;
1526 }
1527
1528 CoTaskMemFree(mix_format);
1529
1530 hr = (*audio_client)->GetBufferSize(buffer_frame_count);
1531 if (FAILED(hr)) {
1532 LOG("Could not get the buffer size from the client"
1533 " for %s %x.", DIRECTION_NAME, hr);
1534 return CUBEB_ERROR;
1535 }
1536
1537 // Input is up/down mixed when depacketized in get_input_buffer.
1538 if (has_output(stm) &&
1539 (should_upmix(*stream_params, *mix_params) ||
1540 should_downmix(*stream_params, *mix_params))) {
1541 stm->mix_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, *buffer_frame_count));
1542 }
1543
1544 hr = (*audio_client)->SetEventHandle(event);
1545 if (FAILED(hr)) {
1546 LOG("Could set the event handle for the %s client %x.",
1547 DIRECTION_NAME, hr);
1548 return CUBEB_ERROR;
1549 }
1550
1551 hr = (*audio_client)->GetService(riid, (void **)render_or_capture_client);
1552 if (FAILED(hr)) {
1553 LOG("Could not get the %s client %x.", DIRECTION_NAME, hr);
1554 return CUBEB_ERROR;
1555 }
1556
1557 return CUBEB_OK;
1558 }
1559
1560 #undef DIRECTION_NAME
1561
setup_wasapi_stream(cubeb_stream * stm)1562 int setup_wasapi_stream(cubeb_stream * stm)
1563 {
1564 HRESULT hr;
1565 int rv;
1566
1567 stm->stream_reset_lock.assert_current_thread_owns();
1568
1569 auto_com com;
1570 if (!com.ok()) {
1571 LOG("Failure to initialize COM.");
1572 return CUBEB_ERROR;
1573 }
1574
1575 XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first.");
1576
1577 if (has_input(stm)) {
1578 LOG("Setup capture: device=%x", (int)stm->input_device);
1579 rv = setup_wasapi_stream_one_side(stm,
1580 &stm->input_stream_params,
1581 stm->input_device,
1582 eCapture,
1583 __uuidof(IAudioCaptureClient),
1584 &stm->input_client,
1585 &stm->input_buffer_frame_count,
1586 stm->input_available_event,
1587 &stm->capture_client,
1588 &stm->input_mix_params);
1589 if (rv != CUBEB_OK) {
1590 LOG("Failure to open the input side.");
1591 return rv;
1592 }
1593 }
1594
1595 if (has_output(stm)) {
1596 LOG("Setup render: device=%x", (int)stm->output_device);
1597 rv = setup_wasapi_stream_one_side(stm,
1598 &stm->output_stream_params,
1599 stm->output_device,
1600 eRender,
1601 __uuidof(IAudioRenderClient),
1602 &stm->output_client,
1603 &stm->output_buffer_frame_count,
1604 stm->refill_event,
1605 &stm->render_client,
1606 &stm->output_mix_params);
1607 if (rv != CUBEB_OK) {
1608 LOG("Failure to open the output side.");
1609 return rv;
1610 }
1611
1612 hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
1613 (void **)&stm->audio_stream_volume);
1614 if (FAILED(hr)) {
1615 LOG("Could not get the IAudioStreamVolume: %x", hr);
1616 return CUBEB_ERROR;
1617 }
1618
1619 XASSERT(stm->frames_written == 0);
1620 hr = stm->output_client->GetService(__uuidof(IAudioClock),
1621 (void **)&stm->audio_clock);
1622 if (FAILED(hr)) {
1623 LOG("Could not get the IAudioClock: %x", hr);
1624 return CUBEB_ERROR;
1625 }
1626
1627 /* Restore the stream volume over a device change. */
1628 if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
1629 LOG("Could not set the volume.");
1630 return CUBEB_ERROR;
1631 }
1632 }
1633
1634 /* If we have both input and output, we resample to
1635 * the highest sample rate available. */
1636 int32_t target_sample_rate;
1637 if (has_input(stm) && has_output(stm)) {
1638 assert(stm->input_stream_params.rate == stm->output_stream_params.rate);
1639 target_sample_rate = stm->input_stream_params.rate;
1640 } else if (has_input(stm)) {
1641 target_sample_rate = stm->input_stream_params.rate;
1642 } else {
1643 XASSERT(has_output(stm));
1644 target_sample_rate = stm->output_stream_params.rate;
1645 }
1646
1647 LOG("Target sample rate: %d", target_sample_rate);
1648
1649 /* If we are playing/capturing a mono stream, we only resample one channel,
1650 and copy it over, so we are always resampling the number
1651 of channels of the stream, not the number of channels
1652 that WASAPI wants. */
1653 cubeb_stream_params input_params = stm->input_mix_params;
1654 input_params.channels = stm->input_stream_params.channels;
1655 cubeb_stream_params output_params = stm->output_mix_params;
1656 output_params.channels = stm->output_stream_params.channels;
1657
1658 stm->resampler =
1659 cubeb_resampler_create(stm,
1660 has_input(stm) ? &input_params : nullptr,
1661 has_output(stm) ? &output_params : nullptr,
1662 target_sample_rate,
1663 stm->data_callback,
1664 stm->user_ptr,
1665 CUBEB_RESAMPLER_QUALITY_DESKTOP);
1666 if (!stm->resampler) {
1667 LOG("Could not get a resampler");
1668 return CUBEB_ERROR;
1669 }
1670
1671 XASSERT(has_input(stm) || has_output(stm));
1672
1673 if (has_input(stm) && has_output(stm)) {
1674 stm->refill_callback = refill_callback_duplex;
1675 } else if (has_input(stm)) {
1676 stm->refill_callback = refill_callback_input;
1677 } else if (has_output(stm)) {
1678 stm->refill_callback = refill_callback_output;
1679 }
1680
1681 return CUBEB_OK;
1682 }
1683
1684 int
wasapi_stream_init(cubeb * context,cubeb_stream ** stream,char const * stream_name,cubeb_devid input_device,cubeb_stream_params * input_stream_params,cubeb_devid output_device,cubeb_stream_params * output_stream_params,unsigned int latency_frames,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)1685 wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
1686 char const * stream_name,
1687 cubeb_devid input_device,
1688 cubeb_stream_params * input_stream_params,
1689 cubeb_devid output_device,
1690 cubeb_stream_params * output_stream_params,
1691 unsigned int latency_frames, cubeb_data_callback data_callback,
1692 cubeb_state_callback state_callback, void * user_ptr)
1693 {
1694 HRESULT hr;
1695 int rv;
1696 auto_com com;
1697 if (!com.ok()) {
1698 return CUBEB_ERROR;
1699 }
1700
1701 XASSERT(context && stream && (input_stream_params || output_stream_params));
1702
1703 if (output_stream_params && output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE ||
1704 input_stream_params && input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE) {
1705 LOG("Invalid format, %p %p %d %d",
1706 output_stream_params, input_stream_params,
1707 output_stream_params && output_stream_params->format,
1708 input_stream_params && input_stream_params->format);
1709 return CUBEB_ERROR_INVALID_FORMAT;
1710 }
1711
1712 cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
1713
1714 XASSERT(stm);
1715
1716 stm->context = context;
1717 stm->data_callback = data_callback;
1718 stm->state_callback = state_callback;
1719 stm->user_ptr = user_ptr;
1720 stm->draining = false;
1721 if (input_stream_params) {
1722 stm->input_stream_params = *input_stream_params;
1723 stm->input_device = input_device;
1724 }
1725 if (output_stream_params) {
1726 stm->output_stream_params = *output_stream_params;
1727 stm->output_device = output_device;
1728 }
1729
1730 stm->latency = latency_frames;
1731 stm->volume = 1.0;
1732
1733 // Placement new to call ctor.
1734 new (&stm->stream_reset_lock) owned_critical_section();
1735
1736 stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
1737 if (!stm->reconfigure_event) {
1738 LOG("Can't create the reconfigure event, error: %x", GetLastError());
1739 wasapi_stream_destroy(stm);
1740 return CUBEB_ERROR;
1741 }
1742
1743 /* Unconditionally create the two events so that the wait logic is simpler. */
1744 stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
1745 if (!stm->refill_event) {
1746 LOG("Can't create the refill event, error: %x", GetLastError());
1747 wasapi_stream_destroy(stm);
1748 return CUBEB_ERROR;
1749 }
1750
1751 stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
1752 if (!stm->input_available_event) {
1753 LOG("Can't create the input available event , error: %x", GetLastError());
1754 wasapi_stream_destroy(stm);
1755 return CUBEB_ERROR;
1756 }
1757
1758
1759 {
1760 /* Locking here is not strictly necessary, because we don't have a
1761 notification client that can reset the stream yet, but it lets us
1762 assert that the lock is held in the function. */
1763 auto_lock lock(stm->stream_reset_lock);
1764 rv = setup_wasapi_stream(stm);
1765 }
1766 if (rv != CUBEB_OK) {
1767 wasapi_stream_destroy(stm);
1768 return rv;
1769 }
1770
1771 hr = register_notification_client(stm);
1772 if (FAILED(hr)) {
1773 /* this is not fatal, we can still play audio, but we won't be able
1774 to keep using the default audio endpoint if it changes. */
1775 LOG("failed to register notification client, %x", hr);
1776 }
1777
1778 *stream = stm;
1779
1780 return CUBEB_OK;
1781 }
1782
close_wasapi_stream(cubeb_stream * stm)1783 void close_wasapi_stream(cubeb_stream * stm)
1784 {
1785 XASSERT(stm);
1786
1787 stm->stream_reset_lock.assert_current_thread_owns();
1788
1789 SafeRelease(stm->output_client);
1790 stm->output_client = NULL;
1791 SafeRelease(stm->input_client);
1792 stm->input_client = NULL;
1793
1794 SafeRelease(stm->render_client);
1795 stm->render_client = NULL;
1796
1797 SafeRelease(stm->capture_client);
1798 stm->capture_client = NULL;
1799
1800 SafeRelease(stm->audio_stream_volume);
1801 stm->audio_stream_volume = NULL;
1802
1803 SafeRelease(stm->audio_clock);
1804 stm->audio_clock = NULL;
1805 stm->total_frames_written += static_cast<UINT64>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
1806 stm->frames_written = 0;
1807
1808 if (stm->resampler) {
1809 cubeb_resampler_destroy(stm->resampler);
1810 stm->resampler = NULL;
1811 }
1812
1813 free(stm->mix_buffer);
1814 stm->mix_buffer = NULL;
1815 }
1816
wasapi_stream_destroy(cubeb_stream * stm)1817 void wasapi_stream_destroy(cubeb_stream * stm)
1818 {
1819 XASSERT(stm);
1820
1821 // Only free stm->emergency_bailout if we could not join the thread.
1822 // If we could not join the thread, stm->emergency_bailout is true
1823 // and is still alive until the thread wakes up and exits cleanly.
1824 if (stop_and_join_render_thread(stm)) {
1825 delete stm->emergency_bailout.load();
1826 stm->emergency_bailout = nullptr;
1827 }
1828
1829 unregister_notification_client(stm);
1830
1831 SafeRelease(stm->reconfigure_event);
1832 SafeRelease(stm->refill_event);
1833 SafeRelease(stm->input_available_event);
1834
1835 {
1836 auto_lock lock(stm->stream_reset_lock);
1837 close_wasapi_stream(stm);
1838 }
1839
1840 // Need to call dtor to free the resource in owned_critical_section.
1841 stm->stream_reset_lock.~owned_critical_section();
1842
1843 free(stm);
1844 }
1845
1846 enum StreamDirection {
1847 OUTPUT,
1848 INPUT
1849 };
1850
stream_start_one_side(cubeb_stream * stm,StreamDirection dir)1851 int stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
1852 {
1853 XASSERT((dir == OUTPUT && stm->output_client) ||
1854 (dir == INPUT && stm->input_client));
1855
1856 HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
1857 if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
1858 LOG("audioclient invalidated for %s device, reconfiguring",
1859 dir == OUTPUT ? "output" : "input");
1860
1861 BOOL ok = ResetEvent(stm->reconfigure_event);
1862 if (!ok) {
1863 LOG("resetting reconfig event failed for %s stream: %x",
1864 dir == OUTPUT ? "output" : "input", GetLastError());
1865 }
1866
1867 close_wasapi_stream(stm);
1868 int r = setup_wasapi_stream(stm);
1869 if (r != CUBEB_OK) {
1870 LOG("reconfigure failed");
1871 return r;
1872 }
1873
1874 HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
1875 if (FAILED(hr2)) {
1876 LOG("could not start the %s stream after reconfig: %x",
1877 dir == OUTPUT ? "output" : "input", hr);
1878 return CUBEB_ERROR;
1879 }
1880 } else if (FAILED(hr)) {
1881 LOG("could not start the %s stream: %x.",
1882 dir == OUTPUT ? "output" : "input", hr);
1883 return CUBEB_ERROR;
1884 }
1885
1886 return CUBEB_OK;
1887 }
1888
wasapi_stream_start(cubeb_stream * stm)1889 int wasapi_stream_start(cubeb_stream * stm)
1890 {
1891 auto_lock lock(stm->stream_reset_lock);
1892
1893 XASSERT(stm && !stm->thread && !stm->shutdown_event);
1894 XASSERT(stm->output_client || stm->input_client);
1895
1896 stm->emergency_bailout = new std::atomic<bool>(false);
1897
1898 if (stm->output_client) {
1899 int rv = stream_start_one_side(stm, OUTPUT);
1900 if (rv != CUBEB_OK) {
1901 return rv;
1902 }
1903 }
1904
1905 if (stm->input_client) {
1906 int rv = stream_start_one_side(stm, INPUT);
1907 if (rv != CUBEB_OK) {
1908 return rv;
1909 }
1910 }
1911
1912 stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
1913 if (!stm->shutdown_event) {
1914 LOG("Can't create the shutdown event, error: %x", GetLastError());
1915 return CUBEB_ERROR;
1916 }
1917
1918 stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
1919 if (stm->thread == NULL) {
1920 LOG("could not create WASAPI render thread.");
1921 return CUBEB_ERROR;
1922 }
1923
1924 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
1925
1926 return CUBEB_OK;
1927 }
1928
wasapi_stream_stop(cubeb_stream * stm)1929 int wasapi_stream_stop(cubeb_stream * stm)
1930 {
1931 XASSERT(stm);
1932 HRESULT hr;
1933
1934 {
1935 auto_lock lock(stm->stream_reset_lock);
1936
1937 if (stm->output_client) {
1938 hr = stm->output_client->Stop();
1939 if (FAILED(hr)) {
1940 LOG("could not stop AudioClient (output)");
1941 return CUBEB_ERROR;
1942 }
1943 }
1944
1945 if (stm->input_client) {
1946 hr = stm->input_client->Stop();
1947 if (FAILED(hr)) {
1948 LOG("could not stop AudioClient (input)");
1949 return CUBEB_ERROR;
1950 }
1951 }
1952
1953
1954 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
1955 }
1956
1957 if (stop_and_join_render_thread(stm)) {
1958 // This is null if we've given the pointer to the other thread
1959 if (stm->emergency_bailout.load()) {
1960 delete stm->emergency_bailout.load();
1961 stm->emergency_bailout = nullptr;
1962 }
1963 }
1964
1965 return CUBEB_OK;
1966 }
1967
wasapi_stream_get_position(cubeb_stream * stm,uint64_t * position)1968 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
1969 {
1970 XASSERT(stm && position);
1971 auto_lock lock(stm->stream_reset_lock);
1972
1973 if (!has_output(stm)) {
1974 return CUBEB_ERROR;
1975 }
1976
1977 /* Calculate how far behind the current stream head the playback cursor is. */
1978 uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) * stm->output_stream_params.rate);
1979
1980 /* Calculate the logical stream head in frames at the stream sample rate. */
1981 uint64_t max_pos = stm->total_frames_written +
1982 static_cast<uint64_t>(round(stm->frames_written * stream_to_mix_samplerate_ratio(stm->output_stream_params, stm->output_mix_params)));
1983
1984 *position = max_pos;
1985 if (stream_delay <= *position) {
1986 *position -= stream_delay;
1987 }
1988
1989 if (*position < stm->prev_position) {
1990 *position = stm->prev_position;
1991 }
1992 stm->prev_position = *position;
1993
1994 return CUBEB_OK;
1995 }
1996
wasapi_stream_get_latency(cubeb_stream * stm,uint32_t * latency)1997 int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
1998 {
1999 XASSERT(stm && latency);
2000
2001 if (!has_output(stm)) {
2002 return CUBEB_ERROR;
2003 }
2004
2005 auto_lock lock(stm->stream_reset_lock);
2006
2007 /* The GetStreamLatency method only works if the
2008 AudioClient has been initialized. */
2009 if (!stm->output_client) {
2010 return CUBEB_ERROR;
2011 }
2012
2013 REFERENCE_TIME latency_hns;
2014 HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
2015 if (FAILED(hr)) {
2016 return CUBEB_ERROR;
2017 }
2018 *latency = hns_to_frames(stm, latency_hns);
2019
2020 return CUBEB_OK;
2021 }
2022
wasapi_stream_set_volume(cubeb_stream * stm,float volume)2023 int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
2024 {
2025 auto_lock lock(stm->stream_reset_lock);
2026
2027 if (!has_output(stm)) {
2028 return CUBEB_ERROR;
2029 }
2030
2031 if (stream_set_volume(stm, volume) != CUBEB_OK) {
2032 return CUBEB_ERROR;
2033 }
2034
2035 stm->volume = volume;
2036
2037 return CUBEB_OK;
2038 }
2039
2040 static char *
wstr_to_utf8(LPCWSTR str)2041 wstr_to_utf8(LPCWSTR str)
2042 {
2043 char * ret = NULL;
2044 int size;
2045
2046 size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL);
2047 if (size > 0) {
2048 ret = static_cast<char *>(malloc(size));
2049 ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
2050 }
2051
2052 return ret;
2053 }
2054
2055 static std::unique_ptr<const wchar_t[]>
utf8_to_wstr(char * str)2056 utf8_to_wstr(char* str)
2057 {
2058 std::unique_ptr<wchar_t[]> ret;
2059 int size;
2060
2061 size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
2062 if (size > 0) {
2063 ret.reset(new wchar_t[size]);
2064 ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
2065 }
2066
2067 return std::move(ret);
2068 }
2069
2070 static IMMDevice *
wasapi_get_device_node(IMMDeviceEnumerator * enumerator,IMMDevice * dev)2071 wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
2072 {
2073 IMMDevice * ret = NULL;
2074 IDeviceTopology * devtopo = NULL;
2075 IConnector * connector = NULL;
2076
2077 if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) &&
2078 SUCCEEDED(devtopo->GetConnector(0, &connector))) {
2079 LPWSTR filterid;
2080 if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) {
2081 if (FAILED(enumerator->GetDevice(filterid, &ret)))
2082 ret = NULL;
2083 CoTaskMemFree(filterid);
2084 }
2085 }
2086
2087 SafeRelease(connector);
2088 SafeRelease(devtopo);
2089 return ret;
2090 }
2091
2092 static BOOL
wasapi_is_default_device(EDataFlow flow,ERole role,LPCWSTR device_id,IMMDeviceEnumerator * enumerator)2093 wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
2094 IMMDeviceEnumerator * enumerator)
2095 {
2096 BOOL ret = FALSE;
2097 IMMDevice * dev;
2098 HRESULT hr;
2099
2100 hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev);
2101 if (SUCCEEDED(hr)) {
2102 LPWSTR defdevid = NULL;
2103 if (SUCCEEDED(dev->GetId(&defdevid)))
2104 ret = (wcscmp(defdevid, device_id) == 0);
2105 if (defdevid != NULL)
2106 CoTaskMemFree(defdevid);
2107 SafeRelease(dev);
2108 }
2109
2110 return ret;
2111 }
2112
2113 static cubeb_device_info *
wasapi_create_device(IMMDeviceEnumerator * enumerator,IMMDevice * dev)2114 wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
2115 {
2116 IMMEndpoint * endpoint = NULL;
2117 IMMDevice * devnode = NULL;
2118 IAudioClient * client = NULL;
2119 cubeb_device_info * ret = NULL;
2120 EDataFlow flow;
2121 LPWSTR device_id = NULL;
2122 DWORD state = DEVICE_STATE_NOTPRESENT;
2123 IPropertyStore * propstore = NULL;
2124 PROPVARIANT propvar;
2125 REFERENCE_TIME def_period, min_period;
2126 HRESULT hr;
2127
2128 PropVariantInit(&propvar);
2129
2130 hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint));
2131 if (FAILED(hr)) goto done;
2132
2133 hr = endpoint->GetDataFlow(&flow);
2134 if (FAILED(hr)) goto done;
2135
2136 hr = dev->GetId(&device_id);
2137 if (FAILED(hr)) goto done;
2138
2139 hr = dev->OpenPropertyStore(STGM_READ, &propstore);
2140 if (FAILED(hr)) goto done;
2141
2142 hr = dev->GetState(&state);
2143 if (FAILED(hr)) goto done;
2144
2145 ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
2146
2147 ret->devid = ret->device_id = wstr_to_utf8(device_id);
2148 hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar);
2149 if (SUCCEEDED(hr))
2150 ret->friendly_name = wstr_to_utf8(propvar.pwszVal);
2151
2152 devnode = wasapi_get_device_node(enumerator, dev);
2153 if (devnode != NULL) {
2154 IPropertyStore * ps = NULL;
2155 hr = devnode->OpenPropertyStore(STGM_READ, &ps);
2156 if (FAILED(hr)) goto done;
2157
2158 PropVariantClear(&propvar);
2159 hr = ps->GetValue(PKEY_Device_InstanceId, &propvar);
2160 if (SUCCEEDED(hr)) {
2161 ret->group_id = wstr_to_utf8(propvar.pwszVal);
2162 }
2163 SafeRelease(ps);
2164 }
2165
2166 ret->preferred = CUBEB_DEVICE_PREF_NONE;
2167 if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
2168 ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
2169 if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator))
2170 ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE);
2171 if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
2172 ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
2173
2174 if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
2175 else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT;
2176 switch (state) {
2177 case DEVICE_STATE_ACTIVE:
2178 ret->state = CUBEB_DEVICE_STATE_ENABLED;
2179 break;
2180 case DEVICE_STATE_UNPLUGGED:
2181 ret->state = CUBEB_DEVICE_STATE_UNPLUGGED;
2182 break;
2183 default:
2184 ret->state = CUBEB_DEVICE_STATE_DISABLED;
2185 break;
2186 };
2187
2188 ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */
2189 ret->default_format = CUBEB_DEVICE_FMT_F32NE;
2190 PropVariantClear(&propvar);
2191 hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar);
2192 if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) {
2193 if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
2194 const PCMWAVEFORMAT * pcm = reinterpret_cast<const PCMWAVEFORMAT *>(propvar.blob.pBlobData);
2195
2196 ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec;
2197 ret->max_channels = pcm->wf.nChannels;
2198 } else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
2199 WAVEFORMATEX* wfx = reinterpret_cast<WAVEFORMATEX*>(propvar.blob.pBlobData);
2200
2201 if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
2202 wfx->wFormatTag == WAVE_FORMAT_PCM) {
2203 ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec;
2204 ret->max_channels = wfx->nChannels;
2205 }
2206 }
2207 }
2208
2209 if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) &&
2210 SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
2211 ret->latency_lo = hns_to_frames(ret->default_rate, min_period);
2212 ret->latency_hi = hns_to_frames(ret->default_rate, def_period);
2213 } else {
2214 ret->latency_lo = 0;
2215 ret->latency_hi = 0;
2216 }
2217 SafeRelease(client);
2218
2219 done:
2220 SafeRelease(devnode);
2221 SafeRelease(endpoint);
2222 SafeRelease(propstore);
2223 if (device_id != NULL)
2224 CoTaskMemFree(device_id);
2225 PropVariantClear(&propvar);
2226 return ret;
2227 }
2228
2229 static int
wasapi_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection ** out)2230 wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
2231 cubeb_device_collection ** out)
2232 {
2233 auto_com com;
2234 IMMDeviceEnumerator * enumerator;
2235 IMMDeviceCollection * collection;
2236 IMMDevice * dev;
2237 cubeb_device_info * cur;
2238 HRESULT hr;
2239 UINT cc, i;
2240 EDataFlow flow;
2241
2242 *out = NULL;
2243
2244 if (!com.ok())
2245 return CUBEB_ERROR;
2246
2247 hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
2248 CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
2249 if (FAILED(hr)) {
2250 LOG("Could not get device enumerator: %x", hr);
2251 return CUBEB_ERROR;
2252 }
2253
2254 if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender;
2255 else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture;
2256 else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll;
2257 else return CUBEB_ERROR;
2258
2259 hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection);
2260 if (FAILED(hr)) {
2261 LOG("Could not enumerate audio endpoints: %x", hr);
2262 return CUBEB_ERROR;
2263 }
2264
2265 hr = collection->GetCount(&cc);
2266 if (FAILED(hr)) {
2267 LOG("IMMDeviceCollection::GetCount() failed: %x", hr);
2268 return CUBEB_ERROR;
2269 }
2270 *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) +
2271 sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0));
2272 if (!*out) {
2273 return CUBEB_ERROR;
2274 }
2275 (*out)->count = 0;
2276 for (i = 0; i < cc; i++) {
2277 hr = collection->Item(i, &dev);
2278 if (FAILED(hr)) {
2279 LOG("IMMDeviceCollection::Item(%u) failed: %x", i-1, hr);
2280 } else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) {
2281 (*out)->device[(*out)->count++] = cur;
2282 }
2283 }
2284
2285 SafeRelease(collection);
2286 SafeRelease(enumerator);
2287 return CUBEB_OK;
2288 }
2289
2290 cubeb_ops const wasapi_ops = {
2291 /*.init =*/ wasapi_init,
2292 /*.get_backend_id =*/ wasapi_get_backend_id,
2293 /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
2294 /*.get_min_latency =*/ wasapi_get_min_latency,
2295 /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
2296 /*.enumerate_devices =*/ wasapi_enumerate_devices,
2297 /*.destroy =*/ wasapi_destroy,
2298 /*.stream_init =*/ wasapi_stream_init,
2299 /*.stream_destroy =*/ wasapi_stream_destroy,
2300 /*.stream_start =*/ wasapi_stream_start,
2301 /*.stream_stop =*/ wasapi_stream_stop,
2302 /*.stream_get_position =*/ wasapi_stream_get_position,
2303 /*.stream_get_latency =*/ wasapi_stream_get_latency,
2304 /*.stream_set_volume =*/ wasapi_stream_set_volume,
2305 /*.stream_set_panning =*/ NULL,
2306 /*.stream_get_current_device =*/ NULL,
2307 /*.stream_device_destroy =*/ NULL,
2308 /*.stream_register_device_changed_callback =*/ NULL,
2309 /*.register_device_collection_changed =*/ NULL
2310 };
2311 } // namespace anonymous
2312