/* GStreamer
 * Copyright (C) <2009> Prajnashi S <prajnashi@gmail.com>
 * Copyright (C) <2011> Alessandro Decina <alessandro.decina@collabora.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/**
 * SECTION:element-audioflindersink
 *
 * This element lets you output sound using the Audio Flinger system in Android
 *
 * Note that you should almost always use generic audio conversion elements
 * like audioconvert and audioresample in front of an audiosink to make sure
 * your pipeline works under all circumstances (those conversion elements will
 * act in passthrough-mode if no conversion is necessary).
 */

#ifdef HAVE_CONFIG_H
//#include "config.h"
#endif
#include "gstaudioflingersink.h"
#include <utils/Log.h>


#define LOG_NDEBUG 0

#undef LOG_TAG
#define LOG_TAG "GstAudioFlingerSink"


#define DEFAULT_BUFFERTIME (500*GST_MSECOND) / (GST_USECOND)
#define DEFAULT_LATENCYTIME (50*GST_MSECOND) / (GST_USECOND)
#define DEFAULT_VOLUME 1.0
#define DEFAULT_MUTE FALSE
#define DEFAULT_EXPORT_SYSTEM_AUDIO_CLOCK TRUE

/*
 * PROPERTY_ID
 */
enum
{
  PROP_NULL,
  PROP_VOLUME,
  PROP_MUTE,
  PROP_AUDIO_SINK,
};

GST_DEBUG_CATEGORY_STATIC (audioflinger_debug);
#define GST_CAT_DEFAULT audioflinger_debug

#define GST_TYPE_ANDROID_AUDIORING_BUFFER        \
        (gst_android_audioringbuffer_get_type())
#define GST_ANDROID_AUDIORING_BUFFER(obj)        \
        (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ANDROID_AUDIORING_BUFFER,GstAndroidAudioRingBuffer))
#define GST_ANDROID_AUDIORING_BUFFER_CLASS(klass) \
        (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ANDROID_AUDIORING_BUFFER,GstAndroidAudioRingBufferClass))
#define GST_ANDROID_AUDIORING_BUFFER_GET_CLASS(obj) \
        (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_ANDROID_AUDIORING_BUFFER, GstAndroidAudioRingBufferClass))
#define GST_ANDROID_AUDIORING_BUFFER_CAST(obj)        \
        ((GstAndroidAudioRingBuffer *)obj)
#define GST_IS_ANDROID_AUDIORING_BUFFER(obj)     \
        (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ANDROID_AUDIORING_BUFFER))
#define GST_IS_ANDROID_AUDIORING_BUFFER_CLASS(klass)\
        (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ANDROID_AUDIORING_BUFFER))

typedef struct _GstAndroidAudioRingBuffer GstAndroidAudioRingBuffer;
typedef struct _GstAndroidAudioRingBufferClass GstAndroidAudioRingBufferClass;

#define GST_ANDROID_AUDIORING_BUFFER_GET_COND(buf) (((GstAndroidAudioRingBuffer *)buf)->cond)
#define GST_ANDROID_AUDIORING_BUFFER_WAIT(buf)     (g_cond_wait (GST_ANDROID_ANDROID_AUDIORING_BUFFER_GET_COND (buf), GST_OBJECT_GET_LOCK (buf)))
#define GST_ANDROID_AUDIORING_BUFFER_SIGNAL(buf)   (g_cond_signal (GST_ANDROID_ANDROID_AUDIORING_BUFFER_GET_COND (buf)))
#define GST_ANDROID_AUDIORING_BUFFER_BROADCAST(buf)(g_cond_broadcast (GST_ANDROID_ANDROID_AUDIORING_BUFFER_GET_COND (buf)))

struct _GstAndroidAudioRingBuffer
{
  GstRingBuffer object;

  gboolean running;
  gint queuedseg;

  GCond *cond;
};

struct _GstAndroidAudioRingBufferClass
{
  GstRingBufferClass parent_class;
};

static void gst_android_audioringbuffer_dispose (GObject * object);
static void gst_android_audioringbuffer_finalize (GObject * object);

static GstRingBufferClass *ring_parent_class = NULL;

static gboolean gst_android_audioringbuffer_open_device (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_close_device (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_acquire (GstRingBuffer * buf,
    GstRingBufferSpec * spec);
static gboolean gst_android_audioringbuffer_release (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_start (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_pause (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_stop (GstRingBuffer * buf);
static gboolean gst_android_audioringbuffer_activate (GstRingBuffer * buf,
    gboolean active);
static void gst_android_audioringbuffer_clear (GstRingBuffer * buf);
static guint gst_android_audioringbuffer_commit (GstRingBuffer * buf,
    guint64 * sample, guchar * data, gint in_samples, gint out_samples,
    gint * accum);

static void gst_audioflinger_sink_dispose (GObject * object);
static void gst_audioflinger_sink_finalise (GObject * object);

static void gst_audioflinger_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_audioflinger_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);

static GstCaps *gst_audioflinger_sink_getcaps (GstBaseSink * bsink);

static gboolean gst_audioflinger_sink_open (GstAudioFlingerSink * asink);
static gboolean gst_audioflinger_sink_close (GstAudioFlingerSink * asink);
static gboolean gst_audioflinger_sink_prepare (GstAudioFlingerSink * asink,
    GstRingBufferSpec * spec);
static gboolean gst_audioflinger_sink_unprepare (GstAudioFlingerSink * asink);
static void gst_audioflinger_sink_reset (GstAudioFlingerSink * asink,
    gboolean create_clock);
static void gst_audioflinger_sink_set_mute (GstAudioFlingerSink *
    audioflinger_sink, gboolean mute);
static void gst_audioflinger_sink_set_volume (GstAudioFlingerSink *
    audioflinger_sink, float volume);
static gboolean gst_audioflinger_sink_event (GstBaseSink * bsink,
    GstEvent * event);
static GstRingBuffer *gst_audioflinger_sink_create_ringbuffer (GstBaseAudioSink
    * sink);
static GstClockTime gst_audioflinger_sink_get_time (GstClock * clock,
    gpointer user_data);
static GstFlowReturn gst_audioflinger_sink_preroll (GstBaseSink * bsink,
    GstBuffer * buffer);
static GstClockTime gst_audioflinger_sink_system_audio_clock_get_time (GstClock
    * clock, gpointer user_data);
static GstClock *gst_audioflinger_sink_provide_clock (GstElement * elem);
static GstStateChangeReturn gst_audioflinger_sink_change_state (GstElement *
    element, GstStateChange transition);

static GstStaticPadTemplate audioflingersink_sink_factory =
    GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw-int, "
        "endianness = (int) { " G_STRINGIFY (G_BYTE_ORDER) " }, "
        "signed = (boolean) { TRUE }, "
        "width = (int) 16, "
        "depth = (int) 16, "
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; ")
    );

G_DEFINE_TYPE (GstAndroidAudioRingBuffer,
    gst_android_audioringbuffer, GST_TYPE_RING_BUFFER);
GST_BOILERPLATE (GstAudioFlingerSink, gst_audioflinger_sink, GstAudioSink,
    GST_TYPE_AUDIO_SINK);

static void
gst_android_audioringbuffer_class_init (GstAndroidAudioRingBufferClass * klass)
{
  GObjectClass *gobject_class;
  GstRingBufferClass *gstringbuffer_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gstringbuffer_class = GST_RING_BUFFER_CLASS (klass);

  ring_parent_class = g_type_class_peek_parent (klass);

  gobject_class->dispose = gst_android_audioringbuffer_dispose;
  gobject_class->finalize = gst_android_audioringbuffer_finalize;

  gstringbuffer_class->open_device =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_open_device);
  gstringbuffer_class->close_device =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_close_device);
  gstringbuffer_class->acquire =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_acquire);
  gstringbuffer_class->release =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_release);
  gstringbuffer_class->start =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_start);
  gstringbuffer_class->pause =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_pause);
  gstringbuffer_class->resume =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_start);
  gstringbuffer_class->stop =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_stop);
  gstringbuffer_class->clear_all =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_clear);
  gstringbuffer_class->commit =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_commit);

