media/libcubeb/src/cubeb_wasapi.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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

mercurial