/*
 * Copyright © 2012 Mozilla Foundation
 *
 * This program is made available under an ISC-style license.  See the
 * accompanying file LICENSE for details.
 */
#undef NDEBUG
#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <pthread.h>
#include <SLES/OpenSLES.h>
#include <math.h>
#include <time.h>
#if defined(__ANDROID__)
#include <dlfcn.h>
#include <sys/system_properties.h>
#include "android/sles_definitions.h"
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#include <android/api-level.h>
#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
#define ANDROID_VERSION_GINGERBREAD_MR1 10
#define ANDROID_VERSION_LOLLIPOP 21
#define ANDROID_VERSION_MARSHMALLOW 23
#endif
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include "cubeb_resampler.h"
#include "cubeb-sles.h"

static struct cubeb_ops const opensl_ops;

struct cubeb {
  struct cubeb_ops const * ops;
  void * lib;
  void * libmedia;
  int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
  SLInterfaceID SL_IID_BUFFERQUEUE;
  SLInterfaceID SL_IID_PLAY;
#if defined(__ANDROID__)
  SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
#endif
  SLInterfaceID SL_IID_VOLUME;
  SLObjectItf engObj;
  SLEngineItf eng;
  SLObjectItf outmixObj;
};

#define NELEMS(A) (sizeof(A) / sizeof A[0])
#define NBUFS 4
#define AUDIO_STREAM_TYPE_MUSIC 3

struct cubeb_stream {
  cubeb * context;
  pthread_mutex_t mutex;
  SLObjectItf playerObj;
  SLPlayItf play;
  SLBufferQueueItf bufq;
  SLVolumeItf volume;
  uint8_t *queuebuf[NBUFS];
  int queuebuf_idx;
  long queuebuf_len;
  long bytespersec;
  long framesize;
  long written;
  int draining;
  cubeb_stream_type stream_type;

  cubeb_data_callback data_callback;
  cubeb_state_callback state_callback;
  void * user_ptr;

  cubeb_resampler * resampler;
  unsigned int inputrate;
  unsigned int outputrate;
  unsigned int latency;
  int64_t lastPosition;
  int64_t lastPositionTimeStamp;
  int64_t lastCompensativePosition;
};

