Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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 #undef NDEBUG
8 #if defined(HAVE_CONFIG_H)
9 #include "config.h"
10 #endif
11 #include <assert.h>
12 #include <windows.h>
13 #include <mmdeviceapi.h>
14 #include <windef.h>
15 #include <audioclient.h>
16 #include <math.h>
17 #include <process.h>
18 #include <avrt.h>
19 #include "cubeb/cubeb.h"
20 #include "cubeb-internal.h"
21 #include "cubeb/cubeb-stdint.h"
22 #include "cubeb-speex-resampler.h"
23 #include <stdio.h>
25 #if 0
26 # define LOG(...) do { \
27 fprintf(stderr, __VA_ARGS__); \
28 fprintf(stderr, "\n"); \
29 } while(0);
30 #else
31 # define LOG(...)
32 #endif
34 #define ARRAY_LENGTH(array_) \
35 (sizeof(array_) / sizeof(array_[0]))
37 namespace {
38 uint32_t
39 ms_to_hns(uint32_t ms)
40 {
41 return ms * 10000;
42 }
44 uint32_t
45 hns_to_ms(uint32_t hns)
46 {
47 return hns / 10000;
48 }
50 double
51 hns_to_s(uint32_t hns)
52 {
53 return static_cast<double>(hns) / 10000000;
54 }
56 long
57 frame_count_at_rate(long frame_count, float rate)
58 {
59 return static_cast<long>(ceilf(rate * frame_count) + 1);
60 }
62 void
63 SafeRelease(HANDLE handle)
64 {
65 if (handle) {
66 CloseHandle(handle);
67 }
68 }
70 template <typename T>
71 void SafeRelease(T * ptr)
72 {
73 if (ptr) {
74 ptr->Release();
75 }
76 }
78 typedef void (*refill_function2)(cubeb_stream * stm,
79 float * data, long frames_needed);
81 typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
82 const char* TaskName, LPDWORD TaskIndex);
83 typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
85 extern cubeb_ops const wasapi_ops;
86 }
88 struct cubeb
89 {
90 cubeb_ops const * ops;
91 /* Library dynamically opened to increase the render
92 * thread priority, and the two function pointers we need. */
93 HMODULE mmcss_module;
94 set_mm_thread_characteristics_function set_mm_thread_characteristics;
95 revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
96 };
98 struct cubeb_stream
99 {
100 cubeb * context;
101 /* Mixer pameters. We need to convert the input
102 * stream to this samplerate/channel layout, as WASAPI
103 * does not resample nor upmix itself. */
104 cubeb_stream_params mix_params;
105 cubeb_stream_params stream_params;
106 cubeb_state_callback state_callback;
107 cubeb_data_callback data_callback;
108 void * user_ptr;
109 /* Main handle on the WASAPI stream. */
110 IAudioClient * client;
111 /* Interface pointer to use the event-driven interface. */
112 IAudioRenderClient * render_client;
113 /* Interface pointer to use the clock facilities. */
114 IAudioClock * audio_clock;
115 /* This event is set by the stream_stop and stream_destroy
116 * function, so the render loop can exit properly. */
117 HANDLE shutdown_event;
118 /* This is set by WASAPI when we should refill the stream. */
119 HANDLE refill_event;
120 /* Each cubeb_stream has its own thread. */
121 HANDLE thread;
122 uint64_t clock_freq;
123 /* Maximum number of frames we can be requested in a callback. */
124 uint32_t buffer_frame_count;
125 /* Resampler instance. If this is !NULL, resampling should happen. */
126 SpeexResamplerState * resampler;
127 /* Buffer to resample from, into the mix buffer or the final buffer. */
128 float * resampling_src_buffer;
129 /* Pointer to the function used to refill the buffer, depending
130 * on the respective samplerate of the stream and the mix. */
131 refill_function2 refill_function;
132 /* Leftover frames handling, only used when resampling. */
133 uint32_t leftover_frame_count;
134 uint32_t leftover_frame_size;
135 float * leftover_frames_buffer;
136 /* Buffer used to downmix or upmix to the number of channels the mixer has.
137 * its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */
138 float * mix_buffer;
139 /* True if the stream is draining. */
140 bool draining;
141 };
143 namespace {
144 bool should_upmix(cubeb_stream * stream)
145 {
146 return stream->mix_params.channels > stream->stream_params.channels;
147 }
149 bool should_downmix(cubeb_stream * stream)
150 {
151 return stream->mix_params.channels < stream->stream_params.channels;
152 }
154 /* Upmix function, copies a mono channel in two interleaved
155 * stereo channel. |out| has to be twice as long as |in| */
156 template<typename T>
157 void
158 mono_to_stereo(T * in, long insamples, T * out)
159 {
160 int j = 0;
161 for (int i = 0; i < insamples; ++i, j += 2) {
162 out[j] = out[j + 1] = in[i];
163 }
164 }
166 template<typename T>
167 void
168 upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
169 {
170 assert(out_channels >= in_channels);
171 /* If we are playing a mono stream over stereo speakers, copy the data over. */
172 if (in_channels == 1 && out_channels == 2) {
173 mono_to_stereo(in, inframes, out);
174 return;
175 }
176 /* Otherwise, put silence in other channels. */
177 long out_index = 0;
178 for (long i = 0; i < inframes * in_channels; i += in_channels) {
179 for (int j = 0; j < in_channels; ++j) {
180 out[out_index + j] = in[i + j];
181 }
182 for (int j = in_channels; j < out_channels; ++j) {
183 out[out_index + j] = 0.0;
184 }
185 out_index += out_channels;
186 }
187 }
189 template<typename T>
190 void
191 downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
192 {
193 assert(in_channels >= out_channels);
194 /* We could use a downmix matrix here, applying mixing weight based on the
195 * channel, but directsound and winmm simply drop the channels that cannot be
196 * rendered by the hardware, so we do the same for consistency. */
197 long out_index = 0;
198 for (long i = 0; i < inframes * in_channels; i += in_channels) {
199 for (int j = 0; j < out_channels; ++j) {
200 out[out_index + j] = in[i + j];
201 }
202 out_index += out_channels;
203 }
204 }
206 /* This returns the size of a frame in the stream,
207 * before the eventual upmix occurs. */
208 static size_t
209 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
210 {
211 size_t stream_frame_size = stm->stream_params.channels * sizeof(float);
212 return stream_frame_size * frames;
213 }
215 void
216 refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
217 {
218 /* Use more input frames that strictly necessary, so in the worst case,
219 * we have leftover unresampled frames at the end, that we can use
220 * during the next iteration. */
221 float rate =
222 static_cast<float>(stm->stream_params.rate) / stm->mix_params.rate;
224 long before_resampling = frame_count_at_rate(frames_needed, rate);
226 long frame_requested = before_resampling - stm->leftover_frame_count;
228 size_t leftover_bytes =
229 frames_to_bytes_before_mix(stm, stm->leftover_frame_count);
231 /* Copy the previous leftover frames to the front of the buffer. */
232 memcpy(stm->resampling_src_buffer, stm->leftover_frames_buffer, leftover_bytes);
233 uint8_t * buffer_start = reinterpret_cast<uint8_t *>(
234 stm->resampling_src_buffer) + leftover_bytes;
236 long got = stm->data_callback(stm, stm->user_ptr, buffer_start, frame_requested);
238 if (got != frame_requested) {
239 stm->draining = true;
240 }
242 uint32_t in_frames = before_resampling;
243 uint32_t out_frames = frames_needed;
245 /* If we need to upmix after resampling, resample into the mix buffer to
246 * avoid a copy. */
247 float * resample_dest;
248 if (should_upmix(stm) || should_downmix(stm)) {
249 resample_dest = stm->mix_buffer;
250 } else {
251 resample_dest = data;
252 }
254 speex_resampler_process_interleaved_float(stm->resampler,
255 stm->resampling_src_buffer,
256 &in_frames,
257 resample_dest,
258 &out_frames);
260 /* Copy the leftover frames to buffer for the next time. */
261 stm->leftover_frame_count = before_resampling - in_frames;
262 size_t unresampled_bytes =
263 frames_to_bytes_before_mix(stm, stm->leftover_frame_count);
265 uint8_t * leftover_frames_start =
266 reinterpret_cast<uint8_t *>(stm->resampling_src_buffer);
267 leftover_frames_start += frames_to_bytes_before_mix(stm, in_frames);
269 assert(stm->leftover_frame_count <= stm->leftover_frame_size);
270 memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes);
272 /* If this is not true, there will be glitches.
273 * It is alright to have produced less frames if we are draining, though. */
274 assert(out_frames == frames_needed || stm->draining);
276 if (should_upmix(stm)) {
277 upmix(resample_dest, out_frames, data,
278 stm->stream_params.channels, stm->mix_params.channels);
279 } else if (should_downmix(stm)) {
280 downmix(resample_dest, out_frames, data,
281 stm->stream_params.channels, stm->mix_params.channels);
282 }
283 }
285 void
286 refill(cubeb_stream * stm, float * data, long frames_needed)
287 {
288 /* If we need to upmix/downmix, get the data into the mix buffer to avoid a
289 * copy, then do the processing process. */
290 float * dest;
291 if (should_upmix(stm) || should_downmix(stm)) {
292 dest = stm->mix_buffer;
293 } else {
294 dest = data;
295 }
297 long got = stm->data_callback(stm, stm->user_ptr, dest, frames_needed);
298 assert(got <= frames_needed);
299 if (got != frames_needed) {
300 LOG("draining.");
301 stm->draining = true;
302 }
304 if (should_upmix(stm)) {
305 upmix(dest, got, data,
306 stm->stream_params.channels, stm->mix_params.channels);
307 } else if (should_downmix(stm)) {
308 downmix(dest, got, data,
309 stm->stream_params.channels, stm->mix_params.channels);
310 }
311 }
313 static unsigned int __stdcall
314 wasapi_stream_render_loop(LPVOID stream)
315 {
316 cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
318 bool is_playing = true;
319 HANDLE wait_array[2] = {stm->shutdown_event, stm->refill_event};
320 HANDLE mmcss_handle = NULL;
321 HRESULT hr;
322 bool first = true;
323 DWORD mmcss_task_index = 0;
325 /* We could consider using "Pro Audio" here for WebAudio and
326 * maybe WebRTC. */
327 mmcss_handle =
328 stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
329 if (!mmcss_handle) {
330 /* This is not fatal, but we might glitch under heavy load. */
331 LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError());
332 }
334 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
335 if (FAILED(hr)) {
336 LOG("could not initialize COM in render thread: %x", hr);
337 return hr;
338 }
340 while (is_playing) {
341 DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
342 wait_array,
343 FALSE,
344 INFINITE);
346 switch (waitResult) {
347 case WAIT_OBJECT_0: { /* shutdown */
348 is_playing = false;
349 /* We don't check if the drain is actually finished here, we just want to
350 * shutdown. */
351 if (stm->draining) {
352 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
353 }
354 continue;
355 }
356 case WAIT_OBJECT_0 + 1: { /* refill */
357 UINT32 padding;
359 hr = stm->client->GetCurrentPadding(&padding);
360 if (FAILED(hr)) {
361 LOG("Failed to get padding");
362 is_playing = false;
363 continue;
364 }
365 assert(padding <= stm->buffer_frame_count);
367 if (stm->draining) {
368 if (padding == 0) {
369 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
370 is_playing = false;
371 }
372 continue;
373 }
375 long available = stm->buffer_frame_count - padding;
377 if (available == 0) {
378 continue;
379 }
381 BYTE* data;
382 hr = stm->render_client->GetBuffer(available, &data);
383 if (SUCCEEDED(hr)) {
384 stm->refill_function(stm, reinterpret_cast<float *>(data), available);
386 hr = stm->render_client->ReleaseBuffer(available, 0);
387 if (FAILED(hr)) {
388 LOG("failed to release buffer.");
389 is_playing = false;
390 }
391 } else {
392 LOG("failed to get buffer.");
393 is_playing = false;
394 }
395 }
396 break;
397 default:
398 LOG("case %d not handled in render loop.", waitResult);
399 abort();
400 }
401 }
403 if (FAILED(hr)) {
404 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
405 }
407 stm->context->revert_mm_thread_characteristics(mmcss_handle);
409 CoUninitialize();
410 return 0;
411 }
413 void wasapi_destroy(cubeb * context);
415 HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index)
416 {
417 return (HANDLE)1;
418 }
420 BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)
421 {
422 return true;
423 }
425 HRESULT get_default_endpoint(IMMDevice ** device)
426 {
427 IMMDeviceEnumerator * enumerator;
428 HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
429 NULL, CLSCTX_INPROC_SERVER,
430 IID_PPV_ARGS(&enumerator));
431 if (FAILED(hr)) {
432 LOG("Could not get device enumerator.");
433 return hr;
434 }
435 /* eMultimedia is okay for now ("Music, movies, narration, [...]").
436 * We will need to change this when we distinguish streams by use-case, other
437 * possible values being eConsole ("Games, system notification sounds [...]")
438 * and eCommunication ("Voice communication"). */
439 hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device);
440 if (FAILED(hr)) {
441 LOG("Could not get default audio endpoint.");
442 SafeRelease(enumerator);
443 return hr;
444 }
446 SafeRelease(enumerator);
448 return ERROR_SUCCESS;
449 }
450 } // namespace anonymous
452 extern "C" {
453 int wasapi_init(cubeb ** context, char const * context_name)
454 {
455 HRESULT hr;
457 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
458 if (FAILED(hr)) {
459 LOG("Could not init COM.");
460 return CUBEB_ERROR;
461 }
463 /* We don't use the device yet, but need to make sure we can initialize one
464 so that this backend is not incorrectly enabled on platforms that don't
465 support WASAPI. */
466 IMMDevice * device;
467 hr = get_default_endpoint(&device);
468 if (FAILED(hr)) {
469 LOG("Could not get device.");
470 return CUBEB_ERROR;
471 }
472 SafeRelease(device);
474 cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
476 ctx->ops = &wasapi_ops;
478 ctx->mmcss_module = LoadLibraryA("Avrt.dll");
480 if (ctx->mmcss_module) {
481 ctx->set_mm_thread_characteristics =
482 (set_mm_thread_characteristics_function) GetProcAddress(
483 ctx->mmcss_module, "AvSetMmThreadCharacteristicsA");
484 ctx->revert_mm_thread_characteristics =
485 (revert_mm_thread_characteristics_function) GetProcAddress(
486 ctx->mmcss_module, "AvRevertMmThreadCharacteristics");
487 if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) {
488 LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError());
489 FreeLibrary(ctx->mmcss_module);
490 }
491 } else {
492 // This is not a fatal error, but we might end up glitching when
493 // the system is under high load.
494 LOG("Could not load Avrt.dll");
495 ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop;
496 ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop;
497 }
499 *context = ctx;
501 return CUBEB_OK;
502 }
503 }
505 namespace {
507 void wasapi_destroy(cubeb * context)
508 {
509 if (context->mmcss_module) {
510 FreeLibrary(context->mmcss_module);
511 }
512 free(context);
513 }
515 char const* wasapi_get_backend_id(cubeb * context)
516 {
517 return "wasapi";
518 }
520 int
521 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
522 {
523 IAudioClient * client;
524 WAVEFORMATEX * mix_format;
526 assert(ctx && max_channels);
528 IMMDevice * device;
529 HRESULT hr = get_default_endpoint(&device);
530 if (FAILED(hr)) {
531 return CUBEB_ERROR;
532 }
534 hr = device->Activate(__uuidof(IAudioClient),
535 CLSCTX_INPROC_SERVER,
536 NULL, (void **)&client);
537 SafeRelease(device);
538 if (FAILED(hr)) {
539 return CUBEB_ERROR;
540 }
542 hr = client->GetMixFormat(&mix_format);
543 if (FAILED(hr)) {
544 SafeRelease(client);
545 return CUBEB_ERROR;
546 }
548 *max_channels = mix_format->nChannels;
550 CoTaskMemFree(mix_format);
551 SafeRelease(client);
553 return CUBEB_OK;
554 }
556 int
557 wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
558 {
559 HRESULT hr;
560 IAudioClient * client;
561 REFERENCE_TIME default_period;
563 IMMDevice * device;
564 hr = get_default_endpoint(&device);
565 if (FAILED(hr)) {
566 return CUBEB_ERROR;
567 }
569 hr = device->Activate(__uuidof(IAudioClient),
570 CLSCTX_INPROC_SERVER,
571 NULL, (void **)&client);
572 SafeRelease(device);
573 if (FAILED(hr)) {
574 return CUBEB_ERROR;
575 }
577 /* The second parameter is for exclusive mode, that we don't use. */
578 hr = client->GetDevicePeriod(&default_period, NULL);
579 if (FAILED(hr)) {
580 SafeRelease(client);
581 return CUBEB_ERROR;
582 }
584 /* According to the docs, the best latency we can achieve is by synchronizing
585 * the stream and the engine.
586 * http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
587 *latency_ms = hns_to_ms(default_period);
589 SafeRelease(client);
591 return CUBEB_OK;
592 }
594 int
595 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
596 {
597 HRESULT hr;
598 IAudioClient * client;
599 WAVEFORMATEX * mix_format;
601 IMMDevice * device;
602 hr = get_default_endpoint(&device);
603 if (FAILED(hr)) {
604 return CUBEB_ERROR;
605 }
607 hr = device->Activate(__uuidof(IAudioClient),
608 CLSCTX_INPROC_SERVER,
609 NULL, (void **)&client);
610 SafeRelease(device);
611 if (FAILED(hr)) {
612 return CUBEB_ERROR;
613 }
615 hr = client->GetMixFormat(&mix_format);
616 if (FAILED(hr)) {
617 SafeRelease(client);
618 return CUBEB_ERROR;
619 }
621 *rate = mix_format->nSamplesPerSec;
623 CoTaskMemFree(mix_format);
624 SafeRelease(client);
626 return CUBEB_OK;
627 }
629 void wasapi_stream_destroy(cubeb_stream * stm);
631 /* Based on the mix format and the stream format, try to find a way to play what
632 * the user requested. */
633 static void
634 handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
635 {
636 /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
637 * handled in the callback. */
638 if ((*mix_format)->nChannels <= 2) {
639 return;
640 }
642 /* Otherwise, the hardware supports more than two channels. */
643 WAVEFORMATEX hw_mixformat = **mix_format;
645 /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
646 * so the reinterpret_cast below should be safe. In practice, this is not
647 * true, and we just want to bail out and let the rest of the code find a good
648 * conversion path instead of trying to make WASAPI do it by itself.
649 * [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
650 if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
651 return;
652 }
654 /* The hardware is in surround mode, we want to only use front left and front
655 * right. Try that, and check if it works. */
656 WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>((*mix_format));
657 switch (stream_params->channels) {
658 case 1: /* Mono */
659 format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
660 break;
661 case 2: /* Stereo */
662 format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
663 break;
664 default:
665 assert(false && "Channel layout not supported.");
666 break;
667 }
668 (*mix_format)->nChannels = stream_params->channels;
669 (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
670 (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
671 format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
672 (*mix_format)->wBitsPerSample = 32;
673 format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
675 /* Check if wasapi will accept our channel layout request. */
676 WAVEFORMATEX * closest;
677 HRESULT hr = stm->client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
678 *mix_format,
679 &closest);
681 if (hr == S_FALSE) {
682 /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
683 * eventual upmix/downmix ourselves */
684 LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
685 WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
686 assert(closest_pcm->SubFormat == format_pcm->SubFormat);
687 CoTaskMemFree(*mix_format);
688 *mix_format = closest;
689 } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
690 /* Not supported, no suggestion. This should not happen, but it does in the
691 * field with some sound cards. We restore the mix format, and let the rest
692 * of the code figure out the right conversion path. */
693 **mix_format = hw_mixformat;
694 } else if (hr == S_OK) {
695 LOG("Requested format accepted by WASAPI.");
696 }
697 }
699 int
700 wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
701 char const * stream_name, cubeb_stream_params stream_params,
702 unsigned int latency, cubeb_data_callback data_callback,
703 cubeb_state_callback state_callback, void * user_ptr)
704 {
705 HRESULT hr;
706 WAVEFORMATEX * mix_format;
708 assert(context && stream);
710 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
711 if (FAILED(hr)) {
712 LOG("Could not initialize COM.");
713 return CUBEB_ERROR;
714 }
716 cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
718 assert(stm);
720 stm->context = context;
721 stm->data_callback = data_callback;
722 stm->state_callback = state_callback;
723 stm->user_ptr = user_ptr;
724 stm->stream_params = stream_params;
725 stm->draining = false;
727 stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
728 stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
730 if (!stm->shutdown_event) {
731 LOG("Can't create the shutdown event, error: %x.", GetLastError());
732 wasapi_stream_destroy(stm);
733 return CUBEB_ERROR;
734 }
736 if (!stm->refill_event) {
737 SafeRelease(stm->shutdown_event);
738 LOG("Can't create the refill event, error: %x.", GetLastError());
739 wasapi_stream_destroy(stm);
740 return CUBEB_ERROR;
741 }
743 IMMDevice * device;
744 hr = get_default_endpoint(&device);
745 if (FAILED(hr)) {
746 LOG("Could not get default endpoint, error: %x", hr);
747 wasapi_stream_destroy(stm);
748 return CUBEB_ERROR;
749 }
751 /* Get a client. We will get all other interfaces we need from
752 * this pointer. */
753 hr = device->Activate(__uuidof(IAudioClient),
754 CLSCTX_INPROC_SERVER,
755 NULL, (void **)&stm->client);
756 SafeRelease(device);
757 if (FAILED(hr)) {
758 LOG("Could not activate the device to get an audio client: error: %x", hr);
759 wasapi_stream_destroy(stm);
760 return CUBEB_ERROR;
761 }
763 /* We have to distinguish between the format the mixer uses,
764 * and the format the stream we want to play uses. */
765 hr = stm->client->GetMixFormat(&mix_format);
766 if (FAILED(hr)) {
767 LOG("Could not fetch current mix format from the audio client: error: %x", hr);
768 wasapi_stream_destroy(stm);
769 return CUBEB_ERROR;
770 }
772 handle_channel_layout(stm, &mix_format, &stream_params);
774 /* Shared mode WASAPI always supports float32 sample format, so this
775 * is safe. */
776 stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
778 stm->mix_params.rate = mix_format->nSamplesPerSec;
779 stm->mix_params.channels = mix_format->nChannels;
781 float resampling_rate = static_cast<float>(stm->stream_params.rate) /
782 stm->mix_params.rate;
784 if (resampling_rate != 1.0) {
785 /* If we are playing a mono stream, we only resample one channel,
786 * and copy it over, so we are always resampling the number
787 * of channels of the stream, not the number of channels
788 * that WASAPI wants. */
789 stm->resampler = speex_resampler_init(stm->stream_params.channels,
790 stm->stream_params.rate,
791 stm->mix_params.rate,
792 SPEEX_RESAMPLER_QUALITY_DESKTOP,
793 NULL);
794 if (!stm->resampler) {
795 LOG("Could not get a resampler");
796 CoTaskMemFree(mix_format);
797 wasapi_stream_destroy(stm);
798 return CUBEB_ERROR;
799 }
801 /* Get a little buffer so we can store the leftover frames,
802 * that is, the samples not consumed by the resampler that we will end up
803 * using next time the render callback is called. */
804 stm->leftover_frame_size = static_cast<uint32_t>(ceilf(1 / resampling_rate * 2) + 1);
805 stm->leftover_frames_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, stm->leftover_frame_size));
807 stm->refill_function = &refill_with_resampling;
808 } else {
809 stm->refill_function = &refill;
810 }
812 hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED,
813 AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
814 AUDCLNT_STREAMFLAGS_NOPERSIST,
815 ms_to_hns(latency),
816 0,
817 mix_format,
818 NULL);
820 CoTaskMemFree(mix_format);
822 if (FAILED(hr)) {
823 LOG("Unable to initialize audio client: %x.", hr);
824 wasapi_stream_destroy(stm);
825 return CUBEB_ERROR;
826 }
828 hr = stm->client->GetBufferSize(&stm->buffer_frame_count);
829 if (FAILED(hr)) {
830 LOG("Could not get the buffer size from the client %x.", hr);
831 wasapi_stream_destroy(stm);
832 return CUBEB_ERROR;
833 }
835 if (should_upmix(stm) || should_downmix(stm)) {
836 stm->mix_buffer = (float *) malloc(frames_to_bytes_before_mix(stm, stm->buffer_frame_count));
837 }
839 /* If we are going to resample, we will end up needing a buffer
840 * to resample from, because speex's resampler does not do
841 * in-place processing. Of course we need to take the resampling
842 * factor and the channel layout into account. */
843 if (stm->resampler) {
844 size_t frames_needed = static_cast<size_t>(frame_count_at_rate(stm->buffer_frame_count, resampling_rate));
845 stm->resampling_src_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, frames_needed));
846 }
848 hr = stm->client->SetEventHandle(stm->refill_event);
849 if (FAILED(hr)) {
850 LOG("Could set the event handle for the client %x.", hr);
851 wasapi_stream_destroy(stm);
852 return CUBEB_ERROR;
853 }
855 hr = stm->client->GetService(__uuidof(IAudioRenderClient),
856 (void **)&stm->render_client);
857 if (FAILED(hr)) {
858 LOG("Could not get the render client %x.", hr);
859 wasapi_stream_destroy(stm);
860 return CUBEB_ERROR;
861 }
863 hr = stm->client->GetService(__uuidof(IAudioClock),
864 (void **)&stm->audio_clock);
865 if (FAILED(hr)) {
866 LOG("Could not get the IAudioClock, %x", hr);
867 wasapi_stream_destroy(stm);
868 return CUBEB_ERROR;
869 }
871 hr = stm->audio_clock->GetFrequency(&stm->clock_freq);
872 if (FAILED(hr)) {
873 LOG("failed to get audio clock frequency, %x", hr);
874 return CUBEB_ERROR;
875 }
877 *stream = stm;
879 return CUBEB_OK;
880 }
882 void wasapi_stream_destroy(cubeb_stream * stm)
883 {
884 assert(stm);
886 if (stm->thread) {
887 SetEvent(stm->shutdown_event);
888 WaitForSingleObject(stm->thread, INFINITE);
889 CloseHandle(stm->thread);
890 stm->thread = 0;
891 }
893 SafeRelease(stm->shutdown_event);
894 SafeRelease(stm->refill_event);
896 SafeRelease(stm->client);
897 SafeRelease(stm->render_client);
898 SafeRelease(stm->audio_clock);
900 if (stm->resampler) {
901 speex_resampler_destroy(stm->resampler);
902 }
904 free(stm->leftover_frames_buffer);
905 free(stm->resampling_src_buffer);
906 free(stm->mix_buffer);
907 free(stm);
908 CoUninitialize();
909 }
911 int wasapi_stream_start(cubeb_stream * stm)
912 {
913 HRESULT hr;
915 assert(stm);
917 stm->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
918 if (stm->thread == NULL) {
919 LOG("could not create WASAPI render thread.");
920 return CUBEB_ERROR;
921 }
923 hr = stm->client->Start();
924 if (FAILED(hr)) {
925 LOG("could not start the stream.");
926 return CUBEB_ERROR;
927 }
929 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
931 return CUBEB_OK;
932 }
934 int wasapi_stream_stop(cubeb_stream * stm)
935 {
936 assert(stm && stm->shutdown_event);
938 SetEvent(stm->shutdown_event);
940 HRESULT hr = stm->client->Stop();
941 if (FAILED(hr)) {
942 LOG("could not stop AudioClient");
943 }
945 if (stm->thread) {
946 WaitForSingleObject(stm->thread, INFINITE);
947 CloseHandle(stm->thread);
948 stm->thread = NULL;
949 }
951 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
953 return CUBEB_OK;
954 }
956 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
957 {
958 assert(stm && position);
960 UINT64 pos;
961 HRESULT hr;
963 hr = stm->audio_clock->GetPosition(&pos, NULL);
964 if (FAILED(hr)) {
965 LOG("Could not get accurate position: %x\n", hr);
966 return CUBEB_ERROR;
967 }
969 *position = static_cast<uint64_t>(static_cast<double>(pos) / stm->clock_freq * stm->stream_params.rate);
971 return CUBEB_OK;
972 }
974 int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
975 {
976 assert(stm && latency);
978 /* The GetStreamLatency method only works if the
979 * AudioClient has been initialized. */
980 if (!stm->client) {
981 return CUBEB_ERROR;
982 }
984 REFERENCE_TIME latency_hns;
985 stm->client->GetStreamLatency(&latency_hns);
986 double latency_s = hns_to_s(latency_hns);
987 *latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate);
989 return CUBEB_OK;
990 }
992 cubeb_ops const wasapi_ops = {
993 /*.init =*/ wasapi_init,
994 /*.get_backend_id =*/ wasapi_get_backend_id,
995 /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
996 /*.get_min_latency =*/ wasapi_get_min_latency,
997 /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
998 /*.destroy =*/ wasapi_destroy,
999 /*.stream_init =*/ wasapi_stream_init,
1000 /*.stream_destroy =*/ wasapi_stream_destroy,
1001 /*.stream_start =*/ wasapi_stream_start,
1002 /*.stream_stop =*/ wasapi_stream_stop,
1003 /*.stream_get_position =*/ wasapi_stream_get_position,
1004 /*.stream_get_latency =*/ wasapi_stream_get_latency
1005 };
1006 } // namespace anonymous