#if 0
  gstringbuffer_class->delay =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_delay);
#endif
  gstringbuffer_class->activate =
      GST_DEBUG_FUNCPTR (gst_android_audioringbuffer_activate);
}

static void
gst_android_audioringbuffer_init (G_GNUC_UNUSED GstAndroidAudioRingBuffer *
    ringbuffer)
{
}

static void
gst_android_audioringbuffer_dispose (GObject * object)
{
  G_OBJECT_CLASS (ring_parent_class)->dispose (object);
}

static void
gst_android_audioringbuffer_finalize (GObject * object)
{
  G_OBJECT_CLASS (ring_parent_class)->finalize (object);
}

static gboolean
gst_android_audioringbuffer_open_device (GstRingBuffer * buf)
{
  GstAudioFlingerSink *sink;
  gboolean result = TRUE;
  LOGD (">gst_android_audioringbuffer_open_device");
  sink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (buf));
  result = gst_audioflinger_sink_open (sink);
  GST_INFO_OBJECT (sink, "open device result %d", result);

  if (!result)
    goto could_not_open;

  return result;

could_not_open:
  {
    GST_DEBUG_OBJECT (sink, "could not open device");
    LOGE ("could not open device");
    return FALSE;
  }
}

static gboolean
gst_android_audioringbuffer_close_device (GstRingBuffer * buf)
{
  GstAudioFlingerSink *sink;
  gboolean result = TRUE;

  LOGD (">gst_android_audioringbuffer_close_device");

  sink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (buf));

  result = gst_audioflinger_sink_close (sink);

  if (!result)
    goto could_not_close;

  return result;

could_not_close:
  {
    GST_DEBUG_OBJECT (sink, "could not close device");
    LOGE ("could not close device");
    return FALSE;
  }
}

static gboolean
gst_android_audioringbuffer_acquire (GstRingBuffer * buf,
    GstRingBufferSpec * spec)
{
  GstAudioFlingerSink *sink;
  gboolean result = FALSE;

  LOGD (">gst_android_audioringbuffer_acquire");

  sink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (buf));

  result = gst_audioflinger_sink_prepare (sink, spec);

  if (!result)
    goto could_not_prepare;

  return TRUE;

  /* ERRORS */
could_not_prepare:
  {
    GST_DEBUG_OBJECT (sink, "could not prepare device");
    LOGE ("could not close device");
    return FALSE;
  }
}

static gboolean
gst_android_audioringbuffer_activate (G_GNUC_UNUSED GstRingBuffer * buf,
    G_GNUC_UNUSED gboolean active)
{
  return TRUE;
}

/* function is called with LOCK */
static gboolean
gst_android_audioringbuffer_release (GstRingBuffer * buf)
{
  GstAudioFlingerSink *sink;
  gboolean result = FALSE;
  LOGD (">gst_android_audioringbuffer_release");

  sink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (buf));

  result = gst_audioflinger_sink_unprepare (sink);

  if (!result)
    goto could_not_unprepare;

  GST_DEBUG_OBJECT (sink, "unprepared");

  return result;

could_not_unprepare:
  {
    GST_DEBUG_OBJECT (sink, "could not unprepare device");
    LOGE ("could not unprepare device");
    return FALSE;
  }
}

static gboolean
gst_android_audioringbuffer_start (GstRingBuffer * buf)
{
  GstAudioFlingerSink *asink;
  GstAndroidAudioRingBuffer *abuf;

  abuf = GST_ANDROID_AUDIORING_BUFFER_CAST (buf);
  asink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (abuf));

  GST_INFO_OBJECT (buf, "starting ringbuffer");
  LOGD ("starting ringbuffer");

  audioflinger_device_start (asink->audioflinger_device);

  return TRUE;
}

static gboolean
gst_android_audioringbuffer_pause (GstRingBuffer * buf)
{
  GstAudioFlingerSink *asink;
  GstAndroidAudioRingBuffer *abuf;

  abuf = GST_ANDROID_AUDIORING_BUFFER_CAST (buf);
  asink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (abuf));

  GST_INFO_OBJECT (buf, "pausing ringbuffer");
  LOGD ("pausing ringbuffer");

  audioflinger_device_pause (asink->audioflinger_device);

  return TRUE;
}

static gboolean
gst_android_audioringbuffer_stop (GstRingBuffer * buf)
{
  GstAudioFlingerSink *asink;
  GstAndroidAudioRingBuffer *abuf;

  abuf = GST_ANDROID_AUDIORING_BUFFER_CAST (buf);
  asink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (abuf));

  GST_INFO_OBJECT (buf, "stopping ringbuffer");
  LOGD ("stopping ringbuffer");

  audioflinger_device_stop (asink->audioflinger_device);

  return TRUE;
}

#if 0
static guint
gst_android_audioringbuffer_delay (GstRingBuffer * buf)
{
  return 0;
}
#endif

static void
gst_android_audioringbuffer_clear (GstRingBuffer * buf)
{
  GstAudioFlingerSink *asink;
  GstAndroidAudioRingBuffer *abuf;
  guint32 position;

  abuf = GST_ANDROID_AUDIORING_BUFFER_CAST (buf);
  asink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (abuf));

  GST_INFO_OBJECT (buf, "clearing ringbuffer, device %p",
      asink->audioflinger_device);

  if (asink->audioflinger_device == NULL)
    return;

  GST_INFO_OBJECT (buf, "before flush");
  audioflinger_device_flush (asink->audioflinger_device);
  GST_INFO_OBJECT (buf, "after flush");
//  position = audioflinger_device_get_position (asink->audioflinger_device);
  GST_INFO_OBJECT (buf, "after get position");
  if (TRUE || position == 0) {
    GST_INFO_OBJECT (asink, "resetting clock");
    gst_audio_clock_reset (GST_AUDIO_CLOCK (asink->audio_clock), 0);
  } else {
    GST_WARNING_OBJECT (asink, "flush failed, not resetting clock");
  }
}