static void
play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
{
  cubeb_stream * stm = user_ptr;
  int draining;
  assert(stm);
  switch (event) {
  case SL_PLAYEVENT_HEADATMARKER:
    pthread_mutex_lock(&stm->mutex);
    draining = stm->draining;
    pthread_mutex_unlock(&stm->mutex);
    if (draining) {
      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
      (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
    }
    break;
  default:
    break;
  }
}

static void
bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
{
  cubeb_stream * stm = user_ptr;
  assert(stm);
  SLBufferQueueState state;
  SLresult res;

  res = (*stm->bufq)->GetState(stm->bufq, &state);
  assert(res == SL_RESULT_SUCCESS);

  if (state.count > 1)
    return;

  SLuint32 i;
  for (i = state.count; i < NBUFS; i++) {
    uint8_t *buf = stm->queuebuf[stm->queuebuf_idx];
    long written = 0;
    pthread_mutex_lock(&stm->mutex);
    int draining = stm->draining;
    pthread_mutex_unlock(&stm->mutex);

    if (!draining) {
      written = cubeb_resampler_fill(stm->resampler,
                                     NULL, NULL,
                                     buf, stm->queuebuf_len / stm->framesize);
      if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
        (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
        return;
      }
    }

    // Keep sending silent data even in draining mode to prevent the audio
    // back-end from being stopped automatically by OpenSL/ES.
    memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
    res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
    assert(res == SL_RESULT_SUCCESS);
    stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
    if (written > 0) {
      pthread_mutex_lock(&stm->mutex);
      stm->written += written;
      pthread_mutex_unlock(&stm->mutex);
    }

    if (!draining && written * stm->framesize < stm->queuebuf_len) {
      pthread_mutex_lock(&stm->mutex);
      int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
      stm->draining = 1;
      pthread_mutex_unlock(&stm->mutex);
      // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
      // to make sure all the data has been processed.
      (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
      return;
    }
  }
}

#if defined(__ANDROID__)
static SLuint32
convert_stream_type_to_sl_stream(cubeb_stream_type stream_type)
{
  switch(stream_type) {
  case CUBEB_STREAM_TYPE_SYSTEM:
    return SL_ANDROID_STREAM_SYSTEM;
  case CUBEB_STREAM_TYPE_MUSIC:
    return SL_ANDROID_STREAM_MEDIA;
  case CUBEB_STREAM_TYPE_NOTIFICATION:
    return SL_ANDROID_STREAM_NOTIFICATION;
  case CUBEB_STREAM_TYPE_ALARM:
    return SL_ANDROID_STREAM_ALARM;
  case CUBEB_STREAM_TYPE_VOICE_CALL:
    return SL_ANDROID_STREAM_VOICE;
  case CUBEB_STREAM_TYPE_RING:
    return SL_ANDROID_STREAM_RING;
  case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED:
    return SL_ANDROID_STREAM_SYSTEM_ENFORCED;
  default:
    return 0xFFFFFFFF;
  }
}
#endif

static void opensl_destroy(cubeb * ctx);

#if defined(__ANDROID__)

#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
typedef int (system_property_get)(const char*, char*);

static int
__system_property_get(const char* name, char* value)
{
  void* libc = dlopen("libc.so", RTLD_LAZY);
  if (!libc) {
    LOG("Failed to open libc.so");
    return -1;
  }
  system_property_get* func = (system_property_get*)
                              dlsym(libc, "__system_property_get");
  int ret = -1;
  if (func) {
    ret = func(name, value);
  }
  dlclose(libc);
  return ret;
}
#endif

static int
get_android_version(void)
{
  char version_string[PROP_VALUE_MAX];

  memset(version_string, 0, PROP_VALUE_MAX);

  int len = __system_property_get("ro.build.version.sdk", version_string);
  if (len <= 0) {
    LOG("Failed to get Android version!\n");
    return len;
  }

  int version = (int)strtol(version_string, NULL, 10);
  LOG("%d", version);
  return version;
}
#endif

/*static*/ int
opensl_init(cubeb ** context, char const * context_name)
{
  cubeb * ctx;

#if defined(__ANDROID__)
  int android_version = get_android_version();
  if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
    // Don't even attempt to run on Gingerbread and lower
    return CUBEB_ERROR;
  }
#endif

  *context = NULL;

  ctx = calloc(1, sizeof(*ctx));
  assert(ctx);

  ctx->ops = &opensl_ops;

  ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
  ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
  if (!ctx->lib || !ctx->libmedia) {
    free(ctx);
    return CUBEB_ERROR;
  }

  /* Get the latency, in ms, from AudioFlinger */
  /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
   *                                        audio_stream_type_t streamType) */
  /* First, try the most recent signature. */
  ctx->get_output_latency =
    dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
  if (!ctx->get_output_latency) {
    /* in case of failure, try the legacy version. */
    /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
     *                                        int streamType) */
    ctx->get_output_latency =
      dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
    if (!ctx->get_output_latency) {
      opensl_destroy(ctx);
      return CUBEB_ERROR;
    }
  }

  typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
                                       SLuint32,
                                       const SLEngineOption *,
                                       SLuint32,
                                       const SLInterfaceID *,
                                       const SLboolean *);
  slCreateEngine_t f_slCreateEngine =
    (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
  SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
  SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
  ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
  ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
#if defined(__ANDROID__)
  ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
#endif
  ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
  if (!f_slCreateEngine ||
      !SL_IID_ENGINE ||
      !SL_IID_OUTPUTMIX ||
      !ctx->SL_IID_BUFFERQUEUE ||
#if defined(__ANDROID__)
      !ctx->SL_IID_ANDROIDCONFIGURATION ||
#endif
      !ctx->SL_IID_PLAY) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}};

  SLresult res;
  res = cubeb_get_sles_engine(&ctx->engObj, 1, opt, 0, NULL, NULL);

  if (res != SL_RESULT_SUCCESS) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  res = cubeb_realize_sles_engine(ctx->engObj);
  if (res != SL_RESULT_SUCCESS) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng);
  if (res != SL_RESULT_SUCCESS) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
  const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
  res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
  if (res != SL_RESULT_SUCCESS) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
  if (res != SL_RESULT_SUCCESS) {
    opensl_destroy(ctx);
    return CUBEB_ERROR;
  }

  *context = ctx;

  return CUBEB_OK;
}

static char const *
opensl_get_backend_id(cubeb * ctx)
{
  return "opensl";
}

