media/libcubeb/src/cubeb_audiotrack.c

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/media/libcubeb/src/cubeb_audiotrack.c	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,484 @@
     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 +
    1.11 +#if !defined(NDEBUG)
    1.12 +#define NDEBUG
    1.13 +#endif
    1.14 +#include <assert.h>
    1.15 +#include <pthread.h>
    1.16 +#include <stdlib.h>
    1.17 +#include <time.h>
    1.18 +#include <dlfcn.h>
    1.19 +#include "android/log.h"
    1.20 +
    1.21 +#include "cubeb/cubeb.h"
    1.22 +#include "cubeb-internal.h"
    1.23 +#include "android/audiotrack_definitions.h"
    1.24 +
    1.25 +#ifndef ALOG
    1.26 +#if defined(DEBUG) || defined(FORCE_ALOG)
    1.27 +#define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
    1.28 +#else
    1.29 +#define ALOG(args...)
    1.30 +#endif
    1.31 +#endif
    1.32 +
    1.33 +/**
    1.34 + * A lot of bytes for safety. It should be possible to bring this down a bit. */
    1.35 +#define SIZE_AUDIOTRACK_INSTANCE 256
    1.36 +
    1.37 +/**
    1.38 + * call dlsym to get the symbol |mangled_name|, handle the error and store the
    1.39 + * pointer in |pointer|. Because depending on Android version, we want different
    1.40 + * symbols, not finding a symbol is not an error. */
    1.41 +#define DLSYM_DLERROR(mangled_name, pointer, lib)                        \
    1.42 +  do {                                                                   \
    1.43 +    pointer = dlsym(lib, mangled_name);                                  \
    1.44 +    if (!pointer) {                                                      \
    1.45 +      ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
    1.46 +    } else {                                                             \
    1.47 +      ALOG("%stm: OK", mangled_name);                                    \
    1.48 +    }                                                                    \
    1.49 +  } while(0);
    1.50 +
    1.51 +static struct cubeb_ops const audiotrack_ops;
    1.52 +void audiotrack_destroy(cubeb * context);
    1.53 +void audiotrack_stream_destroy(cubeb_stream * stream);
    1.54 +
    1.55 +struct AudioTrack {
    1.56 +               /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */
    1.57 +  /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate);
    1.58 +              /* if we have a recent ctor, but can't find the above symbol, we
    1.59 +               * can get the minimum frame count with this signature, and we are
    1.60 +               * running gingerbread. */
    1.61 +  /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate);
    1.62 +               /* if this symbol is not availble, and the next one is, we know
    1.63 +                * we are on a Froyo (Android 2.2) device. */
    1.64 +               void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int);
    1.65 +               void* (*ctor_froyo)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int);
    1.66 +               void* (*dtor)(void* instance);
    1.67 +               void (*start)(void* instance);
    1.68 +               void (*pause)(void* instance);
    1.69 +               uint32_t (*latency)(void* instance);
    1.70 +               status_t (*check)(void* instance);
    1.71 +               status_t (*get_position)(void* instance, uint32_t* position);
    1.72 +              /* only used on froyo. */
    1.73 +  /* static */ int (*get_output_frame_count)(int* frame_count, int stream);
    1.74 +  /* static */ int (*get_output_latency)(uint32_t* latency, int stream);
    1.75 +  /* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
    1.76 +               status_t (*set_marker_position)(void* instance, unsigned int);
    1.77 +
    1.78 +};
    1.79 +
    1.80 +struct cubeb {
    1.81 +  struct cubeb_ops const * ops;
    1.82 +  void * library;
    1.83 +  struct AudioTrack klass;
    1.84 +};
    1.85 +
    1.86 +struct cubeb_stream {
    1.87 +  cubeb * context;
    1.88 +  cubeb_stream_params params;
    1.89 +  cubeb_data_callback data_callback;
    1.90 +  cubeb_state_callback state_callback;
    1.91 +  void * instance;
    1.92 +  void * user_ptr;
    1.93 +  /* Number of frames that have been passed to the AudioTrack callback */
    1.94 +  long unsigned written;
    1.95 +  int draining;
    1.96 +};
    1.97 +
    1.98 +static void
    1.99 +audiotrack_refill(int event, void* user, void* info)
   1.100 +{
   1.101 +  cubeb_stream * stream = user;
   1.102 +  switch (event) {
   1.103 +    case EVENT_MORE_DATA: {
   1.104 +      long got = 0;
   1.105 +      struct Buffer * b = (struct Buffer*)info;
   1.106 +
   1.107 +      if (stream->draining) {
   1.108 +        return;
   1.109 +      }
   1.110 +
   1.111 +      got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount);
   1.112 +
   1.113 +      stream->written += got;
   1.114 +
   1.115 +      if (got != (long)b->frameCount) {
   1.116 +        uint32_t p;
   1.117 +        stream->draining = 1;
   1.118 +        /* set a marker so we are notified when the are done draining, that is,
   1.119 +         * when every frame has been played by android. */
   1.120 +        stream->context->klass.set_marker_position(stream->instance, stream->written);
   1.121 +      }
   1.122 +
   1.123 +      break;
   1.124 +    }
   1.125 +    case EVENT_UNDERRUN:
   1.126 +      ALOG("underrun in cubeb backend.");
   1.127 +      break;
   1.128 +    case EVENT_LOOP_END:
   1.129 +      assert(0 && "We don't support the loop feature of audiotrack.");
   1.130 +      break;
   1.131 +    case EVENT_MARKER:
   1.132 +      assert(stream->draining);
   1.133 +      stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
   1.134 +      break;
   1.135 +    case EVENT_NEW_POS:
   1.136 +      assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
   1.137 +      break;
   1.138 +    case EVENT_BUFFER_END:
   1.139 +      assert(0 && "Should not happen.");
   1.140 +      break;
   1.141 +  }
   1.142 +}
   1.143 +
   1.144 +/* We are running on froyo if we found the right AudioTrack constructor */
   1.145 +static int
   1.146 +audiotrack_version_is_froyo(cubeb * ctx)
   1.147 +{
   1.148 +  return ctx->klass.ctor_froyo != NULL;
   1.149 +}
   1.150 +
   1.151 +/* We are running on gingerbread if we found the gingerbread signature for
   1.152 + * getMinFrameCount */
   1.153 +static int
   1.154 +audiotrack_version_is_gingerbread(cubeb * ctx)
   1.155 +{
   1.156 +  return ctx->klass.get_min_frame_count_gingerbread != NULL;
   1.157 +}
   1.158 +
   1.159 +int
   1.160 +audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
   1.161 +{
   1.162 +  status_t status;
   1.163 +  /* Recent Android have a getMinFrameCount method. On Froyo, we have to compute it by hand. */
   1.164 +  if (audiotrack_version_is_froyo(ctx)) {
   1.165 +    int samplerate, frame_count, latency, min_buffer_count;
   1.166 +    status = ctx->klass.get_output_frame_count(&frame_count, params->stream_type);
   1.167 +    if (status) {
   1.168 +      ALOG("error getting the output frame count.");
   1.169 +      return CUBEB_ERROR;
   1.170 +    }
   1.171 +    status = ctx->klass.get_output_latency((uint32_t*)&latency, params->stream_type);
   1.172 +    if (status) {
   1.173 +      ALOG("error getting the output frame count.");
   1.174 +      return CUBEB_ERROR;
   1.175 +    }
   1.176 +    status = ctx->klass.get_output_samplingrate(&samplerate, params->stream_type);
   1.177 +    if (status) {
   1.178 +      ALOG("error getting the output frame count.");
   1.179 +      return CUBEB_ERROR;
   1.180 +    }
   1.181 +
   1.182 +    /* Those numbers were found reading the Android source. It is the minimum
   1.183 +     * numbers that will be accepted by the AudioTrack class, hence yielding the
   1.184 +     * best latency possible.
   1.185 +     * See https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/media/libmedia/AudioTrack.cpp
   1.186 +     * around line 181 for Android 2.2 */
   1.187 +    min_buffer_count = latency / ((1000 * frame_count) / samplerate);
   1.188 +    min_buffer_count = min_buffer_count < 2 ? min_buffer_count : 2;
   1.189 +    *min_frame_count = (frame_count * params->rate * min_buffer_count) / samplerate;
   1.190 +    return CUBEB_OK;
   1.191 +  }
   1.192 +  /* Recent Android have a getMinFrameCount method. */
   1.193 +  if (!audiotrack_version_is_gingerbread(ctx)) {
   1.194 +    status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
   1.195 +  } else {
   1.196 +    status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
   1.197 +  }
   1.198 +  if (status != 0) {
   1.199 +    ALOG("error getting the min frame count");
   1.200 +    return CUBEB_ERROR;
   1.201 +  }
   1.202 +  return CUBEB_OK;
   1.203 +}
   1.204 +
   1.205 +int
   1.206 +audiotrack_init(cubeb ** context, char const * context_name)
   1.207 +{
   1.208 +  cubeb * ctx;
   1.209 +  struct AudioTrack* c;
   1.210 +
   1.211 +  assert(context);
   1.212 +  *context = NULL;
   1.213 +
   1.214 +  ctx = calloc(1, sizeof(*ctx));
   1.215 +  assert(ctx);
   1.216 +
   1.217 +  /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
   1.218 +   * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
   1.219 +   * the first call to a dlsym'ed function. Somehow this does not happen when
   1.220 +   * using only the name of the library. */
   1.221 +  ctx->library = dlopen("libmedia.so", RTLD_LAZY);
   1.222 +  if (!ctx->library) {
   1.223 +    ALOG("dlopen error: %s.", dlerror());
   1.224 +    free(ctx);
   1.225 +    return CUBEB_ERROR;
   1.226 +  }
   1.227 +
   1.228 +  /* Recent Android first, then Froyo. */
   1.229 +  DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
   1.230 +  if (!ctx->klass.ctor) {
   1.231 +    DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i", ctx->klass.ctor_froyo, ctx->library);
   1.232 +    assert(ctx->klass.ctor_froyo);
   1.233 +  }
   1.234 +  DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
   1.235 +
   1.236 +  DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
   1.237 +  DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
   1.238 +
   1.239 +  DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
   1.240 +
   1.241 +  /* |getMinFrameCount| is not available on Froyo, and is available on
   1.242 +   * gingerbread and ICS with a different signature. */
   1.243 +  if (audiotrack_version_is_froyo(ctx)) {
   1.244 +    DLSYM_DLERROR("_ZN7android11AudioSystem19getOutputFrameCountEPii", ctx->klass.get_output_frame_count, ctx->library);
   1.245 +    DLSYM_DLERROR("_ZN7android11AudioSystem16getOutputLatencyEPji", ctx->klass.get_output_latency, ctx->library);
   1.246 +  } else {
   1.247 +    DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
   1.248 +    if (!ctx->klass.get_min_frame_count) {
   1.249 +      DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
   1.250 +    }
   1.251 +  }
   1.252 +
   1.253 +  DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
   1.254 +  DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
   1.255 +  DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
   1.256 +  DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
   1.257 +
   1.258 +  /* check that we have a combination of symbol that makes sense */
   1.259 +  c = &ctx->klass;
   1.260 +  if(!((c->ctor || c->ctor_froyo) && /* at least on ctor. */
   1.261 +     c->dtor && c->latency && c->check &&
   1.262 +     /* at least one way to get the minimum frame count to request. */
   1.263 +     ((c->get_output_frame_count && c->get_output_latency && c->get_output_samplingrate) ||
   1.264 +      c->get_min_frame_count ||
   1.265 +      c->get_min_frame_count_gingerbread) &&
   1.266 +     c->start && c->pause && c->get_position && c->set_marker_position)) {
   1.267 +    ALOG("Could not find all the symbols we need.");
   1.268 +    audiotrack_destroy(ctx);
   1.269 +    return CUBEB_ERROR;
   1.270 +  }
   1.271 +
   1.272 +  ctx->ops = &audiotrack_ops;
   1.273 +
   1.274 +  *context = ctx;
   1.275 +
   1.276 +  return CUBEB_OK;
   1.277 +}
   1.278 +
   1.279 +char const *
   1.280 +audiotrack_get_backend_id(cubeb * context)
   1.281 +{
   1.282 +  return "audiotrack";
   1.283 +}
   1.284 +
   1.285 +static int
   1.286 +audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
   1.287 +{
   1.288 +  assert(ctx && max_channels);
   1.289 +
   1.290 +  /* The android mixer handles up to two channels, see
   1.291 +  http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
   1.292 +  *max_channels = 2;
   1.293 +
   1.294 +  return CUBEB_OK;
   1.295 +}
   1.296 +
   1.297 +static int
   1.298 +audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
   1.299 +{
   1.300 +  /* We always use the lowest latency possible when using this backend (see
   1.301 +   * audiotrack_stream_init), so this value is not going to be used. */
   1.302 +  int rv;
   1.303 +
   1.304 +  rv = audiotrack_get_min_frame_count(ctx, &params, (int *)latency_ms);
   1.305 +  if (rv != CUBEB_OK) {
   1.306 +    return CUBEB_ERROR;
   1.307 +  }
   1.308 +
   1.309 +  /* Convert to milliseconds. */
   1.310 +  *latency_ms = *latency_ms * 1000 / params.rate;
   1.311 +
   1.312 +  return CUBEB_OK;
   1.313 +}
   1.314 +
   1.315 +static int
   1.316 +audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
   1.317 +{
   1.318 +  status_t rv;
   1.319 +
   1.320 +  rv = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
   1.321 +
   1.322 +  return rv == 0 ? CUBEB_OK : CUBEB_ERROR;
   1.323 +}
   1.324 +
   1.325 +void
   1.326 +audiotrack_destroy(cubeb * context)
   1.327 +{
   1.328 +  assert(context);
   1.329 +
   1.330 +  dlclose(context->library);
   1.331 +
   1.332 +  free(context);
   1.333 +}
   1.334 +
   1.335 +int
   1.336 +audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
   1.337 +                       cubeb_stream_params stream_params, unsigned int latency,
   1.338 +                       cubeb_data_callback data_callback,
   1.339 +                       cubeb_state_callback state_callback,
   1.340 +                       void * user_ptr)
   1.341 +{
   1.342 +  cubeb_stream * stm;
   1.343 +  int32_t channels;
   1.344 +  uint32_t min_frame_count;
   1.345 +
   1.346 +  assert(ctx && stream);
   1.347 +
   1.348 +  if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE ||
   1.349 +      stream_params.format == CUBEB_SAMPLE_FLOAT32BE) {
   1.350 +    return CUBEB_ERROR_INVALID_FORMAT;
   1.351 +  }
   1.352 +
   1.353 +  if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) {
   1.354 +    return CUBEB_ERROR;
   1.355 +  }
   1.356 +
   1.357 +  stm = calloc(1, sizeof(*stm));
   1.358 +  assert(stm);
   1.359 +
   1.360 +  stm->context = ctx;
   1.361 +  stm->data_callback = data_callback;
   1.362 +  stm->state_callback = state_callback;
   1.363 +  stm->user_ptr = user_ptr;
   1.364 +  stm->params = stream_params;
   1.365 +
   1.366 +  stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
   1.367 +  (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
   1.368 +  assert(stm->instance && "cubeb: EOM");
   1.369 +
   1.370 +  /* gingerbread uses old channel layout enum */
   1.371 +  if (audiotrack_version_is_froyo(ctx) || audiotrack_version_is_gingerbread(ctx)) {
   1.372 +    channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
   1.373 +  } else {
   1.374 +    channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
   1.375 +  }
   1.376 +
   1.377 +  if (audiotrack_version_is_froyo(ctx)) {
   1.378 +    ctx->klass.ctor_froyo(stm->instance,
   1.379 +                          stm->params.stream_type,
   1.380 +                          stm->params.rate,
   1.381 +                          AUDIO_FORMAT_PCM_16_BIT,
   1.382 +                          channels,
   1.383 +                          min_frame_count,
   1.384 +                          0,
   1.385 +                          audiotrack_refill,
   1.386 +                          stm,
   1.387 +                          0);
   1.388 +  } else {
   1.389 +    ctx->klass.ctor(stm->instance,
   1.390 +                    stm->params.stream_type,
   1.391 +                    stm->params.rate,
   1.392 +                    AUDIO_FORMAT_PCM_16_BIT,
   1.393 +                    channels,
   1.394 +                    min_frame_count,
   1.395 +                    0,
   1.396 +                    audiotrack_refill,
   1.397 +                    stm,
   1.398 +                    0,
   1.399 +                    0);
   1.400 +  }
   1.401 +
   1.402 +  assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
   1.403 +
   1.404 +  if (ctx->klass.check(stm->instance)) {
   1.405 +    ALOG("stream not initialized properly.");
   1.406 +    audiotrack_stream_destroy(stm);
   1.407 +    return CUBEB_ERROR;
   1.408 +  }
   1.409 +
   1.410 +  *stream = stm;
   1.411 +
   1.412 +  return CUBEB_OK;
   1.413 +}
   1.414 +
   1.415 +void
   1.416 +audiotrack_stream_destroy(cubeb_stream * stream)
   1.417 +{
   1.418 +  assert(stream->context);
   1.419 +
   1.420 +  stream->context->klass.dtor(stream->instance);
   1.421 +
   1.422 +  free(stream->instance);
   1.423 +  stream->instance = NULL;
   1.424 +  free(stream);
   1.425 +}
   1.426 +
   1.427 +int
   1.428 +audiotrack_stream_start(cubeb_stream * stream)
   1.429 +{
   1.430 +  assert(stream->instance);
   1.431 +
   1.432 +  stream->context->klass.start(stream->instance);
   1.433 +  stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
   1.434 +
   1.435 +  return CUBEB_OK;
   1.436 +}
   1.437 +
   1.438 +int
   1.439 +audiotrack_stream_stop(cubeb_stream * stream)
   1.440 +{
   1.441 +  assert(stream->instance);
   1.442 +
   1.443 +  stream->context->klass.pause(stream->instance);
   1.444 +  stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
   1.445 +
   1.446 +  return CUBEB_OK;
   1.447 +}
   1.448 +
   1.449 +int
   1.450 +audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
   1.451 +{
   1.452 +  uint32_t p;
   1.453 +
   1.454 +  assert(stream->instance && position);
   1.455 +  stream->context->klass.get_position(stream->instance, &p);
   1.456 +  *position = p;
   1.457 +
   1.458 +  return CUBEB_OK;
   1.459 +}
   1.460 +
   1.461 +int
   1.462 +audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
   1.463 +{
   1.464 +  assert(stream->instance && latency);
   1.465 +
   1.466 +  /* Android returns the latency in ms, we want it in frames. */
   1.467 +  *latency = stream->context->klass.latency(stream->instance);
   1.468 +  /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
   1.469 +  *latency = (*latency * stream->params.rate) / 1000;
   1.470 +
   1.471 +  return 0;
   1.472 +}
   1.473 +
   1.474 +static struct cubeb_ops const audiotrack_ops = {
   1.475 +  .init = audiotrack_init,
   1.476 +  .get_backend_id = audiotrack_get_backend_id,
   1.477 +  .get_max_channel_count = audiotrack_get_max_channel_count,
   1.478 +  .get_min_latency = audiotrack_get_min_latency,
   1.479 +  .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
   1.480 +  .destroy = audiotrack_destroy,
   1.481 +  .stream_init = audiotrack_stream_init,
   1.482 +  .stream_destroy = audiotrack_stream_destroy,
   1.483 +  .stream_start = audiotrack_stream_start,
   1.484 +  .stream_stop = audiotrack_stream_stop,
   1.485 +  .stream_get_position = audiotrack_stream_get_position,
   1.486 +  .stream_get_latency = audiotrack_stream_get_latency
   1.487 +};

mercurial