media/libcubeb/src/cubeb_winmm.c

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 © 2011 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 #define __MSVCRT_VERSION__ 0x0700
     9 #define WINVER 0x0501
    10 #define WIN32_LEAN_AND_MEAN
    11 #include <malloc.h>
    12 #include <assert.h>
    13 #include <windows.h>
    14 #include <mmreg.h>
    15 #include <mmsystem.h>
    16 #include <process.h>
    17 #include <stdlib.h>
    18 #include "cubeb/cubeb.h"
    19 #include "cubeb-internal.h"
    21 /* This is missing from the MinGW headers. Use a safe fallback. */
    22 #if !defined(MEMORY_ALLOCATION_ALIGNMENT)
    23 #define MEMORY_ALLOCATION_ALIGNMENT 16
    24 #endif
    26 #define CUBEB_STREAM_MAX 32
    27 #define NBUFS 4
    29 const GUID KSDATAFORMAT_SUBTYPE_PCM =
    30 { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
    31 const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
    32 { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
    34 struct cubeb_stream_item {
    35   SLIST_ENTRY head;
    36   cubeb_stream * stream;
    37 };
    39 static struct cubeb_ops const winmm_ops;
    41 struct cubeb {
    42   struct cubeb_ops const * ops;
    43   HANDLE event;
    44   HANDLE thread;
    45   int shutdown;
    46   PSLIST_HEADER work;
    47   CRITICAL_SECTION lock;
    48   unsigned int active_streams;
    49   unsigned int minimum_latency;
    50 };
    52 struct cubeb_stream {
    53   cubeb * context;
    54   cubeb_stream_params params;
    55   cubeb_data_callback data_callback;
    56   cubeb_state_callback state_callback;
    57   void * user_ptr;
    58   WAVEHDR buffers[NBUFS];
    59   size_t buffer_size;
    60   int next_buffer;
    61   int free_buffers;
    62   int shutdown;
    63   int draining;
    64   HANDLE event;
    65   HWAVEOUT waveout;
    66   CRITICAL_SECTION lock;
    67   uint64_t written;
    68 };
    70 static size_t
    71 bytes_per_frame(cubeb_stream_params params)
    72 {
    73   size_t bytes;
    75   switch (params.format) {
    76   case CUBEB_SAMPLE_S16LE:
    77     bytes = sizeof(signed short);
    78     break;
    79   case CUBEB_SAMPLE_FLOAT32LE:
    80     bytes = sizeof(float);
    81     break;
    82   default:
    83     assert(0);
    84   }
    86   return bytes * params.channels;
    87 }
    89 static WAVEHDR *
    90 winmm_get_next_buffer(cubeb_stream * stm)
    91 {
    92   WAVEHDR * hdr = NULL;
    94   assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
    95   hdr = &stm->buffers[stm->next_buffer];
    96   assert(hdr->dwFlags & WHDR_PREPARED ||
    97          (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
    98   stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
    99   stm->free_buffers -= 1;
   101   return hdr;
   102 }
   104 static void
   105 winmm_refill_stream(cubeb_stream * stm)
   106 {
   107   WAVEHDR * hdr;
   108   long got;
   109   long wanted;
   110   MMRESULT r;
   112   EnterCriticalSection(&stm->lock);
   113   stm->free_buffers += 1;
   114   assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
   116   if (stm->draining) {
   117     LeaveCriticalSection(&stm->lock);
   118     if (stm->free_buffers == NBUFS) {
   119       stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
   120     }
   121     SetEvent(stm->event);
   122     return;
   123   }
   125   if (stm->shutdown) {
   126     LeaveCriticalSection(&stm->lock);
   127     SetEvent(stm->event);
   128     return;
   129   }
   131   hdr = winmm_get_next_buffer(stm);
   133   wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
   135   /* It is assumed that the caller is holding this lock.  It must be dropped
   136      during the callback to avoid deadlocks. */
   137   LeaveCriticalSection(&stm->lock);
   138   got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted);
   139   EnterCriticalSection(&stm->lock);
   140   if (got < 0) {
   141     LeaveCriticalSection(&stm->lock);
   142     /* XXX handle this case */
   143     assert(0);
   144     return;
   145   } else if (got < wanted) {
   146     stm->draining = 1;
   147   }
   148   stm->written += got;
   150   assert(hdr->dwFlags & WHDR_PREPARED);
   152   hdr->dwBufferLength = got * bytes_per_frame(stm->params);
   153   assert(hdr->dwBufferLength <= stm->buffer_size);
   155   r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
   156   if (r != MMSYSERR_NOERROR) {
   157     LeaveCriticalSection(&stm->lock);
   158     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
   159     return;
   160   }
   162   LeaveCriticalSection(&stm->lock);
   163 }
   165 static unsigned __stdcall
   166 winmm_buffer_thread(void * user_ptr)
   167 {
   168   cubeb * ctx = (cubeb *) user_ptr;
   169   assert(ctx);
   171   for (;;) {
   172     DWORD rv;
   173     PSLIST_ENTRY item;
   175     rv = WaitForSingleObject(ctx->event, INFINITE);
   176     assert(rv == WAIT_OBJECT_0);
   178     /* Process work items in batches so that a single stream can't
   179        starve the others by continuously adding new work to the top of
   180        the work item stack. */
   181     item = InterlockedFlushSList(ctx->work);
   182     while (item != NULL) {
   183       PSLIST_ENTRY tmp = item;
   184       winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream);
   185       item = item->Next;
   186       _aligned_free(tmp);
   187     }
   189     if (ctx->shutdown) {
   190       break;
   191     }
   192   }
   194   return 0;
   195 }
   197 static void CALLBACK
   198 winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
   199 {
   200   cubeb_stream * stm = (cubeb_stream *) user_ptr;
   201   struct cubeb_stream_item * item;
   203   if (msg != WOM_DONE) {
   204     return;
   205   }
   207   item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
   208   assert(item);
   209   item->stream = stm;
   210   InterlockedPushEntrySList(stm->context->work, &item->head);
   212   SetEvent(stm->context->event);
   213 }
   215 static unsigned int
   216 calculate_minimum_latency(void)
   217 {
   218   OSVERSIONINFOEX osvi;
   219   DWORDLONG mask;
   221   /* Running under Terminal Services results in underruns with low latency. */
   222   if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
   223     return 500;
   224   }
   226   /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
   227   memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
   228   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
   229   osvi.dwMajorVersion = 6;
   230   osvi.dwMinorVersion = 0;
   232   mask = 0;
   233   VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
   234   VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
   236   if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
   237     return 200;
   238   }
   240   return 100;
   241 }
   243 static void winmm_destroy(cubeb * ctx);
   245 /*static*/ int
   246 winmm_init(cubeb ** context, char const * context_name)
   247 {
   248   cubeb * ctx;
   250   assert(context);
   251   *context = NULL;
   253   ctx = calloc(1, sizeof(*ctx));
   254   assert(ctx);
   256   ctx->ops = &winmm_ops;
   258   ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
   259   assert(ctx->work);
   260   InitializeSListHead(ctx->work);
   262   ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
   263   if (!ctx->event) {
   264     winmm_destroy(ctx);
   265     return CUBEB_ERROR;
   266   }
   268   ctx->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
   269   if (!ctx->thread) {
   270     winmm_destroy(ctx);
   271     return CUBEB_ERROR;
   272   }
   274   SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
   276   InitializeCriticalSection(&ctx->lock);
   277   ctx->active_streams = 0;
   279   ctx->minimum_latency = calculate_minimum_latency();
   281   *context = ctx;
   283   return CUBEB_OK;
   284 }
   286 static char const *
   287 winmm_get_backend_id(cubeb * ctx)
   288 {
   289   return "winmm";
   290 }
   292 static void
   293 winmm_destroy(cubeb * ctx)
   294 {
   295   DWORD rv;
   297   assert(ctx->active_streams == 0);
   298   assert(!InterlockedPopEntrySList(ctx->work));
   300   DeleteCriticalSection(&ctx->lock);
   302   if (ctx->thread) {
   303     ctx->shutdown = 1;
   304     SetEvent(ctx->event);
   305     rv = WaitForSingleObject(ctx->thread, INFINITE);
   306     assert(rv == WAIT_OBJECT_0);
   307     CloseHandle(ctx->thread);
   308   }
   310   if (ctx->event) {
   311     CloseHandle(ctx->event);
   312   }
   314   _aligned_free(ctx->work);
   316   free(ctx);
   317 }
   319 static void winmm_stream_destroy(cubeb_stream * stm);
   321 static int
   322 winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
   323                   cubeb_stream_params stream_params, unsigned int latency,
   324                   cubeb_data_callback data_callback,
   325                   cubeb_state_callback state_callback,
   326                   void * user_ptr)
   327 {
   328   MMRESULT r;
   329   WAVEFORMATEXTENSIBLE wfx;
   330   cubeb_stream * stm;
   331   int i;
   332   size_t bufsz;
   334   assert(context);
   335   assert(stream);
   337   *stream = NULL;
   339   memset(&wfx, 0, sizeof(wfx));
   340   if (stream_params.channels > 2) {
   341     wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
   342     wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
   343   } else {
   344     wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
   345     if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE) {
   346       wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
   347     }
   348     wfx.Format.cbSize = 0;
   349   }
   350   wfx.Format.nChannels = stream_params.channels;
   351   wfx.Format.nSamplesPerSec = stream_params.rate;
   353   /* XXX fix channel mappings */
   354   wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
   356   switch (stream_params.format) {
   357   case CUBEB_SAMPLE_S16LE:
   358     wfx.Format.wBitsPerSample = 16;
   359     wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
   360     break;
   361   case CUBEB_SAMPLE_FLOAT32LE:
   362     wfx.Format.wBitsPerSample = 32;
   363     wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
   364     break;
   365   default:
   366     return CUBEB_ERROR_INVALID_FORMAT;
   367   }
   369   wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
   370   wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
   371   wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
   373   EnterCriticalSection(&context->lock);
   374   /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
   375      many streams are active at once, a subset of them will not consume (via
   376      playback) or release (via waveOutReset) their buffers. */
   377   if (context->active_streams >= CUBEB_STREAM_MAX) {
   378     LeaveCriticalSection(&context->lock);
   379     return CUBEB_ERROR;
   380   }
   381   context->active_streams += 1;
   382   LeaveCriticalSection(&context->lock);
   384   stm = calloc(1, sizeof(*stm));
   385   assert(stm);
   387   stm->context = context;
   389   stm->params = stream_params;
   391   stm->data_callback = data_callback;
   392   stm->state_callback = state_callback;
   393   stm->user_ptr = user_ptr;
   394   stm->written = 0;
   396   if (latency < context->minimum_latency) {
   397     latency = context->minimum_latency;
   398   }
   400   bufsz = (size_t) (stm->params.rate / 1000.0 * latency * bytes_per_frame(stm->params) / NBUFS);
   401   if (bufsz % bytes_per_frame(stm->params) != 0) {
   402     bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
   403   }
   404   assert(bufsz % bytes_per_frame(stm->params) == 0);
   406   stm->buffer_size = bufsz;
   408   InitializeCriticalSection(&stm->lock);
   410   stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
   411   if (!stm->event) {
   412     winmm_stream_destroy(stm);
   413     return CUBEB_ERROR;
   414   }
   416   /* winmm_buffer_callback will be called during waveOutOpen, so all
   417      other initialization must be complete before calling it. */
   418   r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
   419                   (DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm,
   420                   CALLBACK_FUNCTION);
   421   if (r != MMSYSERR_NOERROR) {
   422     winmm_stream_destroy(stm);
   423     return CUBEB_ERROR;
   424   }
   426   r = waveOutPause(stm->waveout);
   427   if (r != MMSYSERR_NOERROR) {
   428     winmm_stream_destroy(stm);
   429     return CUBEB_ERROR;
   430   }
   432   for (i = 0; i < NBUFS; ++i) {
   433     WAVEHDR * hdr = &stm->buffers[i];
   435     hdr->lpData = calloc(1, bufsz);
   436     assert(hdr->lpData);
   437     hdr->dwBufferLength = bufsz;
   438     hdr->dwFlags = 0;
   440     r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
   441     if (r != MMSYSERR_NOERROR) {
   442       winmm_stream_destroy(stm);
   443       return CUBEB_ERROR;
   444     }
   446     winmm_refill_stream(stm);
   447   }
   449   *stream = stm;
   451   return CUBEB_OK;
   452 }
   454 static void
   455 winmm_stream_destroy(cubeb_stream * stm)
   456 {
   457   DWORD rv;
   458   int i;
   459   int enqueued;
   461   if (stm->waveout) {
   462     EnterCriticalSection(&stm->lock);
   463     stm->shutdown = 1;
   465     waveOutReset(stm->waveout);
   467     enqueued = NBUFS - stm->free_buffers;
   468     LeaveCriticalSection(&stm->lock);
   470     /* Wait for all blocks to complete. */
   471     while (enqueued > 0) {
   472       rv = WaitForSingleObject(stm->event, INFINITE);
   473       assert(rv == WAIT_OBJECT_0);
   475       EnterCriticalSection(&stm->lock);
   476       enqueued = NBUFS - stm->free_buffers;
   477       LeaveCriticalSection(&stm->lock);
   478     }
   480     EnterCriticalSection(&stm->lock);
   482     for (i = 0; i < NBUFS; ++i) {
   483       if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
   484         waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
   485       }
   486     }
   488     waveOutClose(stm->waveout);
   490     LeaveCriticalSection(&stm->lock);
   491   }
   493   if (stm->event) {
   494     CloseHandle(stm->event);
   495   }
   497   DeleteCriticalSection(&stm->lock);
   499   for (i = 0; i < NBUFS; ++i) {
   500     free(stm->buffers[i].lpData);
   501   }
   503   EnterCriticalSection(&stm->context->lock);
   504   assert(stm->context->active_streams >= 1);
   505   stm->context->active_streams -= 1;
   506   LeaveCriticalSection(&stm->context->lock);
   508   free(stm);
   509 }
   511 static int
   512 winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
   513 {
   514   assert(ctx && max_channels);
   516   /* We don't support more than two channels in this backend. */
   517   *max_channels = 2;
   519   return CUBEB_OK;
   520 }
   522 static int
   523 winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
   524 {
   525   // 100ms minimum, if we are not in a bizarre configuration.
   526   *latency = ctx->minimum_latency;
   528   return CUBEB_OK;
   529 }
   531 static int
   532 winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
   533 {
   534   WAVEOUTCAPS woc;
   535   MMRESULT r;
   537   r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
   538   if (r != MMSYSERR_NOERROR) {
   539     return CUBEB_ERROR;
   540   }
   542   /* Check if we support 48kHz, but not 44.1kHz. */
   543   if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
   544       woc.dwFormats & WAVE_FORMAT_48S16) {
   545     *rate = 48000;
   546     return CUBEB_OK;
   547   }
   548   /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
   549   *rate = 44100;
   551   return CUBEB_OK;
   552 }
   554 static int
   555 winmm_stream_start(cubeb_stream * stm)
   556 {
   557   MMRESULT r;
   559   EnterCriticalSection(&stm->lock);
   560   r = waveOutRestart(stm->waveout);
   561   LeaveCriticalSection(&stm->lock);
   563   if (r != MMSYSERR_NOERROR) {
   564     return CUBEB_ERROR;
   565   }
   567   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
   569   return CUBEB_OK;
   570 }
   572 static int
   573 winmm_stream_stop(cubeb_stream * stm)
   574 {
   575   MMRESULT r;
   577   EnterCriticalSection(&stm->lock);
   578   r = waveOutPause(stm->waveout);
   579   LeaveCriticalSection(&stm->lock);
   581   if (r != MMSYSERR_NOERROR) {
   582     return CUBEB_ERROR;
   583   }
   585   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
   587   return CUBEB_OK;
   588 }
   590 static int
   591 winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
   592 {
   593   MMRESULT r;
   594   MMTIME time;
   596   EnterCriticalSection(&stm->lock);
   597   time.wType = TIME_SAMPLES;
   598   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
   599   LeaveCriticalSection(&stm->lock);
   601   if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
   602     return CUBEB_ERROR;
   603   }
   605   *position = time.u.sample;
   607   return CUBEB_OK;
   608 }
   610 static int
   611 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
   612 {
   613   MMRESULT r;
   614   MMTIME time;
   615   uint64_t written;
   617   EnterCriticalSection(&stm->lock);
   618   time.wType = TIME_SAMPLES;
   619   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
   620   written = stm->written;
   621   LeaveCriticalSection(&stm->lock);
   623   *latency = written - time.u.sample;
   625   return CUBEB_OK;
   626 }
   628 static struct cubeb_ops const winmm_ops = {
   629   /*.init =*/ winmm_init,
   630   /*.get_backend_id =*/ winmm_get_backend_id,
   631   /*.get_max_channel_count=*/ winmm_get_max_channel_count,
   632   /*.get_min_latency=*/ winmm_get_min_latency,
   633   /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
   634   /*.destroy =*/ winmm_destroy,
   635   /*.stream_init =*/ winmm_stream_init,
   636   /*.stream_destroy =*/ winmm_stream_destroy,
   637   /*.stream_start =*/ winmm_stream_start,
   638   /*.stream_stop =*/ winmm_stream_stop,
   639   /*.stream_get_position =*/ winmm_stream_get_position,
   640   /*.stream_get_latency = */ winmm_stream_get_latency
   641 };

mercurial