#define FWD_SAMPLES(s,se,d,de)		 	\
G_STMT_START {					\
  /* no rate conversion */			\
  guint towrite = MIN (se + bps - s, de - d);	\
  /* simple copy */				\
  if (!skip)					\
    memcpy (d, s, towrite);			\
  in_samples -= towrite / bps;			\
  out_samples -= towrite / bps;			\
  s += towrite;					\
  GST_LOG ("copy %u bytes", towrite);		\
} G_STMT_END

/* in_samples >= out_samples, rate > 1.0 */
#define FWD_UP_SAMPLES(s,se,d,de) 	 	\
G_STMT_START {					\
  guint8 *sb = s, *db = d;			\
  while (s <= se && d < de) {			\
    if (!skip)					\
      memcpy (d, s, bps);			\
    s += bps;					\
    *accum += outr;				\
    if ((*accum << 1) >= inr) {			\
      *accum -= inr;				\
      d += bps;					\
    }						\
  }						\
  in_samples -= (s - sb)/bps;			\
  out_samples -= (d - db)/bps;			\
  GST_DEBUG ("fwd_up end %d/%d",*accum,*toprocess);	\
} G_STMT_END

/* out_samples > in_samples, for rates smaller than 1.0 */
#define FWD_DOWN_SAMPLES(s,se,d,de) 	 	\
G_STMT_START {					\
  guint8 *sb = s, *db = d;			\
  while (s <= se && d < de) {			\
    if (!skip)					\
      memcpy (d, s, bps);			\
    d += bps;					\
    *accum += inr;				\
    if ((*accum << 1) >= outr) {		\
      *accum -= outr;				\
      s += bps;					\
    }						\
  }						\
  in_samples -= (s - sb)/bps;			\
  out_samples -= (d - db)/bps;			\
  GST_DEBUG ("fwd_down end %d/%d",*accum,*toprocess);	\
} G_STMT_END

#define REV_UP_SAMPLES(s,se,d,de) 	 	\
G_STMT_START {					\
  guint8 *sb = se, *db = d;			\
  while (s <= se && d < de) {			\
    if (!skip)					\
      memcpy (d, se, bps);			\
    se -= bps;					\
    *accum += outr;				\
    while (d < de && (*accum << 1) >= inr) {	\
      *accum -= inr;				\
      d += bps;					\
    }						\
  }						\
  in_samples -= (sb - se)/bps;			\
  out_samples -= (d - db)/bps;			\
  GST_DEBUG ("rev_up end %d/%d",*accum,*toprocess);	\
} G_STMT_END

#define REV_DOWN_SAMPLES(s,se,d,de) 	 	\
G_STMT_START {					\
  guint8 *sb = se, *db = d;			\
  while (s <= se && d < de) {			\
    if (!skip)					\
      memcpy (d, se, bps);			\
    d += bps;					\
    *accum += inr;				\
    while (s <= se && (*accum << 1) >= outr) {	\
      *accum -= outr;				\
      se -= bps;				\
    }						\
  }						\
  in_samples -= (sb - se)/bps;			\
  out_samples -= (d - db)/bps;			\
  GST_DEBUG ("rev_down end %d/%d",*accum,*toprocess);	\
} G_STMT_END

static guint
gst_android_audioringbuffer_commit (GstRingBuffer * buf, guint64 * sample,
    guchar * data, gint in_samples, gint out_samples, gint * accum)
{
  GstBaseAudioSink *baseaudiosink;
  GstAudioFlingerSink *asink;
  GstAndroidAudioRingBuffer *abuf;
  guint result;
  guint8 *data_end;
  gboolean reverse;
  gint *toprocess;
  gint inr, outr, bps;
  guint bufsize;
  gboolean skip = FALSE;
  guint32 position;
  gboolean slaved;
  gboolean sync;

  abuf = GST_ANDROID_AUDIORING_BUFFER_CAST (buf);
  asink = GST_AUDIOFLINGERSINK (GST_OBJECT_PARENT (abuf));
  baseaudiosink = GST_BASE_AUDIO_SINK (asink);
  sync = gst_base_sink_get_sync (GST_BASE_SINK_CAST (asink));

  GST_LOG_OBJECT (asink, "entering commit");

  /* make sure the ringbuffer is started */
  if (G_UNLIKELY (g_atomic_int_get (&buf->state) !=
          GST_RING_BUFFER_STATE_STARTED)) {
    /* see if we are allowed to start it */
    if (G_UNLIKELY (g_atomic_int_get (&buf->abidata.ABI.may_start) == FALSE))
      goto no_start;

    GST_INFO_OBJECT (buf, "start!");
    LOGD ("start!");
    if (!gst_ring_buffer_start (buf))
      goto start_failed;
  }

  slaved = GST_ELEMENT_CLOCK (baseaudiosink) != asink->exported_clock;
  if (asink->last_resync_sample == -1 ||
      (gint64) baseaudiosink->next_sample == -1) {
    if (slaved) {
      /* we're writing a discont buffer. Disable slaving for a while in order to
       * fill the initial buffer needed by the audio mixer thread. This avoids
       * some cases where audioflinger removes us from the list of active tracks
       * because we aren't writing enough data.
       */
      GST_INFO_OBJECT (asink, "no previous sample, now %" G_GINT64_FORMAT
          " disabling slaving", *sample);
      asink->last_resync_sample = *sample;
      g_object_set (asink, "slave-method", GST_BASE_AUDIO_SINK_SLAVE_NONE,
          NULL);
      asink->slaving_disabled = TRUE;
    }
  }

  if (slaved && asink->slaving_disabled) {
    guint64 threshold;

    threshold = gst_util_uint64_scale_int (buf->spec.rate, 5, 1);
    threshold += asink->last_resync_sample;

    if (*sample >= threshold) {
      GST_INFO_OBJECT (asink, "last sync %" G_GINT64_FORMAT
          " reached sample %" G_GINT64_FORMAT ", enabling slaving",
          asink->last_resync_sample, *sample);
      g_object_set (asink, "slave-method", GST_BASE_AUDIO_SINK_SLAVE_SKEW,
          NULL);
      asink->slaving_disabled = FALSE;
    }
  }

  bps = buf->spec.bytes_per_sample;
  bufsize = buf->spec.segsize * buf->spec.segtotal;

  /* our toy resampler for trick modes */
  reverse = out_samples < 0;
  out_samples = ABS (out_samples);

  if (in_samples >= out_samples)
    toprocess = &in_samples;
  else
    toprocess = &out_samples;

  inr = in_samples - 1;
  outr = out_samples - 1;

  GST_LOG_OBJECT (asink, "in %d, out %d reverse %d sync %d", inr, outr,
      reverse, sync);

  /* data_end points to the last sample we have to write, not past it. This is
   * needed to properly handle reverse playback: it points to the last sample. */
  data_end = data + (bps * inr);

  while (*toprocess > 0) {
    if (sync) {
      size_t avail;
      guint towrite;
      gint err;
      guint8 *d, *d_end;
      gpointer buffer_handle;

      position = audioflinger_device_get_position (asink->audioflinger_device);
      avail = out_samples;
      buffer_handle = NULL;
      GST_LOG_OBJECT (asink, "calling obtain buffer, position %d"
          " offset %" G_GINT64_FORMAT " samples %" G_GSSIZE_FORMAT,
          position, *sample, avail);
      err = audioflinger_device_obtain_buffer (asink->audioflinger_device,
          &buffer_handle, (int8_t **) & d, &avail, *sample);
      GST_LOG_OBJECT (asink, "obtain buffer returned");
//      if (err != NO_ERROR) {
      if (err != 0) {
        GST_INFO_OBJECT (asink, "obtain buffer error %d, state %d",
            err, buf->state);
        LOGD ("obtain buffer error 0x%x, state %d", err, buf->state);

        if (err == LATE)
          skip = TRUE;
        else if (buf->state != GST_RING_BUFFER_STATE_STARTED)
          goto done;
        else
          goto obtain_buffer_failed;
      }

      towrite = avail * bps;
      d_end = d + towrite;

      GST_LOG_OBJECT (asink, "writing %u samples at offset %" G_GUINT64_FORMAT,
          (guint) avail, *sample);

      if (G_LIKELY (inr == outr && !reverse)) {
        FWD_SAMPLES (data, data_end, d, d_end);
      } else if (!reverse) {
        if (inr >= outr) {
          /* forward speed up */
          FWD_UP_SAMPLES (data, data_end, d, d_end);
        } else {
          /* forward slow down */
          FWD_DOWN_SAMPLES (data, data_end, d, d_end);
        }
      } else {
        if (inr >= outr)
          /* reverse speed up */
          REV_UP_SAMPLES (data, data_end, d, d_end);
        else
          /* reverse slow down */
          REV_DOWN_SAMPLES (data, data_end, d, d_end);
      }

      *sample += avail;

      if (buffer_handle)
        audioflinger_device_release_buffer (asink->audioflinger_device,
            buffer_handle);
    } else {
      gint written;

      written = audioflinger_device_write (asink->audioflinger_device, data,
          *toprocess * bps);
      if (written > 0) {
        *toprocess -= written / bps;
        data += written;
      } else {
        LOGE ("Error to write buffer(error=%d)", written);
        GST_LOG_OBJECT (asink, "Error to write buffer(error=%d)", written);
        goto done;
      }
    }
  }

  /* we consumed all samples here */
  data = data_end + bps;

done:
  result = inr - ((data_end - data) / bps);
  GST_LOG_OBJECT (asink, "wrote %d samples", result);

  return result;

  /* ERRORS */
no_start:
  {
    GST_LOG_OBJECT (asink, "we can not start");
    LOGE ("we can not start");
    return 0;
  }
start_failed:
  {
    GST_LOG_OBJECT (asink, "failed to start the ringbuffer");
    LOGE ("failed to start the ringbuffer");
    return 0;
  }
obtain_buffer_failed:
  {
    GST_ELEMENT_ERROR (asink, RESOURCE, FAILED,
        ("obtain_buffer failed"), (NULL));
    LOGE ("obtain_buffer failed");
    return -1;
  }
}

static void
gst_audioflinger_sink_dispose (GObject * object)
{
  GstAudioFlingerSink *audioflinger_sink = GST_AUDIOFLINGERSINK (object);

  if (audioflinger_sink->probed_caps) {
    gst_caps_unref (audioflinger_sink->probed_caps);
    audioflinger_sink->probed_caps = NULL;
  }

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gst_audioflinger_sink_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_set_details_simple (element_class,
      "Android Audioflinger Sink",
      "Sink/Audio",
      "Output to android's Audioflinger service",
      "Prajnashi S <prajnashi@gmail.com>, "
      "Alessandro Decina <alessandro.decina@collabora.co.uk>");

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&audioflingersink_sink_factory));
  GST_DEBUG_CATEGORY_INIT (audioflinger_debug, "audioflingersink", 0,
      "audioflinger sink trace");
}

static void
gst_audioflinger_sink_class_init (GstAudioFlingerSinkClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseSinkClass *gstbasesink_class;
  GstBaseAudioSinkClass *gstbaseaudiosink_class;
  GstAudioSinkClass *gstaudiosink_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
  gstbasesink_class = (GstBaseSinkClass *) klass;
  gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass;
  gstaudiosink_class = (GstAudioSinkClass *) klass;

  parent_class = g_type_class_peek_parent (klass);

  gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_audioflinger_sink_dispose);
  gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_audioflinger_sink_finalise);
  gobject_class->get_property =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_get_property);
  gobject_class->set_property =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_set_property);

  gstelement_class->provide_clock =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_provide_clock);
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_change_state);

  gstbasesink_class->get_caps =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_getcaps);

  gstbaseaudiosink_class->create_ringbuffer =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_create_ringbuffer);

  gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_audioflinger_sink_event);
  gstbasesink_class->preroll =
      GST_DEBUG_FUNCPTR (gst_audioflinger_sink_preroll);

  /* Install properties */
  g_object_class_install_property (gobject_class, PROP_MUTE,
      g_param_spec_boolean ("mute", "Mute",
          "Mute output", DEFAULT_MUTE, G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class, PROP_VOLUME,
      g_param_spec_double ("volume", "Volume",
          "control volume size", 0, 1.0, DEFAULT_VOLUME, G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class, PROP_AUDIO_SINK,
      g_param_spec_pointer ("audiosink", "AudioSink",
          "The pointer of MediaPlayerBase::AudioSink", G_PARAM_WRITABLE));
}

static void
gst_audioflinger_sink_init (GstAudioFlingerSink * audioflinger_sink,
    GstAudioFlingerSinkClass * klass)
{
  GST_DEBUG_OBJECT (audioflinger_sink, "initializing audioflinger_sink");

  audioflinger_sink->audio_clock = NULL;
  audioflinger_sink->system_clock = NULL;
  audioflinger_sink->system_audio_clock = NULL;
  audioflinger_sink->exported_clock = NULL;
  audioflinger_sink->export_system_audio_clock =
      DEFAULT_EXPORT_SYSTEM_AUDIO_CLOCK;
  gst_audioflinger_sink_reset (audioflinger_sink, TRUE);
}

static void
gst_audioflinger_sink_reset (GstAudioFlingerSink * sink, gboolean create_clocks)
{

  if (sink->audioflinger_device != NULL) {
    audioflinger_device_release (sink->audioflinger_device);
    sink->audioflinger_device = NULL;
  }

  sink->audioflinger_device = NULL;
  sink->m_volume = DEFAULT_VOLUME;
  sink->m_mute = DEFAULT_MUTE;
  sink->m_init = FALSE;
  sink->m_audiosink = NULL;
  sink->eos = FALSE;
  sink->may_provide_clock = TRUE;
  sink->last_resync_sample = -1;

  if (sink->system_clock) {
    gst_clock_set_master (sink->system_clock, NULL);
    gst_object_replace ((GstObject **) & sink->system_clock, NULL);
    gst_object_replace ((GstObject **) & sink->system_audio_clock, NULL);
  }

  if (sink->audio_clock)
    gst_object_replace ((GstObject **) & sink->audio_clock, NULL);

  if (sink->exported_clock)
    gst_object_replace ((GstObject **) & sink->exported_clock, NULL);

  if (create_clocks) {
    GstClockTime external, internal;

    /* create the audio clock that uses the ringbuffer as its audio source */
    sink->audio_clock = gst_audio_clock_new ("GstAudioFlingerSinkClock",
        gst_audioflinger_sink_get_time, sink);

    /* always set audio_clock as baseaudiosink's provided_clock */
    gst_object_replace ((GstObject **) &
        GST_BASE_AUDIO_SINK (sink)->provided_clock,
        GST_OBJECT (sink->audio_clock));

    /* create the system_audio_clock, which is an *audio clock* that uses an
     * instance of the system clock as its time source */
    sink->system_audio_clock =
        gst_audio_clock_new ("GstAudioFlingerSystemAudioClock",
        gst_audioflinger_sink_system_audio_clock_get_time, sink);

    /* create an instance of the system clock, that we slave to
     * sink->audio_clock to have an audio clock with an higher resolution than
     * the segment size (50ms) */
    sink->system_clock = g_object_new (GST_TYPE_SYSTEM_CLOCK,
        "name", "GstAudioFlingerSystemClock", NULL);

    /* calibrate the clocks */
    external = gst_clock_get_time (sink->audio_clock);
    internal = gst_clock_get_internal_time (sink->system_clock);
    gst_clock_set_calibration (sink->system_clock, internal, external, 1, 1);

    /* slave the system clock to the audio clock */
    GST_OBJECT_FLAG_SET (sink->system_clock, GST_CLOCK_FLAG_CAN_SET_MASTER);
    g_object_set (sink->system_clock, "timeout", 50 * GST_MSECOND, NULL);
    gst_clock_set_master (sink->system_clock, sink->audio_clock);
  }

}

static void
gst_audioflinger_sink_finalise (GObject * object)
{
  GstAudioFlingerSink *audioflinger_sink = GST_AUDIOFLINGERSINK (object);

  GST_INFO_OBJECT (object, "finalize");

  gst_audioflinger_sink_reset (audioflinger_sink, FALSE);

  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (object));
}

static GstRingBuffer *
gst_audioflinger_sink_create_ringbuffer (GstBaseAudioSink * sink)
{
  GstRingBuffer *buffer;

  GST_DEBUG_OBJECT (sink, "creating ringbuffer");
  LOGD ("creating ringbuffer");
  buffer = g_object_new (GST_TYPE_ANDROID_AUDIORING_BUFFER, NULL);
  GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer);
  LOGD ("created ringbuffer @%p", buffer);

  return buffer;
}

static void
gst_audioflinger_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstAudioFlingerSink *audioflinger_sink;

  audioflinger_sink = GST_AUDIOFLINGERSINK (object);
  g_return_if_fail (audioflinger_sink != NULL);

  switch (prop_id) {
    case PROP_MUTE:
      g_value_set_boolean (value, audioflinger_sink->m_mute);
      GST_DEBUG_OBJECT (audioflinger_sink, "get mute: %d",
          audioflinger_sink->m_mute);
      break;
    case PROP_VOLUME:
      g_value_set_double (value, audioflinger_sink->m_volume);
      GST_DEBUG_OBJECT (audioflinger_sink, "get volume: %f",
          audioflinger_sink->m_volume);
      break;
    case PROP_AUDIO_SINK:
      GST_ERROR_OBJECT (audioflinger_sink, "Shall not go here!");
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_audioflinger_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstAudioFlingerSink *audioflinger_sink;
  audioflinger_sink = GST_AUDIOFLINGERSINK (object);

  g_return_if_fail (audioflinger_sink != NULL);
  GST_OBJECT_LOCK (audioflinger_sink);
  switch (prop_id) {
    case PROP_MUTE:
      audioflinger_sink->m_mute = g_value_get_boolean (value);
      GST_DEBUG_OBJECT (audioflinger_sink, "set mute: %d",
          audioflinger_sink->m_mute);
      /* set device if it's initialized */
      if (audioflinger_sink->audioflinger_device && audioflinger_sink->m_init)
        gst_audioflinger_sink_set_mute (audioflinger_sink,
            (int) (audioflinger_sink->m_mute));
      break;
    case PROP_VOLUME:
      audioflinger_sink->m_volume = g_value_get_double (value);
      GST_DEBUG_OBJECT (audioflinger_sink, "set volume: %f",
          audioflinger_sink->m_volume);
      /* set device if it's initialized */
      if (audioflinger_sink->audioflinger_device && audioflinger_sink->m_init)
        gst_audioflinger_sink_set_volume (audioflinger_sink,
            (float) audioflinger_sink->m_volume);
      break;
    case PROP_AUDIO_SINK:
      audioflinger_sink->m_audiosink = g_value_get_pointer (value);
      GST_DEBUG_OBJECT (audioflinger_sink, "set audiosink: %p",
          audioflinger_sink->m_audiosink);
      LOGD ("set audiosink: %p", audioflinger_sink->m_audiosink);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
  GST_OBJECT_UNLOCK (audioflinger_sink);
}

static GstCaps *
gst_audioflinger_sink_getcaps (GstBaseSink * bsink)
{
  GstAudioFlingerSink *audioflinger_sink;
  GstCaps *caps;

  audioflinger_sink = GST_AUDIOFLINGERSINK (bsink);
  GST_DEBUG_OBJECT (audioflinger_sink, "enter,%p",
      audioflinger_sink->audioflinger_device);
  LOGD ("gst_audioflinger_sink_getcaps,%p",
      audioflinger_sink->audioflinger_device);
  if (audioflinger_sink->audioflinger_device == NULL
      || audioflinger_sink->m_init == FALSE) {
    caps =
        gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD
            (bsink)));
  } else if (audioflinger_sink->probed_caps) {
    caps = gst_caps_copy (audioflinger_sink->probed_caps);
  } else {
    caps = gst_caps_new_any ();
    if (caps && !gst_caps_is_empty (caps)) {
      audioflinger_sink->probed_caps = gst_caps_copy (caps);
    }
  }

  return caps;
}

