media/libcubeb/src/cubeb_wasapi.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial