media/libcubeb/src/cubeb_wasapi.cpp

changeset 0
6474c204b198
     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 +

mercurial