Added gst-plugins-base-subtitles0.10-0.10.34 for Meego Harmattan 1.2
[mafwsubrenderer] / gst-plugins-base-subtitles0.10 / gst / videorate / gstvideorate.c
diff --git a/gst-plugins-base-subtitles0.10/gst/videorate/gstvideorate.c b/gst-plugins-base-subtitles0.10/gst/videorate/gstvideorate.c
new file mode 100644 (file)
index 0000000..4f53c81
--- /dev/null
@@ -0,0 +1,1024 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ *
+ * 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-videorate
+ *
+ * This element takes an incoming stream of timestamped video frames.
+ * It will produce a perfect stream that matches the source pad's framerate.
+ *
+ * The correction is performed by dropping and duplicating frames, no fancy
+ * algorithm is used to interpolate frames (yet).
+ *
+ * By default the element will simply negotiate the same framerate on its
+ * source and sink pad.
+ *
+ * This operation is useful to link to elements that require a perfect stream.
+ * Typical examples are formats that do not store timestamps for video frames,
+ * but only store a framerate, like Ogg and AVI.
+ *
+ * A conversion to a specific framerate can be forced by using filtered caps on
+ * the source pad.
+ *
+ * The properties #GstVideoRate:in, #GstVideoRate:out, #GstVideoRate:duplicate
+ * and #GstVideoRate:drop can be read to obtain information about number of
+ * input frames, output frames, dropped frames (i.e. the number of unused input
+ * frames) and duplicated frames (i.e. the number of times an input frame was
+ * duplicated, beside being used normally).
+ *
+ * An input stream that needs no adjustments will thus never have dropped or
+ * duplicated frames.
+ *
+ * When the #GstVideoRate:silent property is set to FALSE, a GObject property
+ * notification will be emitted whenever one of the #GstVideoRate:duplicate or
+ * #GstVideoRate:drop values changes.
+ * This can potentially cause performance degradation.
+ * Note that property notification will happen from the streaming thread, so
+ * applications should be prepared for this.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch -v filesrc location=videotestsrc.ogg ! oggdemux ! theoradec ! videorate ! video/x-raw-yuv,framerate=15/1 ! xvimagesink
+ * ]| Decode an Ogg/Theora file and adjust the framerate to 15 fps before playing.
+ * To create the test Ogg/Theora file refer to the documentation of theoraenc.
+ * |[
+ * gst-launch -v v4lsrc ! videorate ! video/x-raw-yuv,framerate=25/2 ! theoraenc ! oggmux ! filesink location=v4l.ogg
+ * ]| Capture video from a V4L device, and adjust the stream to 12.5 fps before
+ * encoding to Ogg/Theora.
+ * </refsect2>
+ *
+ * Last reviewed on 2006-09-02 (0.10.11)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvideorate.h"
+
+GST_DEBUG_CATEGORY_STATIC (video_rate_debug);
+#define GST_CAT_DEFAULT video_rate_debug
+
+/* GstVideoRate signals and args */
+enum
+{
+  /* FILL ME */
+  LAST_SIGNAL
+};
+
+#define DEFAULT_SILENT          TRUE
+#define DEFAULT_NEW_PREF        1.0
+#define DEFAULT_SKIP_TO_FIRST   FALSE
+
+enum
+{
+  ARG_0,
+  ARG_IN,
+  ARG_OUT,
+  ARG_DUP,
+  ARG_DROP,
+  ARG_SILENT,
+  ARG_NEW_PREF,
+  ARG_SKIP_TO_FIRST
+      /* FILL ME */
+};
+
+static GstStaticPadTemplate gst_video_rate_src_template =
+    GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw-yuv;"
+        "video/x-raw-rgb;" "video/x-raw-gray;" "image/jpeg;" "image/png")
+    );
+
+static GstStaticPadTemplate gst_video_rate_sink_template =
+    GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw-yuv;"
+        "video/x-raw-rgb;" "video/x-raw-gray;" "image/jpeg;" "image/png")
+    );
+
+static void gst_video_rate_swap_prev (GstVideoRate * videorate,
+    GstBuffer * buffer, gint64 time);
+static gboolean gst_video_rate_event (GstPad * pad, GstEvent * event);
+static gboolean gst_video_rate_query (GstPad * pad, GstQuery * query);
+static GstFlowReturn gst_video_rate_chain (GstPad * pad, GstBuffer * buffer);
+
+static void gst_video_rate_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_video_rate_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn gst_video_rate_change_state (GstElement * element,
+    GstStateChange transition);
+
+/*static guint gst_video_rate_signals[LAST_SIGNAL] = { 0 }; */
+
+static GParamSpec *pspec_drop = NULL;
+static GParamSpec *pspec_duplicate = NULL;
+
+GST_BOILERPLATE (GstVideoRate, gst_video_rate, GstElement, GST_TYPE_ELEMENT);
+
+static void
+gst_video_rate_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_set_details_simple (element_class,
+      "Video rate adjuster", "Filter/Effect/Video",
+      "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream",
+      "Wim Taymans <wim@fluendo.com>");
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_video_rate_sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_video_rate_src_template));
+}
+
+static void
+gst_video_rate_class_init (GstVideoRateClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  object_class->set_property = gst_video_rate_set_property;
+  object_class->get_property = gst_video_rate_get_property;
+
+  g_object_class_install_property (object_class, ARG_IN,
+      g_param_spec_uint64 ("in", "In",
+          "Number of input frames", 0, G_MAXUINT64, 0,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, ARG_OUT,
+      g_param_spec_uint64 ("out", "Out", "Number of output frames", 0,
+          G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  pspec_duplicate = g_param_spec_uint64 ("duplicate", "Duplicate",
+      "Number of duplicated frames", 0, G_MAXUINT64, 0,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, ARG_DUP, pspec_duplicate);
+  pspec_drop = g_param_spec_uint64 ("drop", "Drop", "Number of dropped frames",
+      0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, ARG_DROP, pspec_drop);
+  g_object_class_install_property (object_class, ARG_SILENT,
+      g_param_spec_boolean ("silent", "silent",
+          "Don't emit notify for dropped and duplicated frames", DEFAULT_SILENT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, ARG_NEW_PREF,
+      g_param_spec_double ("new-pref", "New Pref",
+          "Value indicating how much to prefer new frames (unused)", 0.0, 1.0,
+          DEFAULT_NEW_PREF, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstVideoRate:skip-to-first:
+   * 
+   * Don't produce buffers before the first one we receive.
+   *
+   * Since: 0.10.25
+   */
+  g_object_class_install_property (object_class, ARG_SKIP_TO_FIRST,
+      g_param_spec_boolean ("skip-to-first", "Skip to first buffer",
+          "Don't produce buffers before the first one we receive",
+          DEFAULT_SKIP_TO_FIRST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_video_rate_change_state);
+}
+
+/* return the caps that can be used on out_pad given in_caps on in_pad */
+static gboolean
+gst_video_rate_transformcaps (GstPad * in_pad, GstCaps * in_caps,
+    GstPad * out_pad, GstCaps ** out_caps)
+{
+  GstCaps *intersect;
+  const GstCaps *in_templ;
+  gint i;
+  GSList *extra_structures = NULL;
+  GSList *iter;
+
+  in_templ = gst_pad_get_pad_template_caps (in_pad);
+  intersect = gst_caps_intersect (in_caps, in_templ);
+
+  /* all possible framerates are allowed */
+  for (i = 0; i < gst_caps_get_size (intersect); i++) {
+    GstStructure *structure;
+
+    structure = gst_caps_get_structure (intersect, i);
+
+    if (gst_structure_has_field (structure, "framerate")) {
+      GstStructure *copy_structure;
+
+      copy_structure = gst_structure_copy (structure);
+      gst_structure_set (copy_structure,
+          "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
+      extra_structures = g_slist_append (extra_structures, copy_structure);
+    }
+  }
+
+  /* append the extra structures */
+  for (iter = extra_structures; iter != NULL; iter = g_slist_next (iter)) {
+    gst_caps_append_structure (intersect, (GstStructure *) iter->data);
+  }
+  g_slist_free (extra_structures);
+
+  *out_caps = intersect;
+
+  return TRUE;
+}
+
+static GstCaps *
+gst_video_rate_getcaps (GstPad * pad)
+{
+  GstVideoRate *videorate;
+  GstPad *otherpad;
+  GstCaps *caps;
+
+  videorate = GST_VIDEO_RATE (GST_PAD_PARENT (pad));
+
+  otherpad = (pad == videorate->srcpad) ? videorate->sinkpad :
+      videorate->srcpad;
+
+  /* we can do what the peer can */
+  caps = gst_pad_peer_get_caps (otherpad);
+  if (caps) {
+    GstCaps *transform;
+
+    gst_video_rate_transformcaps (otherpad, caps, pad, &transform);
+    gst_caps_unref (caps);
+    caps = transform;
+  } else {
+    /* no peer, our padtemplate is enough then */
+    caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
+  }
+
+  return caps;
+}
+
+static gboolean
+gst_video_rate_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstVideoRate *videorate;
+  GstStructure *structure;
+  gboolean ret = TRUE;
+  GstPad *otherpad, *opeer;
+  gint rate_numerator, rate_denominator;
+
+  videorate = GST_VIDEO_RATE (gst_pad_get_parent (pad));
+
+  GST_DEBUG_OBJECT (pad, "setcaps called %" GST_PTR_FORMAT, caps);
+
+  structure = gst_caps_get_structure (caps, 0);
+  if (!gst_structure_get_fraction (structure, "framerate",
+          &rate_numerator, &rate_denominator))
+    goto no_framerate;
+
+  if (pad == videorate->srcpad) {
+    /* out_frame_count is scaled by the frame rate caps when calculating next_ts.
+     * when the frame rate caps change, we must update base_ts and reset
+     * out_frame_count */
+    if (videorate->to_rate_numerator) {
+      videorate->base_ts +=
+          gst_util_uint64_scale (videorate->out_frame_count,
+          videorate->to_rate_denominator * GST_SECOND,
+          videorate->to_rate_numerator);
+    }
+    videorate->out_frame_count = 0;
+    videorate->to_rate_numerator = rate_numerator;
+    videorate->to_rate_denominator = rate_denominator;
+    otherpad = videorate->sinkpad;
+  } else {
+    videorate->from_rate_numerator = rate_numerator;
+    videorate->from_rate_denominator = rate_denominator;
+    otherpad = videorate->srcpad;
+  }
+
+  /* now try to find something for the peer */
+  opeer = gst_pad_get_peer (otherpad);
+  if (opeer) {
+    if (gst_pad_accept_caps (opeer, caps)) {
+      /* the peer accepts the caps as they are */
+      gst_pad_set_caps (otherpad, caps);
+
+      ret = TRUE;
+    } else {
+      GstCaps *peercaps;
+      GstCaps *transform = NULL;
+
+      ret = FALSE;
+
+      /* see how we can transform the input caps */
+      if (!gst_video_rate_transformcaps (pad, caps, otherpad, &transform))
+        goto no_transform;
+
+      /* see what the peer can do */
+      peercaps = gst_pad_get_caps (opeer);
+
+      GST_DEBUG_OBJECT (opeer, "icaps %" GST_PTR_FORMAT, peercaps);
+      GST_DEBUG_OBJECT (videorate, "transform %" GST_PTR_FORMAT, transform);
+
+      /* filter against our possibilities */
+      caps = gst_caps_intersect (peercaps, transform);
+      gst_caps_unref (peercaps);
+      gst_caps_unref (transform);
+
+      GST_DEBUG_OBJECT (videorate, "intersect %" GST_PTR_FORMAT, caps);
+
+      /* could turn up empty, due to e.g. colorspace etc */
+      if (gst_caps_get_size (caps) == 0) {
+        gst_caps_unref (caps);
+        goto no_transform;
+      }
+
+      /* take first possibility */
+      gst_caps_truncate (caps);
+      structure = gst_caps_get_structure (caps, 0);
+
+      /* and fixate */
+      gst_structure_fixate_field_nearest_fraction (structure, "framerate",
+          rate_numerator, rate_denominator);
+
+      gst_structure_get_fraction (structure, "framerate",
+          &rate_numerator, &rate_denominator);
+
+      if (otherpad == videorate->srcpad) {
+        videorate->to_rate_numerator = rate_numerator;
+        videorate->to_rate_denominator = rate_denominator;
+      } else {
+        videorate->from_rate_numerator = rate_numerator;
+        videorate->from_rate_denominator = rate_denominator;
+      }
+
+      if (gst_structure_has_field (structure, "interlaced"))
+        gst_structure_fixate_field_boolean (structure, "interlaced", FALSE);
+      if (gst_structure_has_field (structure, "color-matrix"))
+        gst_structure_fixate_field_string (structure, "color-matrix", "sdtv");
+      if (gst_structure_has_field (structure, "chroma-site"))
+        gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2");
+      if (gst_structure_has_field (structure, "pixel-aspect-ratio"))
+        gst_structure_fixate_field_nearest_fraction (structure,
+            "pixel-aspect-ratio", 1, 1);
+
+      gst_pad_set_caps (otherpad, caps);
+      gst_caps_unref (caps);
+      ret = TRUE;
+    }
+    gst_object_unref (opeer);
+  }
+done:
+  /* After a setcaps, our caps may have changed. In that case, we can't use
+   * the old buffer, if there was one (it might have different dimensions) */
+  GST_DEBUG_OBJECT (videorate, "swapping old buffers");
+  gst_video_rate_swap_prev (videorate, NULL, GST_CLOCK_TIME_NONE);
+
+  gst_object_unref (videorate);
+  return ret;
+
+no_framerate:
+  {
+    GST_DEBUG_OBJECT (videorate, "no framerate specified");
+    goto done;
+  }
+no_transform:
+  {
+    GST_DEBUG_OBJECT (videorate, "no framerate transform possible");
+    ret = FALSE;
+    goto done;
+  }
+}
+
+static void
+gst_video_rate_reset (GstVideoRate * videorate)
+{
+  GST_DEBUG_OBJECT (videorate, "resetting internal variables");
+
+  videorate->in = 0;
+  videorate->out = 0;
+  videorate->base_ts = 0;
+  videorate->out_frame_count = 0;
+  videorate->drop = 0;
+  videorate->dup = 0;
+  videorate->next_ts = GST_CLOCK_TIME_NONE;
+  videorate->last_ts = GST_CLOCK_TIME_NONE;
+  videorate->discont = TRUE;
+  gst_video_rate_swap_prev (videorate, NULL, 0);
+
+  gst_segment_init (&videorate->segment, GST_FORMAT_TIME);
+}
+
+static void
+gst_video_rate_init (GstVideoRate * videorate, GstVideoRateClass * klass)
+{
+  videorate->sinkpad =
+      gst_pad_new_from_static_template (&gst_video_rate_sink_template, "sink");
+  gst_pad_set_event_function (videorate->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_event));
+  gst_pad_set_chain_function (videorate->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_chain));
+  gst_pad_set_getcaps_function (videorate->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_getcaps));
+  gst_pad_set_setcaps_function (videorate->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_setcaps));
+  gst_element_add_pad (GST_ELEMENT (videorate), videorate->sinkpad);
+
+  videorate->srcpad =
+      gst_pad_new_from_static_template (&gst_video_rate_src_template, "src");
+  gst_pad_set_query_function (videorate->srcpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_query));
+  gst_pad_set_getcaps_function (videorate->srcpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_getcaps));
+  gst_pad_set_setcaps_function (videorate->srcpad,
+      GST_DEBUG_FUNCPTR (gst_video_rate_setcaps));
+  gst_element_add_pad (GST_ELEMENT (videorate), videorate->srcpad);
+
+  gst_video_rate_reset (videorate);
+  videorate->silent = DEFAULT_SILENT;
+  videorate->new_pref = DEFAULT_NEW_PREF;
+
+  videorate->from_rate_numerator = 0;
+  videorate->from_rate_denominator = 0;
+  videorate->to_rate_numerator = 0;
+  videorate->to_rate_denominator = 0;
+}
+
+/* flush the oldest buffer */
+static GstFlowReturn
+gst_video_rate_flush_prev (GstVideoRate * videorate, gboolean duplicate)
+{
+  GstFlowReturn res;
+  GstBuffer *outbuf;
+  GstClockTime push_ts;
+
+  if (!videorate->prevbuf)
+    goto eos_before_buffers;
+
+  /* make sure we can write to the metadata */
+  outbuf = gst_buffer_make_metadata_writable
+      (gst_buffer_ref (videorate->prevbuf));
+
+  GST_BUFFER_OFFSET (outbuf) = videorate->out;
+  GST_BUFFER_OFFSET_END (outbuf) = videorate->out + 1;
+
+  if (videorate->discont) {
+    GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
+    videorate->discont = FALSE;
+  } else
+    GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DISCONT);
+
+  if (duplicate)
+    GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
+  else
+    GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_GAP);
+
+  /* this is the timestamp we put on the buffer */
+  push_ts = videorate->next_ts;
+
+  videorate->out++;
+  videorate->out_frame_count++;
+  if (videorate->to_rate_numerator) {
+    /* interpolate next expected timestamp in the segment */
+    videorate->next_ts =
+        videorate->segment.accum + videorate->segment.start +
+        videorate->base_ts + gst_util_uint64_scale (videorate->out_frame_count,
+        videorate->to_rate_denominator * GST_SECOND,
+        videorate->to_rate_numerator);
+    GST_BUFFER_DURATION (outbuf) = videorate->next_ts - push_ts;
+  }
+
+  /* adapt for looping, bring back to time in current segment. */
+  GST_BUFFER_TIMESTAMP (outbuf) = push_ts - videorate->segment.accum;
+
+  gst_buffer_set_caps (outbuf, GST_PAD_CAPS (videorate->srcpad));
+
+  GST_LOG_OBJECT (videorate,
+      "old is best, dup, pushing buffer outgoing ts %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (push_ts));
+
+  res = gst_pad_push (videorate->srcpad, outbuf);
+
+  return res;
+
+  /* WARNINGS */
+eos_before_buffers:
+  {
+    GST_INFO_OBJECT (videorate, "got EOS before any buffer was received");
+    return GST_FLOW_OK;
+  }
+}
+
+static void
+gst_video_rate_swap_prev (GstVideoRate * videorate, GstBuffer * buffer,
+    gint64 time)
+{
+  GST_LOG_OBJECT (videorate, "swap_prev: storing buffer %p in prev", buffer);
+  if (videorate->prevbuf)
+    gst_buffer_unref (videorate->prevbuf);
+  videorate->prevbuf = buffer;
+  videorate->prev_ts = time;
+}
+
+static void
+gst_video_rate_notify_drop (GstVideoRate * videorate)
+{
+#if !GLIB_CHECK_VERSION(2,26,0)
+  g_object_notify ((GObject *) videorate, "drop");
+#else
+  g_object_notify_by_pspec ((GObject *) videorate, pspec_drop);
+#endif
+}
+
+static void
+gst_video_rate_notify_duplicate (GstVideoRate * videorate)
+{
+#if !GLIB_CHECK_VERSION(2,26,0)
+  g_object_notify ((GObject *) videorate, "duplicate");
+#else
+  g_object_notify_by_pspec ((GObject *) videorate, pspec_duplicate);
+#endif
+}
+
+#define MAGIC_LIMIT  25
+static gboolean
+gst_video_rate_event (GstPad * pad, GstEvent * event)
+{
+  GstVideoRate *videorate;
+  gboolean ret;
+
+  videorate = GST_VIDEO_RATE (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:
+    {
+      gint64 start, stop, time;
+      gdouble rate, arate;
+      gboolean update;
+      GstFormat format;
+
+      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+          &start, &stop, &time);
+
+      if (format != GST_FORMAT_TIME)
+        goto format_error;
+
+      GST_DEBUG_OBJECT (videorate, "handle NEWSEGMENT");
+
+      /* close up the previous segment, if appropriate */
+      if (!update && videorate->prevbuf) {
+        gint count = 0;
+        GstFlowReturn res;
+
+        res = GST_FLOW_OK;
+        /* fill up to the end of current segment,
+         * or only send out the stored buffer if there is no specific stop.
+         * regardless, prevent going loopy in strange cases */
+        while (res == GST_FLOW_OK && count <= MAGIC_LIMIT &&
+            ((GST_CLOCK_TIME_IS_VALID (videorate->segment.stop) &&
+                    videorate->next_ts - videorate->segment.accum
+                    < videorate->segment.stop)
+                || count < 1)) {
+          res = gst_video_rate_flush_prev (videorate, count > 0);
+          count++;
+        }
+        if (count > 1) {
+          videorate->dup += count - 1;
+          if (!videorate->silent)
+            gst_video_rate_notify_duplicate (videorate);
+        } else if (count == 0) {
+          videorate->drop++;
+          if (!videorate->silent)
+            gst_video_rate_notify_drop (videorate);
+        }
+        /* clean up for the new one; _chain will resume from the new start */
+        videorate->base_ts = 0;
+        videorate->out_frame_count = 0;
+        gst_video_rate_swap_prev (videorate, NULL, 0);
+        videorate->next_ts = GST_CLOCK_TIME_NONE;
+      }
+
+      /* We just want to update the accumulated stream_time  */
+      gst_segment_set_newsegment_full (&videorate->segment, update, rate, arate,
+          format, start, stop, time);
+
+      GST_DEBUG_OBJECT (videorate, "updated segment: %" GST_SEGMENT_FORMAT,
+          &videorate->segment);
+      break;
+    }
+    case GST_EVENT_EOS:{
+      gint count = 0;
+      GstFlowReturn res = GST_FLOW_OK;
+
+      GST_DEBUG_OBJECT (videorate, "Got EOS");
+
+      /* If the segment has a stop position, fill the segment */
+      if (GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)) {
+        /* fill up to the end of current segment,
+         * or only send out the stored buffer if there is no specific stop.
+         * regardless, prevent going loopy in strange cases */
+        while (res == GST_FLOW_OK && count <= MAGIC_LIMIT &&
+            ((videorate->next_ts - videorate->segment.accum <
+                    videorate->segment.stop)
+                || count < 1)) {
+          res = gst_video_rate_flush_prev (videorate, count > 0);
+          count++;
+        }
+      } else if (videorate->prevbuf) {
+        /* Output at least one frame but if the buffer duration is valid, output
+         * enough frames to use the complete buffer duration */
+        if (GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf)) {
+          GstClockTime end_ts =
+              videorate->next_ts + GST_BUFFER_DURATION (videorate->prevbuf);
+
+          while (res == GST_FLOW_OK && count <= MAGIC_LIMIT &&
+              ((videorate->next_ts - videorate->segment.accum < end_ts)
+                  || count < 1)) {
+            res = gst_video_rate_flush_prev (videorate, count > 0);
+            count++;
+          }
+        } else {
+          res = gst_video_rate_flush_prev (videorate, FALSE);
+          count = 1;
+        }
+      }
+
+      if (count > 1) {
+        videorate->dup += count - 1;
+        if (!videorate->silent)
+          gst_video_rate_notify_duplicate (videorate);
+      } else if (count == 0) {
+        videorate->drop++;
+        if (!videorate->silent)
+          gst_video_rate_notify_drop (videorate);
+      }
+
+      break;
+    }
+    case GST_EVENT_FLUSH_STOP:
+      /* also resets the segment */
+      GST_DEBUG_OBJECT (videorate, "Got FLUSH_STOP");
+      gst_video_rate_reset (videorate);
+      break;
+    default:
+      break;
+  }
+
+  ret = gst_pad_push_event (videorate->srcpad, event);
+
+done:
+  gst_object_unref (videorate);
+
+  return ret;
+
+  /* ERRORS */
+format_error:
+  {
+    GST_WARNING_OBJECT (videorate,
+        "Got segment but doesn't have GST_FORMAT_TIME value");
+    gst_event_unref (event);
+    ret = FALSE;
+    goto done;
+  }
+}
+
+static gboolean
+gst_video_rate_query (GstPad * pad, GstQuery * query)
+{
+  GstVideoRate *videorate;
+  gboolean res = FALSE;
+
+  videorate = GST_VIDEO_RATE (gst_pad_get_parent (pad));
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_LATENCY:
+    {
+      GstClockTime min, max;
+      gboolean live;
+      guint64 latency;
+      GstPad *peer;
+
+      if ((peer = gst_pad_get_peer (videorate->sinkpad))) {
+        if ((res = gst_pad_query (peer, query))) {
+          gst_query_parse_latency (query, &live, &min, &max);
+
+          GST_DEBUG_OBJECT (videorate, "Peer latency: min %"
+              GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+
+          if (videorate->from_rate_numerator != 0) {
+            /* add latency. We don't really know since we hold on to the frames
+             * until we get a next frame, which can be anything. We assume
+             * however that this will take from_rate time. */
+            latency = gst_util_uint64_scale (GST_SECOND,
+                videorate->from_rate_denominator,
+                videorate->from_rate_numerator);
+          } else {
+            /* no input framerate, we don't know */
+            latency = 0;
+          }
+
+          GST_DEBUG_OBJECT (videorate, "Our latency: %"
+              GST_TIME_FORMAT, GST_TIME_ARGS (latency));
+
+          min += latency;
+          if (max != -1)
+            max += latency;
+
+          GST_DEBUG_OBJECT (videorate, "Calculated total latency : min %"
+              GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+
+          gst_query_set_latency (query, live, min, max);
+        }
+        gst_object_unref (peer);
+      }
+      break;
+    }
+    default:
+      res = gst_pad_query_default (pad, query);
+      break;
+  }
+  gst_object_unref (videorate);
+
+  return res;
+}
+
+static GstFlowReturn
+gst_video_rate_chain (GstPad * pad, GstBuffer * buffer)
+{
+  GstVideoRate *videorate;
+  GstFlowReturn res = GST_FLOW_OK;
+  GstClockTime intime, in_ts, in_dur;
+
+  videorate = GST_VIDEO_RATE (GST_PAD_PARENT (pad));
+
+  /* make sure the denominators are not 0 */
+  if (videorate->from_rate_denominator == 0 ||
+      videorate->to_rate_denominator == 0)
+    goto not_negotiated;
+
+  in_ts = GST_BUFFER_TIMESTAMP (buffer);
+  in_dur = GST_BUFFER_DURATION (buffer);
+
+  if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE)) {
+    in_ts = videorate->last_ts;
+    if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE))
+      goto invalid_buffer;
+  }
+
+  /* get the time of the next expected buffer timestamp, we use this when the
+   * next buffer has -1 as a timestamp */
+  videorate->last_ts = in_ts;
+  if (in_dur != GST_CLOCK_TIME_NONE)
+    videorate->last_ts += in_dur;
+
+  GST_DEBUG_OBJECT (videorate, "got buffer with timestamp %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (in_ts));
+
+  /* the input time is the time in the segment + all previously accumulated
+   * segments */
+  intime = in_ts + videorate->segment.accum;
+
+  /* we need to have two buffers to compare */
+  if (videorate->prevbuf == NULL) {
+    gst_video_rate_swap_prev (videorate, buffer, intime);
+    videorate->in++;
+    if (!GST_CLOCK_TIME_IS_VALID (videorate->next_ts)) {
+      /* new buffer, we expect to output a buffer that matches the first
+       * timestamp in the segment */
+      if (videorate->skip_to_first) {
+        videorate->next_ts = intime;
+        videorate->base_ts = in_ts - videorate->segment.start;
+        videorate->out_frame_count = 0;
+      } else {
+        videorate->next_ts =
+            videorate->segment.start + videorate->segment.accum;
+      }
+    }
+  } else {
+    GstClockTime prevtime;
+    gint count = 0;
+    gint64 diff1, diff2;
+
+    prevtime = videorate->prev_ts;
+
+    GST_LOG_OBJECT (videorate,
+        "BEGINNING prev buf %" GST_TIME_FORMAT " new buf %" GST_TIME_FORMAT
+        " outgoing ts %" GST_TIME_FORMAT, GST_TIME_ARGS (prevtime),
+        GST_TIME_ARGS (intime), GST_TIME_ARGS (videorate->next_ts));
+
+    videorate->in++;
+
+    /* drop new buffer if it's before previous one */
+    if (intime < prevtime) {
+      GST_DEBUG_OBJECT (videorate,
+          "The new buffer (%" GST_TIME_FORMAT
+          ") is before the previous buffer (%"
+          GST_TIME_FORMAT "). Dropping new buffer.",
+          GST_TIME_ARGS (intime), GST_TIME_ARGS (prevtime));
+      videorate->drop++;
+      if (!videorate->silent)
+        gst_video_rate_notify_drop (videorate);
+      gst_buffer_unref (buffer);
+      goto done;
+    }
+
+    /* got 2 buffers, see which one is the best */
+    do {
+
+      diff1 = prevtime - videorate->next_ts;
+      diff2 = intime - videorate->next_ts;
+
+      /* take absolute values, beware: abs and ABS don't work for gint64 */
+      if (diff1 < 0)
+        diff1 = -diff1;
+      if (diff2 < 0)
+        diff2 = -diff2;
+
+      GST_LOG_OBJECT (videorate,
+          "diff with prev %" GST_TIME_FORMAT " diff with new %"
+          GST_TIME_FORMAT " outgoing ts %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (diff1), GST_TIME_ARGS (diff2),
+          GST_TIME_ARGS (videorate->next_ts));
+
+      /* output first one when its the best */
+      if (diff1 <= diff2) {
+        count++;
+
+        /* on error the _flush function posted a warning already */
+        if ((res =
+                gst_video_rate_flush_prev (videorate,
+                    count > 1)) != GST_FLOW_OK) {
+          gst_buffer_unref (buffer);
+          goto done;
+        }
+      }
+      /* continue while the first one was the best, if they were equal avoid
+       * going into an infinite loop */
+    }
+    while (diff1 < diff2);
+
+    /* if we outputed the first buffer more then once, we have dups */
+    if (count > 1) {
+      videorate->dup += count - 1;
+      if (!videorate->silent)
+        gst_video_rate_notify_duplicate (videorate);
+    }
+    /* if we didn't output the first buffer, we have a drop */
+    else if (count == 0) {
+      videorate->drop++;
+
+      if (!videorate->silent)
+        gst_video_rate_notify_drop (videorate);
+
+      GST_LOG_OBJECT (videorate,
+          "new is best, old never used, drop, outgoing ts %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (videorate->next_ts));
+    }
+    GST_LOG_OBJECT (videorate,
+        "END, putting new in old, diff1 %" GST_TIME_FORMAT
+        ", diff2 %" GST_TIME_FORMAT ", next_ts %" GST_TIME_FORMAT
+        ", in %" G_GUINT64_FORMAT ", out %" G_GUINT64_FORMAT ", drop %"
+        G_GUINT64_FORMAT ", dup %" G_GUINT64_FORMAT, GST_TIME_ARGS (diff1),
+        GST_TIME_ARGS (diff2), GST_TIME_ARGS (videorate->next_ts),
+        videorate->in, videorate->out, videorate->drop, videorate->dup);
+
+    /* swap in new one when it's the best */
+    gst_video_rate_swap_prev (videorate, buffer, intime);
+  }
+done:
+  return res;
+
+  /* ERRORS */
+not_negotiated:
+  {
+    GST_WARNING_OBJECT (videorate, "no framerate negotiated");
+    gst_buffer_unref (buffer);
+    res = GST_FLOW_NOT_NEGOTIATED;
+    goto done;
+  }
+
+invalid_buffer:
+  {
+    GST_WARNING_OBJECT (videorate,
+        "Got buffer with GST_CLOCK_TIME_NONE timestamp, discarding it");
+    gst_buffer_unref (buffer);
+    goto done;
+  }
+}
+
+static void
+gst_video_rate_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstVideoRate *videorate = GST_VIDEO_RATE (object);
+
+  switch (prop_id) {
+    case ARG_SILENT:
+      videorate->silent = g_value_get_boolean (value);
+      break;
+    case ARG_NEW_PREF:
+      videorate->new_pref = g_value_get_double (value);
+      break;
+    case ARG_SKIP_TO_FIRST:
+      videorate->skip_to_first = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_video_rate_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstVideoRate *videorate = GST_VIDEO_RATE (object);
+
+  switch (prop_id) {
+    case ARG_IN:
+      g_value_set_uint64 (value, videorate->in);
+      break;
+    case ARG_OUT:
+      g_value_set_uint64 (value, videorate->out);
+      break;
+    case ARG_DUP:
+      g_value_set_uint64 (value, videorate->dup);
+      break;
+    case ARG_DROP:
+      g_value_set_uint64 (value, videorate->drop);
+      break;
+    case ARG_SILENT:
+      g_value_set_boolean (value, videorate->silent);
+      break;
+    case ARG_NEW_PREF:
+      g_value_set_double (value, videorate->new_pref);
+      break;
+    case ARG_SKIP_TO_FIRST:
+      g_value_set_boolean (value, videorate->skip_to_first);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstStateChangeReturn
+gst_video_rate_change_state (GstElement * element, GstStateChange transition)
+{
+  GstStateChangeReturn ret;
+  GstVideoRate *videorate;
+
+  videorate = GST_VIDEO_RATE (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      videorate->discont = TRUE;
+      videorate->last_ts = -1;
+      break;
+    default:
+      break;
+  }
+
+  ret = parent_class->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_video_rate_reset (videorate);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (video_rate_debug, "videorate", 0,
+      "VideoRate stream fixer");
+
+  return gst_element_register (plugin, "videorate", GST_RANK_NONE,
+      GST_TYPE_VIDEO_RATE);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "videorate",
+    "Adjusts video frames",
+    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)