static gboolean
gst_audioflinger_sink_open (GstAudioFlingerSink * audioflinger)
{
  GstBaseAudioSink *baseaudiosink = (GstBaseAudioSink *) audioflinger;

  GST_DEBUG_OBJECT (audioflinger, "enter");
  LOGV ("gst_audioflinger_sink_open");
  g_return_val_if_fail (audioflinger != NULL, FALSE);

  baseaudiosink->buffer_time = DEFAULT_BUFFERTIME;
  baseaudiosink->latency_time = DEFAULT_LATENCYTIME;

  if (audioflinger->audioflinger_device == NULL) {
    if (audioflinger->m_audiosink) {
      if (!(audioflinger->audioflinger_device =
              audioflinger_device_open (audioflinger->m_audiosink)))
        goto failed_creation;
      GST_DEBUG_OBJECT (audioflinger, "open an existed flinger, %p",
          audioflinger->audioflinger_device);
      LOGD ("open an existed flinger, %p", audioflinger->audioflinger_device);
    } else {
      if (!(audioflinger->audioflinger_device = audioflinger_device_create ()))
        goto failed_creation;
      GST_DEBUG_OBJECT (audioflinger, "create a new flinger, %p",
          audioflinger->audioflinger_device);
      LOGD ("create a new flinger, %p", audioflinger->audioflinger_device);
    }
  }
  return TRUE;

  /* ERRORS */
failed_creation:
  {
    GST_ELEMENT_ERROR (audioflinger, RESOURCE, SETTINGS, (NULL),
        ("Failed to create AudioFlinger"));
    LOGE ("Failed to create AudioFlinger");
    return FALSE;
  }
}

static gboolean
gst_audioflinger_sink_close (GstAudioFlingerSink * audioflinger)
{
  GST_DEBUG_OBJECT (audioflinger, "enter");
  LOGD ("gst_audioflinger_sink_close");

  if (audioflinger->audioflinger_device != NULL) {
    GST_DEBUG_OBJECT (audioflinger, "release flinger device");
    LOGD ("release flinger device");
    audioflinger_device_stop (audioflinger->audioflinger_device);
    audioflinger_device_release (audioflinger->audioflinger_device);
    audioflinger->audioflinger_device = NULL;
  }
  return TRUE;
}

static gboolean
gst_audioflinger_sink_prepare (GstAudioFlingerSink * audioflinger,
    GstRingBufferSpec * spec)
{
  GST_DEBUG_OBJECT (audioflinger, "enter");
  LOGD ("gst_audioflinger_sink_prepare");

  /* FIXME: 
   * 
   * Pipeline crashes in audioflinger_device_set(), after releasing audio
   * flinger device and creating it again. In most cases, it will happen when
   * playing the same audio again.
   *
   * It seems the root cause is we create and release audio flinger sink in
   * different thread in playbin2. Till now, I haven't found way to
   * create/release device in the same thread. Fortunately, it will not effect
   * the gst-launch usage 
   */
  if (audioflinger_device_set (audioflinger->audioflinger_device,
          3, spec->channels, spec->rate, spec->segsize) == -1)
    goto failed_creation;

  audioflinger->m_init = TRUE;
  spec->bytes_per_sample = (spec->width / 8) * spec->channels;
  audioflinger->bytes_per_sample = spec->bytes_per_sample;

  spec->segsize =
      audioflinger_device_frameCount (audioflinger->audioflinger_device) *
      spec->bytes_per_sample;

  GST_DEBUG_OBJECT (audioflinger,
      "channels: %d, rate: %d, width: %d, got segsize: %d, segtotal: %d, "
      "frame count: %d, frame size: %d",
      spec->channels, spec->rate, spec->width, spec->segsize, spec->segtotal,
      audioflinger_device_frameCount (audioflinger->audioflinger_device),
      audioflinger_device_frameSize (audioflinger->audioflinger_device)
      );
  LOGD ("channels: %d, rate: %d, width: %d, got segsize: %d, segtotal: %d, "
      "frame count: %d, frame size: %d",
      spec->channels, spec->rate, spec->width, spec->segsize, spec->segtotal,
      audioflinger_device_frameCount (audioflinger->audioflinger_device),
      audioflinger_device_frameSize (audioflinger->audioflinger_device)
      );

#if 0
  GST_DEBUG_OBJECT (audioflinger, "pause device");
  LOGV ("pause device");
  audioflinger_device_pause (audioflinger->audioflinger_device);
#endif

  return TRUE;

  /* ERRORS */
failed_creation:
  {
    GST_ELEMENT_ERROR (audioflinger, RESOURCE, SETTINGS, (NULL),
        ("Failed to create AudioFlinger for format %d", spec->format));
    LOGE ("Failed to create AudioFlinger for format %d", spec->format);
    return FALSE;
  }
}

static gboolean
gst_audioflinger_sink_unprepare (GstAudioFlingerSink * audioflinger)
{
  GST_DEBUG_OBJECT (audioflinger, "enter");
  LOGD ("gst_audioflinger_sink_unprepare");

  if (audioflinger->audioflinger_device != NULL) {
    GST_DEBUG_OBJECT (audioflinger, "release flinger device");
    LOGD ("release flinger device");
    audioflinger_device_stop (audioflinger->audioflinger_device);
    audioflinger->m_init = FALSE;
  }

  return TRUE;
}

static void
gst_audioflinger_sink_set_mute (GstAudioFlingerSink * audioflinger_sink,
    gboolean mute)
{
  GST_DEBUG_OBJECT (audioflinger_sink, "set PROP_MUTE = %d\n", mute);
  LOGD ("set PROP_MUTE = %d\n", mute);

  if (audioflinger_sink->audioflinger_device)
    audioflinger_device_mute (audioflinger_sink->audioflinger_device, mute);
  audioflinger_sink->m_mute = mute;
}

static void
gst_audioflinger_sink_set_volume (GstAudioFlingerSink * audioflinger_sink,
    float volume)
{
  GST_DEBUG_OBJECT (audioflinger_sink, "set PROP_VOLUME = %f\n", volume);
  LOGD ("set PROP_VOLUME = %f\n", volume);

  if (audioflinger_sink->audioflinger_device != NULL) {
    audioflinger_device_set_volume (audioflinger_sink->audioflinger_device,
        volume, volume);
  }
}

gboolean
gst_audioflinger_sink_plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "audioflingersink", GST_RANK_PRIMARY,
      GST_TYPE_AUDIOFLINGERSINK);
}

/*
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "audioflingersink",
    "audioflinger sink audio", plugin_init, VERSION, "LGPL", "GStreamer",
    "http://gstreamer.net/")
    */

