|
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> |
|
24 |
|
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 |
|
33 |
|
34 #define ARRAY_LENGTH(array_) \ |
|
35 (sizeof(array_) / sizeof(array_[0])) |
|
36 |
|
37 namespace { |
|
38 uint32_t |
|
39 ms_to_hns(uint32_t ms) |
|
40 { |
|
41 return ms * 10000; |
|
42 } |
|
43 |
|
44 uint32_t |
|
45 hns_to_ms(uint32_t hns) |
|
46 { |
|
47 return hns / 10000; |
|
48 } |
|
49 |
|
50 double |
|
51 hns_to_s(uint32_t hns) |
|
52 { |
|
53 return static_cast<double>(hns) / 10000000; |
|
54 } |
|
55 |
|
56 long |
|
57 frame_count_at_rate(long frame_count, float rate) |
|
58 { |
|
59 return static_cast<long>(ceilf(rate * frame_count) + 1); |
|
60 } |
|
61 |
|
62 void |
|
63 SafeRelease(HANDLE handle) |
|
64 { |
|
65 if (handle) { |
|
66 CloseHandle(handle); |
|
67 } |
|
68 } |
|
69 |
|
70 template <typename T> |
|
71 void SafeRelease(T * ptr) |
|
72 { |
|
73 if (ptr) { |
|
74 ptr->Release(); |
|
75 } |
|
76 } |
|
77 |
|
78 typedef void (*refill_function2)(cubeb_stream * stm, |
|
79 float * data, long frames_needed); |
|
80 |
|
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); |
|
84 |
|
85 extern cubeb_ops const wasapi_ops; |
|
86 } |
|
87 |
|
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 }; |
|
97 |
|
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 }; |
|
142 |
|
143 namespace { |
|
144 bool should_upmix(cubeb_stream * stream) |
|
145 { |
|
146 return stream->mix_params.channels > stream->stream_params.channels; |
|
147 } |
|
148 |
|
149 bool should_downmix(cubeb_stream * stream) |
|
150 { |
|
151 return stream->mix_params.channels < stream->stream_params.channels; |
|
152 } |
|
153 |
|
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 } |
|
165 |
|
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 } |
|
188 |
|
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 } |
|
205 |
|
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 } |
|
214 |
|
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; |
|
223 |
|
224 long before_resampling = frame_count_at_rate(frames_needed, rate); |
|
225 |
|
226 long frame_requested = before_resampling - stm->leftover_frame_count; |
|
227 |
|
228 size_t leftover_bytes = |
|
229 frames_to_bytes_before_mix(stm, stm->leftover_frame_count); |
|
230 |
|
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; |
|
235 |
|
236 long got = stm->data_callback(stm, stm->user_ptr, buffer_start, frame_requested); |
|
237 |
|
238 if (got != frame_requested) { |
|
239 stm->draining = true; |
|
240 } |
|
241 |
|
242 uint32_t in_frames = before_resampling; |
|
243 uint32_t out_frames = frames_needed; |
|
244 |
|
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 } |
|
253 |
|
254 speex_resampler_process_interleaved_float(stm->resampler, |
|
255 stm->resampling_src_buffer, |
|
256 &in_frames, |
|
257 resample_dest, |
|
258 &out_frames); |
|
259 |
|
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); |
|
264 |
|
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); |
|
268 |
|
269 assert(stm->leftover_frame_count <= stm->leftover_frame_size); |
|
270 memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes); |
|
271 |
|
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); |
|
275 |
|
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 } |
|
284 |
|
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 } |
|
296 |
|
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 } |
|
303 |
|
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 } |
|
312 |
|
313 static unsigned int __stdcall |
|
314 wasapi_stream_render_loop(LPVOID stream) |
|
315 { |
|
316 cubeb_stream * stm = static_cast<cubeb_stream *>(stream); |
|
317 |
|
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; |
|
324 |
|
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 } |
|
333 |
|
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 } |
|
339 |
|
340 while (is_playing) { |
|
341 DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), |
|
342 wait_array, |
|
343 FALSE, |
|
344 INFINITE); |
|
345 |
|
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; |
|
358 |
|
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); |
|
366 |
|
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 } |
|
374 |
|
375 long available = stm->buffer_frame_count - padding; |
|
376 |
|
377 if (available == 0) { |
|
378 continue; |
|
379 } |
|
380 |
|
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); |
|
385 |
|
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 } |
|
402 |
|
403 if (FAILED(hr)) { |
|
404 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); |
|
405 } |
|
406 |
|
407 stm->context->revert_mm_thread_characteristics(mmcss_handle); |
|
408 |
|
409 CoUninitialize(); |
|
410 return 0; |
|
411 } |
|
412 |
|
413 void wasapi_destroy(cubeb * context); |
|
414 |
|
415 HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index) |
|
416 { |
|
417 return (HANDLE)1; |
|
418 } |
|
419 |
|
420 BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle) |
|
421 { |
|
422 return true; |
|
423 } |
|
424 |
|
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 } |
|
445 |
|
446 SafeRelease(enumerator); |
|
447 |
|
448 return ERROR_SUCCESS; |
|
449 } |
|
450 } // namespace anonymous |
|
451 |
|
452 extern "C" { |
|
453 int wasapi_init(cubeb ** context, char const * context_name) |
|
454 { |
|
455 HRESULT hr; |
|
456 |
|
457 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); |
|
458 if (FAILED(hr)) { |
|
459 LOG("Could not init COM."); |
|
460 return CUBEB_ERROR; |
|
461 } |
|
462 |
|
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); |
|
473 |
|
474 cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); |
|
475 |
|
476 ctx->ops = &wasapi_ops; |
|
477 |
|
478 ctx->mmcss_module = LoadLibraryA("Avrt.dll"); |
|
479 |
|
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 } |
|
498 |
|
499 *context = ctx; |
|
500 |
|
501 return CUBEB_OK; |
|
502 } |
|
503 } |
|
504 |
|
505 namespace { |
|
506 |
|
507 void wasapi_destroy(cubeb * context) |
|
508 { |
|
509 if (context->mmcss_module) { |
|
510 FreeLibrary(context->mmcss_module); |
|
511 } |
|
512 free(context); |
|
513 } |
|
514 |
|
515 char const* wasapi_get_backend_id(cubeb * context) |
|
516 { |
|
517 return "wasapi"; |
|
518 } |
|
519 |
|
520 int |
|
521 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) |
|
522 { |
|
523 IAudioClient * client; |
|
524 WAVEFORMATEX * mix_format; |
|
525 |
|
526 assert(ctx && max_channels); |
|
527 |
|
528 IMMDevice * device; |
|
529 HRESULT hr = get_default_endpoint(&device); |
|
530 if (FAILED(hr)) { |
|
531 return CUBEB_ERROR; |
|
532 } |
|
533 |
|
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 } |
|
541 |
|
542 hr = client->GetMixFormat(&mix_format); |
|
543 if (FAILED(hr)) { |
|
544 SafeRelease(client); |
|
545 return CUBEB_ERROR; |
|
546 } |
|
547 |
|
548 *max_channels = mix_format->nChannels; |
|
549 |
|
550 CoTaskMemFree(mix_format); |
|
551 SafeRelease(client); |
|
552 |
|
553 return CUBEB_OK; |
|
554 } |
|
555 |
|
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; |
|
562 |
|
563 IMMDevice * device; |
|
564 hr = get_default_endpoint(&device); |
|
565 if (FAILED(hr)) { |
|
566 return CUBEB_ERROR; |
|
567 } |
|
568 |
|
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 } |
|
576 |
|
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 } |
|
583 |
|
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); |
|
588 |
|
589 SafeRelease(client); |
|
590 |
|
591 return CUBEB_OK; |
|
592 } |
|
593 |
|
594 int |
|
595 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) |
|
596 { |
|
597 HRESULT hr; |
|
598 IAudioClient * client; |
|
599 WAVEFORMATEX * mix_format; |
|
600 |
|
601 IMMDevice * device; |
|
602 hr = get_default_endpoint(&device); |
|
603 if (FAILED(hr)) { |
|
604 return CUBEB_ERROR; |
|
605 } |
|
606 |
|
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 } |
|
614 |
|
615 hr = client->GetMixFormat(&mix_format); |
|
616 if (FAILED(hr)) { |
|
617 SafeRelease(client); |
|
618 return CUBEB_ERROR; |
|
619 } |
|
620 |
|
621 *rate = mix_format->nSamplesPerSec; |
|
622 |
|
623 CoTaskMemFree(mix_format); |
|
624 SafeRelease(client); |
|
625 |
|
626 return CUBEB_OK; |
|
627 } |
|
628 |
|
629 void wasapi_stream_destroy(cubeb_stream * stm); |
|
630 |
|
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 } |
|
641 |
|
642 /* Otherwise, the hardware supports more than two channels. */ |
|
643 WAVEFORMATEX hw_mixformat = **mix_format; |
|
644 |
|
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 } |
|
653 |
|
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; |
|
674 |
|
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); |
|
680 |
|
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 } |
|
698 |
|
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; |
|
707 |
|
708 assert(context && stream); |
|
709 |
|
710 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); |
|
711 if (FAILED(hr)) { |
|
712 LOG("Could not initialize COM."); |
|
713 return CUBEB_ERROR; |
|
714 } |
|
715 |
|
716 cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream)); |
|
717 |
|
718 assert(stm); |
|
719 |
|
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; |
|
726 |
|
727 stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); |
|
728 stm->refill_event = CreateEvent(NULL, 0, 0, NULL); |
|
729 |
|
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 } |
|
735 |
|
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 } |
|
742 |
|
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 } |
|
750 |
|
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 } |
|
762 |
|
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 } |
|
771 |
|
772 handle_channel_layout(stm, &mix_format, &stream_params); |
|
773 |
|
774 /* Shared mode WASAPI always supports float32 sample format, so this |
|
775 * is safe. */ |
|
776 stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE; |
|
777 |
|
778 stm->mix_params.rate = mix_format->nSamplesPerSec; |
|
779 stm->mix_params.channels = mix_format->nChannels; |
|
780 |
|
781 float resampling_rate = static_cast<float>(stm->stream_params.rate) / |
|
782 stm->mix_params.rate; |
|
783 |
|
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 } |
|
800 |
|
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)); |
|
806 |
|
807 stm->refill_function = &refill_with_resampling; |
|
808 } else { |
|
809 stm->refill_function = &refill; |
|
810 } |
|
811 |
|
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); |
|
819 |
|
820 CoTaskMemFree(mix_format); |
|
821 |
|
822 if (FAILED(hr)) { |
|
823 LOG("Unable to initialize audio client: %x.", hr); |
|
824 wasapi_stream_destroy(stm); |
|
825 return CUBEB_ERROR; |
|
826 } |
|
827 |
|
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 } |
|
834 |
|
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 } |
|
838 |
|
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 } |
|
847 |
|
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 } |
|
854 |
|
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 } |
|
862 |
|
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 } |
|
870 |
|
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 } |
|
876 |
|
877 *stream = stm; |
|
878 |
|
879 return CUBEB_OK; |
|
880 } |
|
881 |
|
882 void wasapi_stream_destroy(cubeb_stream * stm) |
|
883 { |
|
884 assert(stm); |
|
885 |
|
886 if (stm->thread) { |
|
887 SetEvent(stm->shutdown_event); |
|
888 WaitForSingleObject(stm->thread, INFINITE); |
|
889 CloseHandle(stm->thread); |
|
890 stm->thread = 0; |
|
891 } |
|
892 |
|
893 SafeRelease(stm->shutdown_event); |
|
894 SafeRelease(stm->refill_event); |
|
895 |
|
896 SafeRelease(stm->client); |
|
897 SafeRelease(stm->render_client); |
|
898 SafeRelease(stm->audio_clock); |
|
899 |
|
900 if (stm->resampler) { |
|
901 speex_resampler_destroy(stm->resampler); |
|
902 } |
|
903 |
|
904 free(stm->leftover_frames_buffer); |
|
905 free(stm->resampling_src_buffer); |
|
906 free(stm->mix_buffer); |
|
907 free(stm); |
|
908 CoUninitialize(); |
|
909 } |
|
910 |
|
911 int wasapi_stream_start(cubeb_stream * stm) |
|
912 { |
|
913 HRESULT hr; |
|
914 |
|
915 assert(stm); |
|
916 |
|
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 } |
|
922 |
|
923 hr = stm->client->Start(); |
|
924 if (FAILED(hr)) { |
|
925 LOG("could not start the stream."); |
|
926 return CUBEB_ERROR; |
|
927 } |
|
928 |
|
929 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); |
|
930 |
|
931 return CUBEB_OK; |
|
932 } |
|
933 |
|
934 int wasapi_stream_stop(cubeb_stream * stm) |
|
935 { |
|
936 assert(stm && stm->shutdown_event); |
|
937 |
|
938 SetEvent(stm->shutdown_event); |
|
939 |
|
940 HRESULT hr = stm->client->Stop(); |
|
941 if (FAILED(hr)) { |
|
942 LOG("could not stop AudioClient"); |
|
943 } |
|
944 |
|
945 if (stm->thread) { |
|
946 WaitForSingleObject(stm->thread, INFINITE); |
|
947 CloseHandle(stm->thread); |
|
948 stm->thread = NULL; |
|
949 } |
|
950 |
|
951 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); |
|
952 |
|
953 return CUBEB_OK; |
|
954 } |
|
955 |
|
956 int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) |
|
957 { |
|
958 assert(stm && position); |
|
959 |
|
960 UINT64 pos; |
|
961 HRESULT hr; |
|
962 |
|
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 } |
|
968 |
|
969 *position = static_cast<uint64_t>(static_cast<double>(pos) / stm->clock_freq * stm->stream_params.rate); |
|
970 |
|
971 return CUBEB_OK; |
|
972 } |
|
973 |
|
974 int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) |
|
975 { |
|
976 assert(stm && latency); |
|
977 |
|
978 /* The GetStreamLatency method only works if the |
|
979 * AudioClient has been initialized. */ |
|
980 if (!stm->client) { |
|
981 return CUBEB_ERROR; |
|
982 } |
|
983 |
|
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); |
|
988 |
|
989 return CUBEB_OK; |
|
990 } |
|
991 |
|
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 |
|
1007 |