media/libcubeb/src/cubeb_audiotrack.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 © 2013 Mozilla Foundation
     3  *
     4  * This program is made available under an ISC-style license.  See the
     5  * accompanying file LICENSE for details.
     6  */
     8 #if !defined(NDEBUG)
     9 #define NDEBUG
    10 #endif
    11 #include <assert.h>
    12 #include <pthread.h>
    13 #include <stdlib.h>
    14 #include <time.h>
    15 #include <dlfcn.h>
    16 #include "android/log.h"
    18 #include "cubeb/cubeb.h"
    19 #include "cubeb-internal.h"
    20 #include "android/audiotrack_definitions.h"
    22 #ifndef ALOG
    23 #if defined(DEBUG) || defined(FORCE_ALOG)
    24 #define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
    25 #else
    26 #define ALOG(args...)
    27 #endif
    28 #endif
    30 /**
    31  * A lot of bytes for safety. It should be possible to bring this down a bit. */
    32 #define SIZE_AUDIOTRACK_INSTANCE 256
    34 /**
    35  * call dlsym to get the symbol |mangled_name|, handle the error and store the
    36  * pointer in |pointer|. Because depending on Android version, we want different
    37  * symbols, not finding a symbol is not an error. */
    38 #define DLSYM_DLERROR(mangled_name, pointer, lib)                        \
    39   do {                                                                   \
    40     pointer = dlsym(lib, mangled_name);                                  \
    41     if (!pointer) {                                                      \
    42       ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
    43     } else {                                                             \
    44       ALOG("%stm: OK", mangled_name);                                    \
    45     }                                                                    \
    46   } while(0);
    48 static struct cubeb_ops const audiotrack_ops;
    49 void audiotrack_destroy(cubeb * context);
    50 void audiotrack_stream_destroy(cubeb_stream * stream);
    52 struct AudioTrack {
    53                /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */
    54   /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate);
    55               /* if we have a recent ctor, but can't find the above symbol, we
    56                * can get the minimum frame count with this signature, and we are
    57                * running gingerbread. */
    58   /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate);
    59                /* if this symbol is not availble, and the next one is, we know
    60                 * we are on a Froyo (Android 2.2) device. */
    61                void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int);
    62                void* (*ctor_froyo)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int);
    63                void* (*dtor)(void* instance);
    64                void (*start)(void* instance);
    65                void (*pause)(void* instance);
    66                uint32_t (*latency)(void* instance);
    67                status_t (*check)(void* instance);
    68                status_t (*get_position)(void* instance, uint32_t* position);
    69               /* only used on froyo. */
    70   /* static */ int (*get_output_frame_count)(int* frame_count, int stream);
    71   /* static */ int (*get_output_latency)(uint32_t* latency, int stream);
    72   /* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
    73                status_t (*set_marker_position)(void* instance, unsigned int);
    75 };
    77 struct cubeb {
    78   struct cubeb_ops const * ops;
    79   void * library;
    80   struct AudioTrack klass;
    81 };
    83 struct cubeb_stream {
    84   cubeb * context;
    85   cubeb_stream_params params;
    86   cubeb_data_callback data_callback;
    87   cubeb_state_callback state_callback;
    88   void * instance;
    89   void * user_ptr;
    90   /* Number of frames that have been passed to the AudioTrack callback */
    91   long unsigned written;
    92   int draining;
    93 };
    95 static void
    96 audiotrack_refill(int event, void* user, void* info)
    97 {
    98   cubeb_stream * stream = user;
    99   switch (event) {
   100     case EVENT_MORE_DATA: {
   101       long got = 0;
   102       struct Buffer * b = (struct Buffer*)info;
   104       if (stream->draining) {
   105         return;
   106       }
   108       got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount);
   110       stream->written += got;
   112       if (got != (long)b->frameCount) {
   113         uint32_t p;
   114         stream->draining = 1;
   115         /* set a marker so we are notified when the are done draining, that is,
   116          * when every frame has been played by android. */
   117         stream->context->klass.set_marker_position(stream->instance, stream->written);
   118       }
   120       break;
   121     }
   122     case EVENT_UNDERRUN:
   123       ALOG("underrun in cubeb backend.");
   124       break;
   125     case EVENT_LOOP_END:
   126       assert(0 && "We don't support the loop feature of audiotrack.");
   127       break;
   128     case EVENT_MARKER:
   129       assert(stream->draining);
   130       stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
   131       break;
   132     case EVENT_NEW_POS:
   133       assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
   134       break;
   135     case EVENT_BUFFER_END:
   136       assert(0 && "Should not happen.");
   137       break;
   138   }
   139 }
   141 /* We are running on froyo if we found the right AudioTrack constructor */
   142 static int
   143 audiotrack_version_is_froyo(cubeb * ctx)
   144 {
   145   return ctx->klass.ctor_froyo != NULL;
   146 }
   148 /* We are running on gingerbread if we found the gingerbread signature for
   149  * getMinFrameCount */
   150 static int
   151 audiotrack_version_is_gingerbread(cubeb * ctx)
   152 {
   153   return ctx->klass.get_min_frame_count_gingerbread != NULL;
   154 }
   156 int
   157 audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
   158 {
   159   status_t status;
   160   /* Recent Android have a getMinFrameCount method. On Froyo, we have to compute it by hand. */
   161   if (audiotrack_version_is_froyo(ctx)) {
   162     int samplerate, frame_count, latency, min_buffer_count;
   163     status = ctx->klass.get_output_frame_count(&frame_count, params->stream_type);
   164     if (status) {
   165       ALOG("error getting the output frame count.");
   166       return CUBEB_ERROR;
   167     }
   168     status = ctx->klass.get_output_latency((uint32_t*)&latency, params->stream_type);
   169     if (status) {
   170       ALOG("error getting the output frame count.");
   171       return CUBEB_ERROR;
   172     }
   173     status = ctx->klass.get_output_samplingrate(&samplerate, params->stream_type);
   174     if (status) {
   175       ALOG("error getting the output frame count.");
   176       return CUBEB_ERROR;
   177     }
   179     /* Those numbers were found reading the Android source. It is the minimum
   180      * numbers that will be accepted by the AudioTrack class, hence yielding the
   181      * best latency possible.
   182      * See https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/media/libmedia/AudioTrack.cpp
   183      * around line 181 for Android 2.2 */
   184     min_buffer_count = latency / ((1000 * frame_count) / samplerate);
   185     min_buffer_count = min_buffer_count < 2 ? min_buffer_count : 2;
   186     *min_frame_count = (frame_count * params->rate * min_buffer_count) / samplerate;
   187     return CUBEB_OK;
   188   }
   189   /* Recent Android have a getMinFrameCount method. */
   190   if (!audiotrack_version_is_gingerbread(ctx)) {
   191     status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
   192   } else {
   193     status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
   194   }
   195   if (status != 0) {
   196     ALOG("error getting the min frame count");
   197     return CUBEB_ERROR;
   198   }
   199   return CUBEB_OK;
   200 }
   202 int
   203 audiotrack_init(cubeb ** context, char const * context_name)
   204 {
   205   cubeb * ctx;
   206   struct AudioTrack* c;
   208   assert(context);
   209   *context = NULL;
   211   ctx = calloc(1, sizeof(*ctx));
   212   assert(ctx);
   214   /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
   215    * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
   216    * the first call to a dlsym'ed function. Somehow this does not happen when
   217    * using only the name of the library. */
   218   ctx->library = dlopen("libmedia.so", RTLD_LAZY);
   219   if (!ctx->library) {
   220     ALOG("dlopen error: %s.", dlerror());
   221     free(ctx);
   222     return CUBEB_ERROR;
   223   }
   225   /* Recent Android first, then Froyo. */
   226   DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
   227   if (!ctx->klass.ctor) {
   228     DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i", ctx->klass.ctor_froyo, ctx->library);
   229     assert(ctx->klass.ctor_froyo);
   230   }
   231   DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
   233   DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
   234   DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
   236   DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
   238   /* |getMinFrameCount| is not available on Froyo, and is available on
   239    * gingerbread and ICS with a different signature. */
   240   if (audiotrack_version_is_froyo(ctx)) {
   241     DLSYM_DLERROR("_ZN7android11AudioSystem19getOutputFrameCountEPii", ctx->klass.get_output_frame_count, ctx->library);
   242     DLSYM_DLERROR("_ZN7android11AudioSystem16getOutputLatencyEPji", ctx->klass.get_output_latency, ctx->library);
   243   } else {
   244     DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
   245     if (!ctx->klass.get_min_frame_count) {
   246       DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
   247     }
   248   }
   250   DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
   251   DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
   252   DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
   253   DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
   255   /* check that we have a combination of symbol that makes sense */
   256   c = &ctx->klass;
   257   if(!((c->ctor || c->ctor_froyo) && /* at least on ctor. */
   258      c->dtor && c->latency && c->check &&
   259      /* at least one way to get the minimum frame count to request. */
   260      ((c->get_output_frame_count && c->get_output_latency && c->get_output_samplingrate) ||
   261       c->get_min_frame_count ||
   262       c->get_min_frame_count_gingerbread) &&
   263      c->start && c->pause && c->get_position && c->set_marker_position)) {
   264     ALOG("Could not find all the symbols we need.");
   265     audiotrack_destroy(ctx);
   266     return CUBEB_ERROR;
   267   }
   269   ctx->ops = &audiotrack_ops;
   271   *context = ctx;
   273   return CUBEB_OK;
   274 }
   276 char const *
   277 audiotrack_get_backend_id(cubeb * context)
   278 {
   279   return "audiotrack";
   280 }
   282 static int
   283 audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
   284 {
   285   assert(ctx && max_channels);
   287   /* The android mixer handles up to two channels, see
   288   http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
   289   *max_channels = 2;
   291   return CUBEB_OK;
   292 }
   294 static int
   295 audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
   296 {
   297   /* We always use the lowest latency possible when using this backend (see
   298    * audiotrack_stream_init), so this value is not going to be used. */
   299   int rv;
   301   rv = audiotrack_get_min_frame_count(ctx, &params, (int *)latency_ms);
   302   if (rv != CUBEB_OK) {
   303     return CUBEB_ERROR;
   304   }
   306   /* Convert to milliseconds. */
   307   *latency_ms = *latency_ms * 1000 / params.rate;
   309   return CUBEB_OK;
   310 }
   312 static int
   313 audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
   314 {
   315   status_t rv;
   317   rv = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
   319   return rv == 0 ? CUBEB_OK : CUBEB_ERROR;
   320 }
   322 void
   323 audiotrack_destroy(cubeb * context)
   324 {
   325   assert(context);
   327   dlclose(context->library);
   329   free(context);
   330 }
   332 int
   333 audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
   334                        cubeb_stream_params stream_params, unsigned int latency,
   335                        cubeb_data_callback data_callback,
   336                        cubeb_state_callback state_callback,
   337                        void * user_ptr)
   338 {
   339   cubeb_stream * stm;
   340   int32_t channels;
   341   uint32_t min_frame_count;
   343   assert(ctx && stream);
   345   if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE ||
   346       stream_params.format == CUBEB_SAMPLE_FLOAT32BE) {
   347     return CUBEB_ERROR_INVALID_FORMAT;
   348   }
   350   if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) {
   351     return CUBEB_ERROR;
   352   }
   354   stm = calloc(1, sizeof(*stm));
   355   assert(stm);
   357   stm->context = ctx;
   358   stm->data_callback = data_callback;
   359   stm->state_callback = state_callback;
   360   stm->user_ptr = user_ptr;
   361   stm->params = stream_params;
   363   stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
   364   (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
   365   assert(stm->instance && "cubeb: EOM");
   367   /* gingerbread uses old channel layout enum */
   368   if (audiotrack_version_is_froyo(ctx) || audiotrack_version_is_gingerbread(ctx)) {
   369     channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
   370   } else {
   371     channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
   372   }
   374   if (audiotrack_version_is_froyo(ctx)) {
   375     ctx->klass.ctor_froyo(stm->instance,
   376                           stm->params.stream_type,
   377                           stm->params.rate,
   378                           AUDIO_FORMAT_PCM_16_BIT,
   379                           channels,
   380                           min_frame_count,
   381                           0,
   382                           audiotrack_refill,
   383                           stm,
   384                           0);
   385   } else {
   386     ctx->klass.ctor(stm->instance,
   387                     stm->params.stream_type,
   388                     stm->params.rate,
   389                     AUDIO_FORMAT_PCM_16_BIT,
   390                     channels,
   391                     min_frame_count,
   392                     0,
   393                     audiotrack_refill,
   394                     stm,
   395                     0,
   396                     0);
   397   }
   399   assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
   401   if (ctx->klass.check(stm->instance)) {
   402     ALOG("stream not initialized properly.");
   403     audiotrack_stream_destroy(stm);
   404     return CUBEB_ERROR;
   405   }
   407   *stream = stm;
   409   return CUBEB_OK;
   410 }
   412 void
   413 audiotrack_stream_destroy(cubeb_stream * stream)
   414 {
   415   assert(stream->context);
   417   stream->context->klass.dtor(stream->instance);
   419   free(stream->instance);
   420   stream->instance = NULL;
   421   free(stream);
   422 }
   424 int
   425 audiotrack_stream_start(cubeb_stream * stream)
   426 {
   427   assert(stream->instance);
   429   stream->context->klass.start(stream->instance);
   430   stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
   432   return CUBEB_OK;
   433 }
   435 int
   436 audiotrack_stream_stop(cubeb_stream * stream)
   437 {
   438   assert(stream->instance);
   440   stream->context->klass.pause(stream->instance);
   441   stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
   443   return CUBEB_OK;
   444 }
   446 int
   447 audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
   448 {
   449   uint32_t p;
   451   assert(stream->instance && position);
   452   stream->context->klass.get_position(stream->instance, &p);
   453   *position = p;
   455   return CUBEB_OK;
   456 }
   458 int
   459 audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
   460 {
   461   assert(stream->instance && latency);
   463   /* Android returns the latency in ms, we want it in frames. */
   464   *latency = stream->context->klass.latency(stream->instance);
   465   /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
   466   *latency = (*latency * stream->params.rate) / 1000;
   468   return 0;
   469 }
   471 static struct cubeb_ops const audiotrack_ops = {
   472   .init = audiotrack_init,
   473   .get_backend_id = audiotrack_get_backend_id,
   474   .get_max_channel_count = audiotrack_get_max_channel_count,
   475   .get_min_latency = audiotrack_get_min_latency,
   476   .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
   477   .destroy = audiotrack_destroy,
   478   .stream_init = audiotrack_stream_init,
   479   .stream_destroy = audiotrack_stream_destroy,
   480   .stream_start = audiotrack_stream_start,
   481   .stream_stop = audiotrack_stream_stop,
   482   .stream_get_position = audiotrack_stream_get_position,
   483   .stream_get_latency = audiotrack_stream_get_latency
   484 };

mercurial