static int
opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{
  assert(ctx && max_channels);
  /* The android mixer handles up to two channels, see
     http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
  *max_channels = 2;

  return CUBEB_OK;
}

static int
opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
  /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
   * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
   * so we just dlopen the library and get the two symbols we need. */
  int r;
  void * libmedia;
  uint32_t (*get_primary_output_samplingrate)();
  uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType);

  libmedia = dlopen("libmedia.so", RTLD_LAZY);
  if (!libmedia) {
    return CUBEB_ERROR;
  }

  /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */
  get_primary_output_samplingrate =
    dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv");
  if (!get_primary_output_samplingrate) {
    /* fallback to
     * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType)
     * if we cannot find getPrimaryOutputSamplingRate. */
    get_output_samplingrate =
      dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t");
    if (!get_output_samplingrate) {
      /* Another signature exists, with a int instead of an audio_stream_type_t */
      get_output_samplingrate =
        dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii");
      if (!get_output_samplingrate) {
        dlclose(libmedia);
        return CUBEB_ERROR;
      }
    }
  }

  if (get_primary_output_samplingrate) {
    *rate = get_primary_output_samplingrate();
  } else {
    /* We don't really know about the type, here, so we just pass music. */
    r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC);
    if (r) {
      dlclose(libmedia);
      return CUBEB_ERROR;
    }
  }

  dlclose(libmedia);

  /* Depending on which method we called above, we can get a zero back, yet have
   * a non-error return value, especially if the audio system is not
   * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger
   * thread). */
  if (*rate == 0) {
    return CUBEB_ERROR;
  }

  return CUBEB_OK;
}

static int
opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
{
  /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
   * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
   * so we just dlopen the library and get the two symbols we need. */

  int r;
  void * libmedia;
  size_t (*get_primary_output_frame_count)(void);
  int (*get_output_frame_count)(size_t * frameCount, int streamType);
  uint32_t primary_sampling_rate;
  size_t primary_buffer_size;

  r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate);

  if (r) {
    return CUBEB_ERROR;
  }

  libmedia = dlopen("libmedia.so", RTLD_LAZY);
  if (!libmedia) {
    return CUBEB_ERROR;
  }

  /* JB variant */
  /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */
  get_primary_output_frame_count =
    dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv");
  if (!get_primary_output_frame_count) {
    /* ICS variant */
    /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */
    get_output_frame_count =
      dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii");
    if (!get_output_frame_count) {
      dlclose(libmedia);
      return CUBEB_ERROR;
    }
  }

  if (get_primary_output_frame_count) {
    primary_buffer_size = get_primary_output_frame_count();
  } else {
    if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) {
      return CUBEB_ERROR;
    }
  }

  /* To get a fast track in Android's mixer, we need to be at the native
   * samplerate, which is device dependant. Some devices might be able to
   * resample when playing a fast track, but it's pretty rare. */
  *latency_frames = NBUFS * primary_buffer_size;

  dlclose(libmedia);

  return CUBEB_OK;
}

static void
opensl_destroy(cubeb * ctx)
{
  if (ctx->outmixObj)
    (*ctx->outmixObj)->Destroy(ctx->outmixObj);
  if (ctx->engObj)
    cubeb_destroy_sles_engine(&ctx->engObj);
  dlclose(ctx->lib);
  dlclose(ctx->libmedia);
  free(ctx);
}

static void opensl_stream_destroy(cubeb_stream * stm);