static GstClock *
gst_audioflinger_sink_provide_clock (GstElement * elem)
{
  GstBaseAudioSink *sink;
  GstAudioFlingerSink *asink;
  GstClock *clock;

  sink = GST_BASE_AUDIO_SINK (elem);
  asink = GST_AUDIOFLINGERSINK (elem);

  /* we have no ringbuffer (must be NULL state) */
  if (sink->ringbuffer == NULL)
    goto wrong_state;

  if (!gst_ring_buffer_is_acquired (sink->ringbuffer))
    goto wrong_state;

  GST_OBJECT_LOCK (sink);
  if (!asink->may_provide_clock)
    goto already_playing;

  if (!sink->provide_clock)
    goto clock_disabled;

  clock = GST_CLOCK_CAST (gst_object_ref (asink->exported_clock));
  GST_INFO_OBJECT (asink, "providing clock %p %s", clock,
      clock == NULL ? NULL : GST_OBJECT_NAME (clock));
  GST_OBJECT_UNLOCK (sink);

  return clock;

  /* ERRORS */
wrong_state:
  {
    GST_DEBUG_OBJECT (sink, "ringbuffer not acquired");
    return NULL;
  }
already_playing:
  {
    GST_INFO_OBJECT (sink, "we went to playing already");
    GST_OBJECT_UNLOCK (sink);
    return NULL;
  }
clock_disabled:
  {
    GST_DEBUG_OBJECT (sink, "clock provide disabled");
    GST_OBJECT_UNLOCK (sink);
    return NULL;
  }
}

static GstStateChangeReturn
gst_audioflinger_sink_change_state (GstElement * element,
    GstStateChange transition)
{
  GstStateChangeReturn ret;
  GstClockTime time;
  GstAudioFlingerSink *sink = GST_AUDIOFLINGERSINK (element);

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      sink->may_provide_clock = FALSE;
      if (sink->exported_clock == sink->system_audio_clock) {
        GstClockTime cinternal, cexternal, crate_num, crate_denom;
        GstClockTime before;

        /* take the slave lock to make sure that the slave_callback doesn't run
         * while we're moving sink->audio_clock forward, causing
         * sink->system_clock to jump as well */
        GST_CLOCK_SLAVE_LOCK (sink->system_clock);
        before = gst_clock_get_time (sink->audio_clock);
        gst_clock_get_calibration (sink->audio_clock, NULL, NULL,
            &crate_num, &crate_denom);
        cinternal = gst_clock_get_internal_time (sink->audio_clock);
        cexternal = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
        gst_clock_set_calibration (sink->audio_clock, cinternal, cexternal,
            crate_num, crate_denom);
        /* reset observations */
        sink->system_clock->filling = TRUE;
        sink->system_clock->time_index = 0;
        GST_CLOCK_SLAVE_UNLOCK (sink->system_clock);

        time = gst_clock_get_time (sink->audio_clock);
        GST_INFO_OBJECT (sink, "PAUSED_TO_PLAYING,"
            " base_time %" GST_TIME_FORMAT
            " before %" GST_TIME_FORMAT " after %" GST_TIME_FORMAT
            " internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT,
            GST_TIME_ARGS (GST_ELEMENT (sink)->base_time),
            GST_TIME_ARGS (before), GST_TIME_ARGS (time),
            GST_TIME_ARGS (cinternal), GST_TIME_ARGS (cexternal));
      }
      break;
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      break;
    default:
      break;
  }
  return ret;
}

static GstFlowReturn
gst_audioflinger_sink_preroll (GstBaseSink * bsink, GstBuffer * buffer)
{
  GstFlowReturn ret;
  gboolean us_live = FALSE;
  GstQuery *query;
  GstAudioFlingerSink *asink = GST_AUDIOFLINGERSINK (bsink);
  GstClock *clock;

  GST_INFO_OBJECT (bsink, "preroll");

  ret = GST_BASE_SINK_CLASS (parent_class)->preroll (bsink, buffer);
  if (ret != GST_FLOW_OK)
    goto done;

  if (asink->exported_clock != NULL) {
    GST_INFO_OBJECT (bsink, "clock already exported");
    goto done;
  }

  query = gst_query_new_latency ();

  /* ask the peer for the latency */
  if (gst_pad_peer_query (bsink->sinkpad, query)) {
    /* get upstream min and max latency */
    gst_query_parse_latency (query, &us_live, NULL, NULL);
    GST_INFO_OBJECT (bsink, "query result live: %d", us_live);
  } else {
    GST_WARNING_OBJECT (bsink, "latency query failed");
  }
  gst_query_unref (query);

  if (!us_live && asink->export_system_audio_clock) {
    clock = asink->system_audio_clock;
    /* set SLAVE_NONE so that baseaudiosink doesn't try to slave audio_clock to
     * system_audio_clock
     */
    g_object_set (asink, "slave-method", GST_BASE_AUDIO_SINK_SLAVE_NONE, NULL);
  } else {
    clock = asink->audio_clock;
  }

  GST_INFO_OBJECT (bsink, "using %s clock",
      clock == asink->audio_clock ? "audio" : "system_audio");
  gst_object_replace ((GstObject **) & asink->exported_clock,
      GST_OBJECT (clock));
  GST_OBJECT_UNLOCK (asink);

done:
  return ret;
}

