1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/libcubeb/src/cubeb_wasapi.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1007 @@ 1.4 +/* 1.5 + * Copyright © 2013 Mozilla Foundation 1.6 + * 1.7 + * This program is made available under an ISC-style license. See the 1.8 + * accompanying file LICENSE for details. 1.9 + */ 1.10 +#undef NDEBUG 1.11 +#if defined(HAVE_CONFIG_H) 1.12 +#include "config.h" 1.13 +#endif 1.14 +#include <assert.h> 1.15 +#include <windows.h> 1.16 +#include <mmdeviceapi.h> 1.17 +#include <windef.h> 1.18 +#include <audioclient.h> 1.19 +#include <math.h> 1.20 +#include <process.h> 1.21 +#include <avrt.h> 1.22 +#include "cubeb/cubeb.h" 1.23 +#include "cubeb-internal.h" 1.24 +#include "cubeb/cubeb-stdint.h" 1.25 +#include "cubeb-speex-resampler.h" 1.26 +#include <stdio.h> 1.27 + 1.28 +#if 0 1.29 +# define LOG(...) do { \ 1.30 + fprintf(stderr, __VA_ARGS__); \ 1.31 + fprintf(stderr, "\n"); \ 1.32 +} while(0); 1.33 +#else 1.34 +# define LOG(...) 1.35 +#endif 1.36 + 1.37 +#define ARRAY_LENGTH(array_) \ 1.38 + (sizeof(array_) / sizeof(array_[0])) 1.39 + 1.40 +namespace { 1.41 +uint32_t 1.42 +ms_to_hns(uint32_t ms) 1.43 +{ 1.44 + return ms * 10000; 1.45 +} 1.46 + 1.47 +uint32_t 1.48 +hns_to_ms(uint32_t hns) 1.49 +{ 1.50 + return hns / 10000; 1.51 +} 1.52 + 1.53 +double 1.54 +hns_to_s(uint32_t hns) 1.55 +{ 1.56 + return static_cast<double>(hns) / 10000000; 1.57 +} 1.58 + 1.59 +long 1.60 +frame_count_at_rate(long frame_count, float rate) 1.61 +{ 1.62 + return static_cast<long>(ceilf(rate * frame_count) + 1); 1.63 +} 1.64 + 1.65 +void 1.66 +SafeRelease(HANDLE handle) 1.67 +{ 1.68 + if (handle) { 1.69 + CloseHandle(handle); 1.70 + } 1.71 +} 1.72 + 1.73 +template <typename T> 1.74 +void SafeRelease(T * ptr) 1.75 +{ 1.76 + if (ptr) { 1.77 + ptr->Release(); 1.78 + } 1.79 +} 1.80 + 1.81 +typedef void (*refill_function2)(cubeb_stream * stm, 1.82 + float * data, long frames_needed); 1.83 + 1.84 +typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)( 1.85 + const char* TaskName, LPDWORD TaskIndex); 1.86 +typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle); 1.87 + 1.88 +extern cubeb_ops const wasapi_ops; 1.89 +} 1.90 + 1.91 +struct cubeb 1.92 +{ 1.93 + cubeb_ops const * ops; 1.94 + /* Library dynamically opened to increase the render 1.95 + * thread priority, and the two function pointers we need. */ 1.96 + HMODULE mmcss_module; 1.97 + set_mm_thread_characteristics_function set_mm_thread_characteristics; 1.98 + revert_mm_thread_characteristics_function revert_mm_thread_characteristics; 1.99 +}; 1.100 + 1.101 +struct cubeb_stream 1.102 +{ 1.103 + cubeb * context; 1.104 + /* Mixer pameters. We need to convert the input 1.105 + * stream to this samplerate/channel layout, as WASAPI 1.106 + * does not resample nor upmix itself. */ 1.107 + cubeb_stream_params mix_params; 1.108 + cubeb_stream_params stream_params; 1.109 + cubeb_state_callback state_callback; 1.110 + cubeb_data_callback data_callback; 1.111 + void * user_ptr; 1.112 + /* Main handle on the WASAPI stream. */ 1.113 + IAudioClient * client; 1.114 + /* Interface pointer to use the event-driven interface. */ 1.115 + IAudioRenderClient * render_client; 1.116 + /* Interface pointer to use the clock facilities. */ 1.117 + IAudioClock * audio_clock; 1.118 + /* This event is set by the stream_stop and stream_destroy 1.119 + * function, so the render loop can exit properly. */ 1.120 + HANDLE shutdown_event; 1.121 + /* This is set by WASAPI when we should refill the stream. */ 1.122 + HANDLE refill_event; 1.123 + /* Each cubeb_stream has its own thread. */ 1.124 + HANDLE thread; 1.125 + uint64_t clock_freq; 1.126 + /* Maximum number of frames we can be requested in a callback. */ 1.127 + uint32_t buffer_frame_count; 1.128 + /* Resampler instance. If this is !NULL, resampling should happen. */ 1.129 + SpeexResamplerState * resampler; 1.130 + /* Buffer to resample from, into the mix buffer or the final buffer. */ 1.131 + float * resampling_src_buffer; 1.132 + /* Pointer to the function used to refill the buffer, depending 1.133 + * on the respective samplerate of the stream and the mix. */ 1.134 + refill_function2 refill_function; 1.135 + /* Leftover frames handling, only used when resampling. */ 1.136 + uint32_t leftover_frame_count; 1.137 + uint32_t leftover_frame_size; 1.138 + float * leftover_frames_buffer; 1.139 + /* Buffer used to downmix or upmix to the number of channels the mixer has. 1.140 + * its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */ 1.141 + float * mix_buffer; 1.142 + /* True if the stream is draining. */ 1.143 + bool draining; 1.144 +}; 1.145 + 1.146 +namespace { 1.147 +bool should_upmix(cubeb_stream * stream) 1.148 +{ 1.149 + return stream->mix_params.channels > stream->stream_params.channels; 1.150 +} 1.151 + 1.152 +bool should_downmix(cubeb_stream * stream) 1.153 +{ 1.154 + return stream->mix_params.channels < stream->stream_params.channels; 1.155 +} 1.156 + 1.157 +/* Upmix function, copies a mono channel in two interleaved 1.158 + * stereo channel. |out| has to be twice as long as |in| */ 1.159 +template<typename T> 1.160 +void 1.161 +mono_to_stereo(T * in, long insamples, T * out) 1.162 +{ 1.163 + int j = 0; 1.164 + for (int i = 0; i < insamples; ++i, j += 2) { 1.165 + out[j] = out[j + 1] = in[i]; 1.166 + } 1.167 +} 1.168 + 1.169 +template<typename T> 1.170 +void 1.171 +upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) 1.172 +{ 1.173 + assert(out_channels >= in_channels); 1.174 + /* If we are playing a mono stream over stereo speakers, copy the data over. */ 1.175 + if (in_channels == 1 && out_channels == 2) { 1.176 + mono_to_stereo(in, inframes, out); 1.177 + return; 1.178 + } 1.179 + /* Otherwise, put silence in other channels. */ 1.180 + long out_index = 0; 1.181 + for (long i = 0; i < inframes * in_channels; i += in_channels) { 1.182 + for (int j = 0; j < in_channels; ++j) { 1.183 + out[out_index + j] = in[i + j]; 1.184 + } 1.185 + for (int j = in_channels; j < out_channels; ++j) { 1.186 + out[out_index + j] = 0.0; 1.187 + } 1.188 + out_index += out_channels; 1.189 + } 1.190 +} 1.191 + 1.192 +template<typename T> 1.193 +void 1.194 +downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) 1.195 +{ 1.196 + assert(in_channels >= out_channels); 1.197 + /* We could use a downmix matrix here, applying mixing weight based on the 1.198 + * channel, but directsound and winmm simply drop the channels that cannot be 1.199 + * rendered by the hardware, so we do the same for consistency. */ 1.200 + long out_index = 0; 1.201 + for (long i = 0; i < inframes * in_channels; i += in_channels) { 1.202 + for (int j = 0; j < out_channels; ++j) { 1.203 + out[out_index + j] = in[i + j]; 1.204 + } 1.205 + out_index += out_channels; 1.206 + } 1.207 +} 1.208 + 1.209 +/* This returns the size of a frame in the stream, 1.210 + * before the eventual upmix occurs. */ 1.211 +static size_t 1.212 +frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) 1.213 +{ 1.214 + size_t stream_frame_size = stm->stream_params.channels * sizeof(float); 1.215 + return stream_frame_size * frames; 1.216 +} 1.217 + 1.218 +void 1.219 +refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed) 1.220 +{ 1.221 + /* Use more input frames that strictly necessary, so in the worst case, 1.222 + * we have leftover unresampled frames at the end, that we can use 1.223 + * during the next iteration. */ 1.224 + float rate = 1.225 + static_cast<float>(stm->stream_params.rate) / stm->mix_params.rate; 1.226 + 1.227 + long before_resampling = frame_count_at_rate(frames_needed, rate); 1.228 + 1.229 + long frame_requested = before_resampling - stm->leftover_frame_count; 1.230 + 1.231 + size_t leftover_bytes = 1.232 + frames_to_bytes_before_mix(stm, stm->leftover_frame_count); 1.233 + 1.234 + /* Copy the previous leftover frames to the front of the buffer. */ 1.235 + memcpy(stm->resampling_src_buffer, stm->leftover_frames_buffer, leftover_bytes); 1.236 + uint8_t * buffer_start = reinterpret_cast<uint8_t *>( 1.237 + stm->resampling_src_buffer) + leftover_bytes; 1.238 + 1.239 + long got = stm->data_callback(stm, stm->user_ptr, buffer_start, frame_requested); 1.240 + 1.241 + if (got != frame_requested) { 1.242 + stm->draining = true; 1.243 + } 1.244 + 1.245 + uint32_t in_frames = before_resampling; 1.246 + uint32_t out_frames = frames_needed; 1.247 + 1.248 + /* If we need to upmix after resampling, resample into the mix buffer to 1.249 + * avoid a copy. */ 1.250 + float * resample_dest; 1.251 + if (should_upmix(stm) || should_downmix(stm)) { 1.252 + resample_dest = stm->mix_buffer; 1.253 + } else { 1.254 + resample_dest = data; 1.255 + } 1.256 + 1.257 + speex_resampler_process_interleaved_float(stm->resampler, 1.258 + stm->resampling_src_buffer, 1.259 + &in_frames, 1.260 + resample_dest, 1.261 + &out_frames); 1.262 + 1.263 + /* Copy the leftover frames to buffer for the next time. */ 1.264 + stm->leftover_frame_count = before_resampling - in_frames; 1.265 + size_t unresampled_bytes = 1.266 + frames_to_bytes_before_mix(stm, stm->leftover_frame_count); 1.267 + 1.268 + uint8_t * leftover_frames_start = 1.269 + reinterpret_cast<uint8_t *>(stm->resampling_src_buffer); 1.270 + leftover_frames_start += frames_to_bytes_before_mix(stm, in_frames); 1.271 + 1.272 + assert(stm->leftover_frame_count <= stm->leftover_frame_size); 1.273 + memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes); 1.274 + 1.275 + /* If this is not true, there will be glitches. 1.276 + * It is alright to have produced less frames if we are draining, though. */ 1.277 + assert(out_frames == frames_needed || stm->draining); 1.278 + 1.279 + if (should_upmix(stm)) { 1.280 + upmix(resample_dest, out_frames, data, 1.281 + stm->stream_params.channels, stm->mix_params.channels); 1.282 + } else if (should_downmix(stm)) { 1.283 + downmix(resample_dest, out_frames, data, 1.284 + stm->stream_params.channels, stm->mix_params.channels); 1.285 + } 1.286 +} 1.287 + 1.288 +void 1.289 +refill(cubeb_stream * stm, float * data, long frames_needed) 1.290 +{ 1.291 + /* If we need to upmix/downmix, get the data into the mix buffer to avoid a 1.292 + * copy, then do the processing process. */ 1.293 + float * dest; 1.294 + if (should_upmix(stm) || should_downmix(stm)) { 1.295 + dest = stm->mix_buffer; 1.296 + } else { 1.297 + dest = data; 1.298 + } 1.299 + 1.300 + long got = stm->data_callback(stm, stm->user_ptr, dest, frames_needed); 1.301 + assert(got <= frames_needed); 1.302 + if (got != frames_needed) { 1.303 + LOG("draining."); 1.304 + stm->draining = true; 1.305 + } 1.306 + 1.307 + if (should_upmix(stm)) { 1.308 + upmix(dest, got, data, 1.309 + stm->stream_params.channels, stm->mix_params.channels); 1.310 + } else if (should_downmix(stm)) { 1.311 + downmix(dest, got, data, 1.312 + stm->stream_params.channels, stm->mix_params.channels); 1.313 + } 1.314 +} 1.315 + 1.316 +static unsigned int __stdcall 1.317 +wasapi_stream_render_loop(LPVOID stream) 1.318 +{ 1.319 + cubeb_stream * stm = static_cast<cubeb_stream *>(stream); 1.320 + 1.321 + bool is_playing = true; 1.322 + HANDLE wait_array[2] = {stm->shutdown_event, stm->refill_event}; 1.323 + HANDLE mmcss_handle = NULL; 1.324 + HRESULT hr; 1.325 + bool first = true; 1.326 + DWORD mmcss_task_index = 0; 1.327 + 1.328 + /* We could consider using "Pro Audio" here for WebAudio and 1.329 + * maybe WebRTC. */ 1.330 + mmcss_handle = 1.331 + stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); 1.332 + if (!mmcss_handle) { 1.333 + /* This is not fatal, but we might glitch under heavy load. */ 1.334 + LOG("Unable to use mmcss to bump the render thread priority: %x", GetLastError()); 1.335 + } 1.336 + 1.337 + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 1.338 + if (FAILED(hr)) { 1.339 + LOG("could not initialize COM in render thread: %x", hr); 1.340 + return hr; 1.341 + } 1.342 + 1.343 + while (is_playing) { 1.344 + DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), 1.345 + wait_array, 1.346 + FALSE, 1.347 + INFINITE); 1.348 + 1.349 + switch (waitResult) { 1.350 + case WAIT_OBJECT_0: { /* shutdown */ 1.351 + is_playing = false; 1.352 + /* We don't check if the drain is actually finished here, we just want to 1.353 + * shutdown. */ 1.354 + if (stm->draining) { 1.355 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1.356 + } 1.357 + continue; 1.358 + } 1.359 + case WAIT_OBJECT_0 + 1: { /* refill */ 1.360 + UINT32 padding; 1.361 + 1.362 + hr = stm->client->GetCurrentPadding(&padding); 1.363 + if (FAILED(hr)) { 1.364 + LOG("Failed to get padding"); 1.365 + is_playing = false; 1.366 + continue; 1.367 + } 1.368 + assert(padding <= stm->buffer_frame_count); 1.369 + 1.370 + if (stm->draining) { 1.371 + if (padding == 0) { 1.372 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1.373 + is_playing = false; 1.374 + } 1.375 + continue; 1.376 + } 1.377 + 1.378 + long available = stm->buffer_frame_count - padding; 1.379 + 1.380 + if (available == 0) { 1.381 + continue; 1.382 + } 1.383 + 1.384 + BYTE* data; 1.385 + hr = stm->render_client->GetBuffer(available, &data); 1.386 + if (SUCCEEDED(hr)) { 1.387 + stm->refill_function(stm, reinterpret_cast<float *>(data), available); 1.388 + 1.389 + hr = stm->render_client->ReleaseBuffer(available, 0); 1.390 + if (FAILED(hr)) { 1.391 + LOG("failed to release buffer."); 1.392 + is_playing = false; 1.393 + } 1.394 + } else { 1.395 + LOG("failed to get buffer."); 1.396 + is_playing = false; 1.397 + } 1.398 + } 1.399 + break; 1.400 + default: 1.401 + LOG("case %d not handled in render loop.", waitResult); 1.402 + abort(); 1.403 + } 1.404 + } 1.405 + 1.406 + if (FAILED(hr)) { 1.407 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 1.408 + } 1.409 + 1.410 + stm->context->revert_mm_thread_characteristics(mmcss_handle); 1.411 + 1.412 + CoUninitialize(); 1.413 + return 0; 1.414 +} 1.415 + 1.416 +void wasapi_destroy(cubeb * context); 1.417 + 1.418 +HANDLE WINAPI set_mm_thread_characteristics_noop(const char *, LPDWORD mmcss_task_index) 1.419 +{ 1.420 + return (HANDLE)1; 1.421 +} 1.422 + 1.423 +BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle) 1.424 +{ 1.425 + return true; 1.426 +} 1.427 + 1.428 +HRESULT get_default_endpoint(IMMDevice ** device) 1.429 +{ 1.430 + IMMDeviceEnumerator * enumerator; 1.431 + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 1.432 + NULL, CLSCTX_INPROC_SERVER, 1.433 + IID_PPV_ARGS(&enumerator)); 1.434 + if (FAILED(hr)) { 1.435 + LOG("Could not get device enumerator."); 1.436 + return hr; 1.437 + } 1.438 + /* eMultimedia is okay for now ("Music, movies, narration, [...]"). 1.439 + * We will need to change this when we distinguish streams by use-case, other 1.440 + * possible values being eConsole ("Games, system notification sounds [...]") 1.441 + * and eCommunication ("Voice communication"). */ 1.442 + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device); 1.443 + if (FAILED(hr)) { 1.444 + LOG("Could not get default audio endpoint."); 1.445 + SafeRelease(enumerator); 1.446 + return hr; 1.447 + } 1.448 + 1.449 + SafeRelease(enumerator); 1.450 + 1.451 + return ERROR_SUCCESS; 1.452 +} 1.453 +} // namespace anonymous 1.454 + 1.455 +extern "C" { 1.456 +int wasapi_init(cubeb ** context, char const * context_name) 1.457 +{ 1.458 + HRESULT hr; 1.459 + 1.460 + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 1.461 + if (FAILED(hr)) { 1.462 + LOG("Could not init COM."); 1.463 + return CUBEB_ERROR; 1.464 + } 1.465 + 1.466 + /* We don't use the device yet, but need to make sure we can initialize one 1.467 + so that this backend is not incorrectly enabled on platforms that don't 1.468 + support WASAPI. */ 1.469 + IMMDevice * device; 1.470 + hr = get_default_endpoint(&device); 1.471 + if (FAILED(hr)) { 1.472 + LOG("Could not get device."); 1.473 + return CUBEB_ERROR; 1.474 + } 1.475 + SafeRelease(device); 1.476 + 1.477 + cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); 1.478 + 1.479 + ctx->ops = &wasapi_ops; 1.480 + 1.481 + ctx->mmcss_module = LoadLibraryA("Avrt.dll"); 1.482 + 1.483 + if (ctx->mmcss_module) { 1.484 + ctx->set_mm_thread_characteristics = 1.485 + (set_mm_thread_characteristics_function) GetProcAddress( 1.486 + ctx->mmcss_module, "AvSetMmThreadCharacteristicsA"); 1.487 + ctx->revert_mm_thread_characteristics = 1.488 + (revert_mm_thread_characteristics_function) GetProcAddress( 1.489 + ctx->mmcss_module, "AvRevertMmThreadCharacteristics"); 1.490 + if (!(ctx->set_mm_thread_characteristics && ctx->revert_mm_thread_characteristics)) { 1.491 + LOG("Could not load AvSetMmThreadCharacteristics or AvRevertMmThreadCharacteristics: %x", GetLastError()); 1.492 + FreeLibrary(ctx->mmcss_module); 1.493 + } 1.494 + } else { 1.495 + // This is not a fatal error, but we might end up glitching when 1.496 + // the system is under high load. 1.497 + LOG("Could not load Avrt.dll"); 1.498 + ctx->set_mm_thread_characteristics = &set_mm_thread_characteristics_noop; 1.499 + ctx->revert_mm_thread_characteristics = &revert_mm_thread_characteristics_noop; 1.500 + } 1.501 + 1.502 + *context = ctx; 1.503 + 1.504 + return CUBEB_OK; 1.505 +} 1.506 +} 1.507 + 1.508 +namespace { 1.509 + 1.510 +void wasapi_destroy(cubeb * context) 1.511 +{ 1.512 + if (context->mmcss_module) { 1.513 + FreeLibrary(context->mmcss_module); 1.514 + } 1.515 + free(context); 1.516 +} 1.517 + 1.518 +char const* wasapi_get_backend_id(cubeb * context) 1.519 +{ 1.520 + return "wasapi"; 1.521 +} 1.522 + 1.523 +int 1.524 +wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1.525 +{ 1.526 + IAudioClient * client; 1.527 + WAVEFORMATEX * mix_format; 1.528 + 1.529 + assert(ctx && max_channels); 1.530 + 1.531 + IMMDevice * device; 1.532 + HRESULT hr = get_default_endpoint(&device); 1.533 + if (FAILED(hr)) { 1.534 + return CUBEB_ERROR; 1.535 + } 1.536 + 1.537 + hr = device->Activate(__uuidof(IAudioClient), 1.538 + CLSCTX_INPROC_SERVER, 1.539 + NULL, (void **)&client); 1.540 + SafeRelease(device); 1.541 + if (FAILED(hr)) { 1.542 + return CUBEB_ERROR; 1.543 + } 1.544 + 1.545 + hr = client->GetMixFormat(&mix_format); 1.546 + if (FAILED(hr)) { 1.547 + SafeRelease(client); 1.548 + return CUBEB_ERROR; 1.549 + } 1.550 + 1.551 + *max_channels = mix_format->nChannels; 1.552 + 1.553 + CoTaskMemFree(mix_format); 1.554 + SafeRelease(client); 1.555 + 1.556 + return CUBEB_OK; 1.557 +} 1.558 + 1.559 +int 1.560 +wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) 1.561 +{ 1.562 + HRESULT hr; 1.563 + IAudioClient * client; 1.564 + REFERENCE_TIME default_period; 1.565 + 1.566 + IMMDevice * device; 1.567 + hr = get_default_endpoint(&device); 1.568 + if (FAILED(hr)) { 1.569 + return CUBEB_ERROR; 1.570 + } 1.571 + 1.572 + hr = device->Activate(__uuidof(IAudioClient), 1.573 + CLSCTX_INPROC_SERVER, 1.574 + NULL, (void **)&client); 1.575 + SafeRelease(device); 1.576 + if (FAILED(hr)) { 1.577 + return CUBEB_ERROR; 1.578 + } 1.579 + 1.580 + /* The second parameter is for exclusive mode, that we don't use. */ 1.581 + hr = client->GetDevicePeriod(&default_period, NULL); 1.582 + if (FAILED(hr)) { 1.583 + SafeRelease(client); 1.584 + return CUBEB_ERROR; 1.585 + } 1.586 + 1.587 + /* According to the docs, the best latency we can achieve is by synchronizing 1.588 + * the stream and the engine. 1.589 + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ 1.590 + *latency_ms = hns_to_ms(default_period); 1.591 + 1.592 + SafeRelease(client); 1.593 + 1.594 + return CUBEB_OK; 1.595 +} 1.596 + 1.597 +int 1.598 +wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1.599 +{ 1.600 + HRESULT hr; 1.601 + IAudioClient * client; 1.602 + WAVEFORMATEX * mix_format; 1.603 + 1.604 + IMMDevice * device; 1.605 + hr = get_default_endpoint(&device); 1.606 + if (FAILED(hr)) { 1.607 + return CUBEB_ERROR; 1.608 + } 1.609 + 1.610 + hr = device->Activate(__uuidof(IAudioClient), 1.611 + CLSCTX_INPROC_SERVER, 1.612 + NULL, (void **)&client); 1.613 + SafeRelease(device); 1.614 + if (FAILED(hr)) { 1.615 + return CUBEB_ERROR; 1.616 + } 1.617 + 1.618 + hr = client->GetMixFormat(&mix_format); 1.619 + if (FAILED(hr)) { 1.620 + SafeRelease(client); 1.621 + return CUBEB_ERROR; 1.622 + } 1.623 + 1.624 + *rate = mix_format->nSamplesPerSec; 1.625 + 1.626 + CoTaskMemFree(mix_format); 1.627 + SafeRelease(client); 1.628 + 1.629 + return CUBEB_OK; 1.630 +} 1.631 + 1.632 +void wasapi_stream_destroy(cubeb_stream * stm); 1.633 + 1.634 +/* Based on the mix format and the stream format, try to find a way to play what 1.635 + * the user requested. */ 1.636 +static void 1.637 +handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params) 1.638 +{ 1.639 + /* Common case: the hardware is stereo. Up-mixing and down-mixing will be 1.640 + * handled in the callback. */ 1.641 + if ((*mix_format)->nChannels <= 2) { 1.642 + return; 1.643 + } 1.644 + 1.645 + /* Otherwise, the hardware supports more than two channels. */ 1.646 + WAVEFORMATEX hw_mixformat = **mix_format; 1.647 + 1.648 + /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], 1.649 + * so the reinterpret_cast below should be safe. In practice, this is not 1.650 + * true, and we just want to bail out and let the rest of the code find a good 1.651 + * conversion path instead of trying to make WASAPI do it by itself. 1.652 + * [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ 1.653 + if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { 1.654 + return; 1.655 + } 1.656 + 1.657 + /* The hardware is in surround mode, we want to only use front left and front 1.658 + * right. Try that, and check if it works. */ 1.659 + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>((*mix_format)); 1.660 + switch (stream_params->channels) { 1.661 + case 1: /* Mono */ 1.662 + format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO; 1.663 + break; 1.664 + case 2: /* Stereo */ 1.665 + format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO; 1.666 + break; 1.667 + default: 1.668 + assert(false && "Channel layout not supported."); 1.669 + break; 1.670 + } 1.671 + (*mix_format)->nChannels = stream_params->channels; 1.672 + (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8; 1.673 + (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign; 1.674 + format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; 1.675 + (*mix_format)->wBitsPerSample = 32; 1.676 + format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample; 1.677 + 1.678 + /* Check if wasapi will accept our channel layout request. */ 1.679 + WAVEFORMATEX * closest; 1.680 + HRESULT hr = stm->client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, 1.681 + *mix_format, 1.682 + &closest); 1.683 + 1.684 + if (hr == S_FALSE) { 1.685 + /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the 1.686 + * eventual upmix/downmix ourselves */ 1.687 + LOG("Using WASAPI suggested format: channels: %d", closest->nChannels); 1.688 + WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest); 1.689 + assert(closest_pcm->SubFormat == format_pcm->SubFormat); 1.690 + CoTaskMemFree(*mix_format); 1.691 + *mix_format = closest; 1.692 + } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { 1.693 + /* Not supported, no suggestion. This should not happen, but it does in the 1.694 + * field with some sound cards. We restore the mix format, and let the rest 1.695 + * of the code figure out the right conversion path. */ 1.696 + **mix_format = hw_mixformat; 1.697 + } else if (hr == S_OK) { 1.698 + LOG("Requested format accepted by WASAPI."); 1.699 + } 1.700 +} 1.701 + 1.702 +int 1.703 +wasapi_stream_init(cubeb * context, cubeb_stream ** stream, 1.704 + char const * stream_name, cubeb_stream_params stream_params, 1.705 + unsigned int latency, cubeb_data_callback data_callback, 1.706 + cubeb_state_callback state_callback, void * user_ptr) 1.707 +{ 1.708 + HRESULT hr; 1.709 + WAVEFORMATEX * mix_format; 1.710 + 1.711 + assert(context && stream); 1.712 + 1.713 + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 1.714 + if (FAILED(hr)) { 1.715 + LOG("Could not initialize COM."); 1.716 + return CUBEB_ERROR; 1.717 + } 1.718 + 1.719 + cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream)); 1.720 + 1.721 + assert(stm); 1.722 + 1.723 + stm->context = context; 1.724 + stm->data_callback = data_callback; 1.725 + stm->state_callback = state_callback; 1.726 + stm->user_ptr = user_ptr; 1.727 + stm->stream_params = stream_params; 1.728 + stm->draining = false; 1.729 + 1.730 + stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); 1.731 + stm->refill_event = CreateEvent(NULL, 0, 0, NULL); 1.732 + 1.733 + if (!stm->shutdown_event) { 1.734 + LOG("Can't create the shutdown event, error: %x.", GetLastError()); 1.735 + wasapi_stream_destroy(stm); 1.736 + return CUBEB_ERROR; 1.737 + } 1.738 + 1.739 + if (!stm->refill_event) { 1.740 + SafeRelease(stm->shutdown_event); 1.741 + LOG("Can't create the refill event, error: %x.", GetLastError()); 1.742 + wasapi_stream_destroy(stm); 1.743 + return CUBEB_ERROR; 1.744 + } 1.745 + 1.746 + IMMDevice * device; 1.747 + hr = get_default_endpoint(&device); 1.748 + if (FAILED(hr)) { 1.749 + LOG("Could not get default endpoint, error: %x", hr); 1.750 + wasapi_stream_destroy(stm); 1.751 + return CUBEB_ERROR; 1.752 + } 1.753 + 1.754 + /* Get a client. We will get all other interfaces we need from 1.755 + * this pointer. */ 1.756 + hr = device->Activate(__uuidof(IAudioClient), 1.757 + CLSCTX_INPROC_SERVER, 1.758 + NULL, (void **)&stm->client); 1.759 + SafeRelease(device); 1.760 + if (FAILED(hr)) { 1.761 + LOG("Could not activate the device to get an audio client: error: %x", hr); 1.762 + wasapi_stream_destroy(stm); 1.763 + return CUBEB_ERROR; 1.764 + } 1.765 + 1.766 + /* We have to distinguish between the format the mixer uses, 1.767 + * and the format the stream we want to play uses. */ 1.768 + hr = stm->client->GetMixFormat(&mix_format); 1.769 + if (FAILED(hr)) { 1.770 + LOG("Could not fetch current mix format from the audio client: error: %x", hr); 1.771 + wasapi_stream_destroy(stm); 1.772 + return CUBEB_ERROR; 1.773 + } 1.774 + 1.775 + handle_channel_layout(stm, &mix_format, &stream_params); 1.776 + 1.777 + /* Shared mode WASAPI always supports float32 sample format, so this 1.778 + * is safe. */ 1.779 + stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE; 1.780 + 1.781 + stm->mix_params.rate = mix_format->nSamplesPerSec; 1.782 + stm->mix_params.channels = mix_format->nChannels; 1.783 + 1.784 + float resampling_rate = static_cast<float>(stm->stream_params.rate) / 1.785 + stm->mix_params.rate; 1.786 + 1.787 + if (resampling_rate != 1.0) { 1.788 + /* If we are playing a mono stream, we only resample one channel, 1.789 + * and copy it over, so we are always resampling the number 1.790 + * of channels of the stream, not the number of channels 1.791 + * that WASAPI wants. */ 1.792 + stm->resampler = speex_resampler_init(stm->stream_params.channels, 1.793 + stm->stream_params.rate, 1.794 + stm->mix_params.rate, 1.795 + SPEEX_RESAMPLER_QUALITY_DESKTOP, 1.796 + NULL); 1.797 + if (!stm->resampler) { 1.798 + LOG("Could not get a resampler"); 1.799 + CoTaskMemFree(mix_format); 1.800 + wasapi_stream_destroy(stm); 1.801 + return CUBEB_ERROR; 1.802 + } 1.803 + 1.804 + /* Get a little buffer so we can store the leftover frames, 1.805 + * that is, the samples not consumed by the resampler that we will end up 1.806 + * using next time the render callback is called. */ 1.807 + stm->leftover_frame_size = static_cast<uint32_t>(ceilf(1 / resampling_rate * 2) + 1); 1.808 + stm->leftover_frames_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, stm->leftover_frame_size)); 1.809 + 1.810 + stm->refill_function = &refill_with_resampling; 1.811 + } else { 1.812 + stm->refill_function = &refill; 1.813 + } 1.814 + 1.815 + hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED, 1.816 + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | 1.817 + AUDCLNT_STREAMFLAGS_NOPERSIST, 1.818 + ms_to_hns(latency), 1.819 + 0, 1.820 + mix_format, 1.821 + NULL); 1.822 + 1.823 + CoTaskMemFree(mix_format); 1.824 + 1.825 + if (FAILED(hr)) { 1.826 + LOG("Unable to initialize audio client: %x.", hr); 1.827 + wasapi_stream_destroy(stm); 1.828 + return CUBEB_ERROR; 1.829 + } 1.830 + 1.831 + hr = stm->client->GetBufferSize(&stm->buffer_frame_count); 1.832 + if (FAILED(hr)) { 1.833 + LOG("Could not get the buffer size from the client %x.", hr); 1.834 + wasapi_stream_destroy(stm); 1.835 + return CUBEB_ERROR; 1.836 + } 1.837 + 1.838 + if (should_upmix(stm) || should_downmix(stm)) { 1.839 + stm->mix_buffer = (float *) malloc(frames_to_bytes_before_mix(stm, stm->buffer_frame_count)); 1.840 + } 1.841 + 1.842 + /* If we are going to resample, we will end up needing a buffer 1.843 + * to resample from, because speex's resampler does not do 1.844 + * in-place processing. Of course we need to take the resampling 1.845 + * factor and the channel layout into account. */ 1.846 + if (stm->resampler) { 1.847 + size_t frames_needed = static_cast<size_t>(frame_count_at_rate(stm->buffer_frame_count, resampling_rate)); 1.848 + stm->resampling_src_buffer = (float *)malloc(frames_to_bytes_before_mix(stm, frames_needed)); 1.849 + } 1.850 + 1.851 + hr = stm->client->SetEventHandle(stm->refill_event); 1.852 + if (FAILED(hr)) { 1.853 + LOG("Could set the event handle for the client %x.", hr); 1.854 + wasapi_stream_destroy(stm); 1.855 + return CUBEB_ERROR; 1.856 + } 1.857 + 1.858 + hr = stm->client->GetService(__uuidof(IAudioRenderClient), 1.859 + (void **)&stm->render_client); 1.860 + if (FAILED(hr)) { 1.861 + LOG("Could not get the render client %x.", hr); 1.862 + wasapi_stream_destroy(stm); 1.863 + return CUBEB_ERROR; 1.864 + } 1.865 + 1.866 + hr = stm->client->GetService(__uuidof(IAudioClock), 1.867 + (void **)&stm->audio_clock); 1.868 + if (FAILED(hr)) { 1.869 + LOG("Could not get the IAudioClock, %x", hr); 1.870 + wasapi_stream_destroy(stm); 1.871 + return CUBEB_ERROR; 1.872 + } 1.873 + 1.874 + hr = stm->audio_clock->GetFrequency(&stm->clock_freq); 1.875 + if (FAILED(hr)) { 1.876 + LOG("failed to get audio clock frequency, %x", hr); 1.877 + return CUBEB_ERROR; 1.878 + } 1.879 + 1.880 + *stream = stm; 1.881 + 1.882 + return CUBEB_OK; 1.883 +} 1.884 + 1.885 +void wasapi_stream_destroy(cubeb_stream * stm) 1.886 +{ 1.887 + assert(stm); 1.888 + 1.889 + if (stm->thread) { 1.890 + SetEvent(stm->shutdown_event); 1.891 + WaitForSingleObject(stm->thread, INFINITE); 1.892 + CloseHandle(stm->thread); 1.893 + stm->thread = 0; 1.894 + } 1.895 + 1.896 + SafeRelease(stm->shutdown_event); 1.897 + SafeRelease(stm->refill_event); 1.898 + 1.899 + SafeRelease(stm->client); 1.900 + SafeRelease(stm->render_client); 1.901 + SafeRelease(stm->audio_clock); 1.902 + 1.903 + if (stm->resampler) { 1.904 + speex_resampler_destroy(stm->resampler); 1.905 + } 1.906 + 1.907 + free(stm->leftover_frames_buffer); 1.908 + free(stm->resampling_src_buffer); 1.909 + free(stm->mix_buffer); 1.910 + free(stm); 1.911 + CoUninitialize(); 1.912 +} 1.913 + 1.914 +int wasapi_stream_start(cubeb_stream * stm) 1.915 +{ 1.916 + HRESULT hr; 1.917 + 1.918 + assert(stm); 1.919 + 1.920 + stm->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); 1.921 + if (stm->thread == NULL) { 1.922 + LOG("could not create WASAPI render thread."); 1.923 + return CUBEB_ERROR; 1.924 + } 1.925 + 1.926 + hr = stm->client->Start(); 1.927 + if (FAILED(hr)) { 1.928 + LOG("could not start the stream."); 1.929 + return CUBEB_ERROR; 1.930 + } 1.931 + 1.932 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); 1.933 + 1.934 + return CUBEB_OK; 1.935 +} 1.936 + 1.937 +int wasapi_stream_stop(cubeb_stream * stm) 1.938 +{ 1.939 + assert(stm && stm->shutdown_event); 1.940 + 1.941 + SetEvent(stm->shutdown_event); 1.942 + 1.943 + HRESULT hr = stm->client->Stop(); 1.944 + if (FAILED(hr)) { 1.945 + LOG("could not stop AudioClient"); 1.946 + } 1.947 + 1.948 + if (stm->thread) { 1.949 + WaitForSingleObject(stm->thread, INFINITE); 1.950 + CloseHandle(stm->thread); 1.951 + stm->thread = NULL; 1.952 + } 1.953 + 1.954 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 1.955 + 1.956 + return CUBEB_OK; 1.957 +} 1.958 + 1.959 +int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) 1.960 +{ 1.961 + assert(stm && position); 1.962 + 1.963 + UINT64 pos; 1.964 + HRESULT hr; 1.965 + 1.966 + hr = stm->audio_clock->GetPosition(&pos, NULL); 1.967 + if (FAILED(hr)) { 1.968 + LOG("Could not get accurate position: %x\n", hr); 1.969 + return CUBEB_ERROR; 1.970 + } 1.971 + 1.972 + *position = static_cast<uint64_t>(static_cast<double>(pos) / stm->clock_freq * stm->stream_params.rate); 1.973 + 1.974 + return CUBEB_OK; 1.975 +} 1.976 + 1.977 +int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 1.978 +{ 1.979 + assert(stm && latency); 1.980 + 1.981 + /* The GetStreamLatency method only works if the 1.982 + * AudioClient has been initialized. */ 1.983 + if (!stm->client) { 1.984 + return CUBEB_ERROR; 1.985 + } 1.986 + 1.987 + REFERENCE_TIME latency_hns; 1.988 + stm->client->GetStreamLatency(&latency_hns); 1.989 + double latency_s = hns_to_s(latency_hns); 1.990 + *latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate); 1.991 + 1.992 + return CUBEB_OK; 1.993 +} 1.994 + 1.995 +cubeb_ops const wasapi_ops = { 1.996 + /*.init =*/ wasapi_init, 1.997 + /*.get_backend_id =*/ wasapi_get_backend_id, 1.998 + /*.get_max_channel_count =*/ wasapi_get_max_channel_count, 1.999 + /*.get_min_latency =*/ wasapi_get_min_latency, 1.1000 + /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, 1.1001 + /*.destroy =*/ wasapi_destroy, 1.1002 + /*.stream_init =*/ wasapi_stream_init, 1.1003 + /*.stream_destroy =*/ wasapi_stream_destroy, 1.1004 + /*.stream_start =*/ wasapi_stream_start, 1.1005 + /*.stream_stop =*/ wasapi_stream_stop, 1.1006 + /*.stream_get_position =*/ wasapi_stream_get_position, 1.1007 + /*.stream_get_latency =*/ wasapi_stream_get_latency 1.1008 + }; 1.1009 +} // namespace anonymous 1.1010 +