static int
opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
                   cubeb_devid input_device,
                   cubeb_stream_params * input_stream_params,
                   cubeb_devid output_device,
                   cubeb_stream_params * output_stream_params,
                   unsigned int latency_frames,
                   cubeb_data_callback data_callback, cubeb_state_callback state_callback,
                   void * user_ptr)
{
  cubeb_stream * stm;

  assert(ctx);
  assert(!input_stream_params && "not supported");
  if (input_device || output_device) {
    /* Device selection not yet implemented. */
    return CUBEB_ERROR_DEVICE_UNAVAILABLE;
  }

  *stream = NULL;

  SLDataFormat_PCM format;

  format.formatType = SL_DATAFORMAT_PCM;
  format.numChannels = output_stream_params->channels;
  // samplesPerSec is in milliHertz
  format.samplesPerSec = output_stream_params->rate * 1000;
  format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
  format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
  format.channelMask = output_stream_params->channels == 1 ?
    SL_SPEAKER_FRONT_CENTER :
    SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;

  switch (output_stream_params->format) {
  case CUBEB_SAMPLE_S16LE:
    format.endianness = SL_BYTEORDER_LITTLEENDIAN;
    break;
  case CUBEB_SAMPLE_S16BE:
    format.endianness = SL_BYTEORDER_BIGENDIAN;
    break;
  default:
    return CUBEB_ERROR_INVALID_FORMAT;
  }

  stm = calloc(1, sizeof(*stm));
  assert(stm);

  stm->context = ctx;
  stm->data_callback = data_callback;
  stm->state_callback = state_callback;
  stm->user_ptr = user_ptr;

  stm->inputrate = output_stream_params->rate;
  stm->latency = latency_frames;
  stm->stream_type = output_stream_params->stream_type;
  stm->framesize = output_stream_params->channels * sizeof(int16_t);
  stm->lastPosition = -1;
  stm->lastPositionTimeStamp = 0;
  stm->lastCompensativePosition = -1;

  int r = pthread_mutex_init(&stm->mutex, NULL);
  assert(r == 0);

  SLDataLocator_BufferQueue loc_bufq;
  loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
  loc_bufq.numBuffers = NBUFS;
  SLDataSource source;
  source.pLocator = &loc_bufq;
  source.pFormat = &format;

  SLDataLocator_OutputMix loc_outmix;
  loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
  loc_outmix.outputMix = ctx->outmixObj;
  SLDataSink sink;
  sink.pLocator = &loc_outmix;
  sink.pFormat = NULL;

#if defined(__ANDROID__)
  const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE,
                               ctx->SL_IID_VOLUME,
                               ctx->SL_IID_ANDROIDCONFIGURATION};
  const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#else
  const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
  const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#endif
  assert(NELEMS(ids) == NELEMS(req));

  uint32_t preferred_sampling_rate = stm->inputrate;
#if defined(__ANDROID__)
  if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) {
    // Reset preferred samping rate to trigger fallback to native sampling rate.
    preferred_sampling_rate = 0;
    if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) {
      // Default to AudioFlinger's advertised fast track latency of 10ms.
      latency_frames = 440;
    }
    stm->latency = latency_frames;
  }
#endif

  SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
  if (preferred_sampling_rate) {
    res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, &source,
                                         &sink, NELEMS(ids), ids, req);
  }

  // Sample rate not supported? Try again with primary sample rate!
  if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
    if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) {
      opensl_stream_destroy(stm);
      return CUBEB_ERROR;
    }

    format.samplesPerSec = preferred_sampling_rate * 1000;
    res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj,
                                         &source, &sink, NELEMS(ids), ids, req);
  }

  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  stm->outputrate = preferred_sampling_rate;
  stm->bytespersec = stm->outputrate * stm->framesize;
  stm->queuebuf_len = stm->framesize * latency_frames / NBUFS;
  // round up to the next multiple of stm->framesize, if needed.
  if (stm->queuebuf_len % stm->framesize) {
    stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
  }

  cubeb_stream_params params = *output_stream_params;
  params.rate = preferred_sampling_rate;

  stm->resampler = cubeb_resampler_create(stm, NULL, &params,
                                          output_stream_params->rate,
                                          data_callback,
                                          user_ptr,
                                          CUBEB_RESAMPLER_QUALITY_DEFAULT);

  if (!stm->resampler) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  int i;
  for (i = 0; i < NBUFS; i++) {
    stm->queuebuf[i] = malloc(stm->queuebuf_len);
    assert(stm->queuebuf[i]);
  }

#if defined(__ANDROID__)
  SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type);
  if (stream_type != 0xFFFFFFFF) {
    SLAndroidConfigurationItf playerConfig;
    res = (*stm->playerObj)->GetInterface(stm->playerObj,
                                          ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig);
    res = (*playerConfig)->SetConfiguration(playerConfig,
                                            SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32));
    if (res != SL_RESULT_SUCCESS) {
      opensl_stream_destroy(stm);
      return CUBEB_ERROR;
    }
  }
#endif

  res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
                                        &stm->bufq);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME,
                                        &stm->volume);

  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  // Work around wilhelm/AudioTrack badness, bug 1221228
  (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);

  res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
  if (res != SL_RESULT_SUCCESS) {
    opensl_stream_destroy(stm);
    return CUBEB_ERROR;
  }

  {
    // Enqueue a silent frame so once the player becomes playing, the frame
    // will be consumed and kick off the buffer queue callback.
    // Note the duration of a single frame is less than 1ms. We don't bother
    // adjusting the playback position.
    uint8_t *buf = stm->queuebuf[stm->queuebuf_idx++];
    memset(buf, 0, stm->framesize);
    res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
    assert(res == SL_RESULT_SUCCESS);
  }

  *stream = stm;
  return CUBEB_OK;
}