static gboolean
gst_audioflinger_sink_event (GstBaseSink * bsink, GstEvent * event)
{
  gboolean res;
  GstAudioFlingerSink *asink = GST_AUDIOFLINGERSINK (bsink);
  GstBaseAudioSink *baseaudiosink = GST_BASE_AUDIO_SINK (bsink);
  GstRingBuffer *ringbuf = baseaudiosink->ringbuffer;
  const gchar *event_name;

  event_name = GST_EVENT_TYPE_NAME (event);
  GST_INFO_OBJECT (bsink, "received %s event", event_name);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_EOS:
      GST_INFO_OBJECT (asink, "got EOS");
      asink->eos = TRUE;

      if (baseaudiosink->next_sample != -1) {
        guint64 next_sample, sample;
        gint sps;
        GstFlowReturn ret;

        sps = ringbuf->spec.segsize / ringbuf->spec.bytes_per_sample;
        sample = baseaudiosink->next_sample;
        next_sample = baseaudiosink->next_sample / sps;
        if (next_sample < ringbuf->spec.segsize) {
          gint samples, out_samples, accum, size;
          GstClockTime timestamp, before, after;
          guchar *data, *data_start;
          guint written;
          guint64 offset;

          samples = (ringbuf->spec.segsize - next_sample);
          size = samples * ringbuf->spec.bytes_per_sample;
          timestamp = gst_util_uint64_scale_int (baseaudiosink->next_sample,
              GST_SECOND, ringbuf->spec.rate);

          before = gst_clock_get_internal_time (asink->audio_clock);
          GST_INFO_OBJECT (asink, "%" G_GINT64_FORMAT " < %d, "
              "padding with silence, samples %d size %d ts %" GST_TIME_FORMAT,
              next_sample, ringbuf->spec.segsize, samples, size,
              GST_TIME_ARGS (timestamp));
          LOGV ("PADDING");

          data_start = data = g_malloc0 (size);
          offset = baseaudiosink->next_sample;
          out_samples = samples;

          GST_INFO_OBJECT (bsink, "about to write");
          do {
            written =
                gst_ring_buffer_commit_full (ringbuf, &offset, data, samples,
                out_samples, &accum);

            GST_INFO_OBJECT (bsink, "wrote %u of %u", written, samples);
            /* if we wrote all, we're done */
            if (written == samples)
              break;

            /* else something interrupted us and we wait for preroll. */
            if ((ret = gst_base_sink_wait_preroll (bsink)) != GST_FLOW_OK)
              break;

            /* update the output samples. FIXME, this will just skip them when pausing
             * during trick mode */
            if (out_samples > written) {
              out_samples -= written;
              accum = 0;
            } else
              break;

            samples -= written;
            data += written * ringbuf->spec.bytes_per_sample;
          } while (TRUE);

          g_free (data_start);
          after = gst_clock_get_internal_time (asink->audio_clock);
          GST_INFO_OBJECT (asink, "padded, left %d before %" GST_TIME_FORMAT
              " after %" GST_TIME_FORMAT, samples,
              GST_TIME_ARGS (before), GST_TIME_ARGS (after));
        } else {
          LOGV ("NOT PADDING 1");
        }
      } else {
        LOGV ("NOT PADDING 2");
      }

      break;
#if 0
    case GST_EVENT_BUFFERING_START:
      GST_INFO_OBJECT (asink, "buffering start");
      break;
    case GST_EVENT_BUFFERING_STOP:
    {
      gboolean slaved;
      GstClockTime cinternal, cexternal, crate_num, crate_denom;
      GstClockTime before, after;

      gst_clock_get_calibration (asink->audio_clock, &cinternal, &cexternal,
          &crate_num, &crate_denom);

      before = gst_clock_get_time (asink->audio_clock);

      cinternal = gst_clock_get_internal_time (asink->audio_clock);
      cexternal = gst_clock_get_time (GST_ELEMENT_CLOCK (asink));
      gst_clock_set_calibration (asink->audio_clock, cinternal,
          cexternal, crate_num, crate_denom);

      after = gst_clock_get_time (asink->audio_clock);

      GST_INFO_OBJECT (asink, "buffering stopped, clock recalibrated"
          " before %" GST_TIME_FORMAT " after %" GST_TIME_FORMAT,
          GST_TIME_ARGS (before), GST_TIME_ARGS (after));

      /* force baseaudiosink to resync from the next buffer */
      GST_BASE_AUDIO_SINK (asink)->next_sample = -1;

      /* reset this so we allow some time before enabling slaving again */
      asink->last_resync_sample = -1;
      slaved = GST_ELEMENT_CLOCK (asink) != asink->exported_clock;
      if (slaved) {
        GST_INFO_OBJECT (asink, "disabling slaving");
        g_object_set (asink, "slave-method", GST_BASE_AUDIO_SINK_SLAVE_NONE,
            NULL);
        asink->slaving_disabled = TRUE;
      }

      g_object_set (asink, "drift-tolerance", 200 * GST_MSECOND, NULL);
      break;
    }
#endif
    default:
      break;
  }

  GST_INFO_OBJECT (bsink, "chaining up event %s", event_name);

  res = GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);

  GST_INFO_OBJECT (bsink, "done with event %s", event_name);

  return res;
}

static GstClockTime
gst_audioflinger_sink_get_time (GstClock * clock, gpointer user_data)
{
  GstBaseAudioSink *sink = GST_BASE_AUDIO_SINK (user_data);
  uint32_t position = -1;
  GstAudioFlingerSink *asink = GST_AUDIOFLINGERSINK (sink);
  GstClockTime time = GST_CLOCK_TIME_NONE;
  GstClockTime pipeline_time = GST_CLOCK_TIME_NONE;
  GstClockTime system_audio_clock_time = GST_CLOCK_TIME_NONE;
  GstClockTime offset = GST_CLOCK_TIME_NONE;
  GstClockTime adjusted_time = GST_CLOCK_TIME_NONE;
  GstClockTime cinternal, cexternal, crate_num, crate_denom;
  gint64 latency, mixer_latency;

  gst_clock_get_calibration (clock, &cinternal, &cexternal,
      &crate_num, &crate_denom);

  if (!asink->audioflinger_device || !asink->m_init) {
    GST_LOG_OBJECT (sink, "device not created yet");

    goto out;
  }

  if (!sink->ringbuffer) {
    GST_DEBUG_OBJECT (sink, "NULL ringbuffer");

    goto out;
  }

  if (!sink->ringbuffer->acquired) {
    GST_DEBUG_OBJECT (sink, "ringbuffer not acquired");

    goto out;
  }

  position = audioflinger_device_get_position (asink->audioflinger_device);
  if (position == -1)
    goto out;

  /* AudioTrack::latency returns the audioflinger latency + the sw mixer latency.
   * The mixer latency doesn't apply here since we get clock values "after"
   * that latency. We just need to get the audioflinger latency.
   *
   * see AudioTrack::set and gst_base_audio_sink_setcaps to understand how
   * latency and mixer_latency are computed.
   */
  latency =
      audioflinger_device_latency (asink->audioflinger_device) * GST_MSECOND;
  mixer_latency = sink->ringbuffer->spec.latency_time * GST_USECOND;
  latency -= mixer_latency;

  time = gst_util_uint64_scale_int (position, GST_SECOND,
      sink->ringbuffer->spec.rate);

  if (time >= latency)
    time -= latency;
  else
    time = 0;

  offset = gst_audio_clock_adjust (GST_CLOCK (clock), 0);
  adjusted_time = gst_audio_clock_adjust (GST_CLOCK (clock), time);

  if (asink->system_audio_clock)
    system_audio_clock_time = gst_clock_get_time (asink->system_audio_clock);

  if (GST_ELEMENT_CLOCK (asink)
      && asink->audio_clock != GST_ELEMENT_CLOCK (asink))
    pipeline_time = gst_clock_get_time (GST_ELEMENT_CLOCK (asink));

out:

  GST_LOG_OBJECT (sink,
      "processed samples %" G_GINT32_FORMAT
      " time %" GST_TIME_FORMAT
      " latency %" GST_TIME_FORMAT
      " pipeline time %" GST_TIME_FORMAT
      " system_audio_clock %" GST_TIME_FORMAT
      " adjusted_time %" GST_TIME_FORMAT
      " cinternal %" GST_TIME_FORMAT
      " cexternal %" GST_TIME_FORMAT,
      position, GST_TIME_ARGS (time), GST_TIME_ARGS (latency),
      GST_TIME_ARGS (pipeline_time), GST_TIME_ARGS (system_audio_clock_time),
      GST_TIME_ARGS (adjusted_time), GST_TIME_ARGS (cinternal),
      GST_TIME_ARGS (cexternal));

  return time;
}

static GstClockTime
gst_audioflinger_sink_system_audio_clock_get_time (GstClock * clock,
    gpointer user_data)
{
  GstClockTime time, offset;
  GstAudioFlingerSink *sink = GST_AUDIOFLINGERSINK (user_data);

  time = gst_clock_get_time (sink->system_clock);
  offset = gst_audio_clock_adjust (clock, (GstClockTime) 0);
  time -= offset;

  return time;
}