static void
opensl_stream_destroy(cubeb_stream * stm)
{
  if (stm->playerObj)
    (*stm->playerObj)->Destroy(stm->playerObj);
  int i;
  for (i = 0; i < NBUFS; i++) {
    free(stm->queuebuf[i]);
  }
  pthread_mutex_destroy(&stm->mutex);

  cubeb_resampler_destroy(stm->resampler);

  free(stm);
}

static int
opensl_stream_start(cubeb_stream * stm)
{
  SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
  if (res != SL_RESULT_SUCCESS)
    return CUBEB_ERROR;
  stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
  return CUBEB_OK;
}

static int
opensl_stream_stop(cubeb_stream * stm)
{
  SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
  if (res != SL_RESULT_SUCCESS)
    return CUBEB_ERROR;
  stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
  return CUBEB_OK;
}

static int
opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
  SLmillisecond msec;
  uint64_t samplerate;
  SLresult res;
  int r;
  uint32_t mixer_latency;
  uint32_t compensation_msec = 0;

  res = (*stm->play)->GetPosition(stm->play, &msec);
  if (res != SL_RESULT_SUCCESS)
    return CUBEB_ERROR;

  struct timespec t;
  clock_gettime(CLOCK_MONOTONIC, &t);
  if(stm->lastPosition == msec) {
    compensation_msec =
      (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
  } else {
    stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec;
    stm->lastPosition = msec;
  }

  samplerate = stm->inputrate;

  r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
  if (r) {
    return CUBEB_ERROR;
  }

  pthread_mutex_lock(&stm->mutex);
  int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate;
  pthread_mutex_unlock(&stm->mutex);
  assert(maximum_position >= 0);

  if (msec > mixer_latency) {
    int64_t unadjusted_position;
    if (stm->lastCompensativePosition > msec + compensation_msec) {
      // Over compensation, use lastCompensativePosition.
      unadjusted_position =
        samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000;
    } else {
      unadjusted_position =
        samplerate * (msec - mixer_latency + compensation_msec) / 1000;
      stm->lastCompensativePosition = msec + compensation_msec;
    }
    *position = unadjusted_position < maximum_position ?
      unadjusted_position : maximum_position;
  } else {
    *position = 0;
  }
  return CUBEB_OK;
}

int
opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
  int r;
  uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms.

  /* audio_stream_type_t is an int, so this is okay. */
  r = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
  if (r) {
    return CUBEB_ERROR;
  }

  *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency
    mixer_latency * stm->inputrate / 1000; // AudioFlinger latency

  return CUBEB_OK;
}

int
opensl_stream_set_volume(cubeb_stream * stm, float volume)
{
  SLresult res;
  SLmillibel max_level, millibels;
  float unclamped_millibels;

  res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level);

  if (res != SL_RESULT_SUCCESS) {
    return CUBEB_ERROR;
  }

  /* millibels are 100*dB, so the conversion from the volume's linear amplitude
   * is 100 * 20 * log(volume). However we clamp the resulting value before
   * passing it to lroundf() in order to prevent it from silently returning an
   * erroneous value when the unclamped value exceeds the size of a long. */
  unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f));
  unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN);
  unclamped_millibels = fminf(unclamped_millibels, max_level);

  millibels = lroundf(unclamped_millibels);

  res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels);

  if (res != SL_RESULT_SUCCESS) {
    return CUBEB_ERROR;
  }
  return CUBEB_OK;
}

static struct cubeb_ops const opensl_ops = {
  .init = opensl_init,
  .get_backend_id = opensl_get_backend_id,
  .get_max_channel_count = opensl_get_max_channel_count,
  .get_min_latency = opensl_get_min_latency,
  .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
  .enumerate_devices = NULL,
  .destroy = opensl_destroy,
  .stream_init = opensl_stream_init,
  .stream_destroy = opensl_stream_destroy,
  .stream_start = opensl_stream_start,
  .stream_stop = opensl_stream_stop,
  .stream_get_position = opensl_stream_get_position,
  .stream_get_latency = opensl_stream_get_latency,
  .stream_set_volume = opensl_stream_set_volume,
  .stream_set_panning = NULL,
  .stream_get_current_device = NULL,
  .stream_device_destroy = NULL,
  .stream_register_device_changed_callback = NULL,
  .register_device_collection_changed = NULL
};