diff --git a/configure.ac b/configure.ac
index 0959fb9..9a6bec2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -654,6 +654,7 @@ tests/misc/Makefile
 tests/examples/Makefile
 tests/examples/adapter/Makefile
 tests/examples/controller/Makefile
+tests/examples/stepping/Makefile
 tests/examples/helloworld/Makefile
 tests/examples/launch/Makefile
 tests/examples/manual/Makefile
diff --git a/docs/design/draft-framestep.txt b/docs/design/draft-framestep.txt
index 8f74ae3..8fe789e 100644
--- a/docs/design/draft-framestep.txt
+++ b/docs/design/draft-framestep.txt
@@ -114,15 +114,6 @@ events
      rates here, use a seek with a negative rate first to reverse the playback
      direction.
 
-  "intermediate", G_TYPE_BOOLEAN
-     Signal that this step operation is an intermediate step, part of a series
-     of step operations. It is mostly interesting for stepping in the PAUSED state
-     because the sink will only perform a preroll after a non-intermediate step
-     operation completes. Intermediate steps are usefull to flush out data from
-     other sinks in order to not cause excessive queueing. In the PLAYING state
-     the intermediate flag has no visual effect. In all states, the intermediate
-     flag is passed to the corresponding GST_MESSAGE_STEP_DONE.
-
   "flush", G_TYPE_BOOLEAN
      when flushing is TRUE, the step is performed immediatly: 
 
@@ -143,7 +134,16 @@ events
       - In the PLAYING state the step operation will be performed after the
 	current step operation completes. If there was no previous step
 	operation, the step operation will be performed from the position of the
-	last PAUSED state.
+	last PAUSED state. 
+
+  "intermediate", G_TYPE_BOOLEAN
+     Signal that this step operation is an intermediate step, part of a series
+     of step operations. It is mostly interesting for stepping in the PAUSED state
+     because the sink will only perform a preroll after a non-intermediate step
+     operation completes. Intermediate steps are usefull to flush out data from
+     other sinks in order to not cause excessive queueing. In the PLAYING state
+     the intermediate flag has no visual effect. In all states, the intermediate
+     flag is passed to the corresponding GST_MESSAGE_STEP_DONE.
 
 
  The application will create a STEP event to start or stop the stepping
@@ -180,13 +180,19 @@ messages
   "rate", G_TYPE_DOUBLE
      The rate and direction at which the frames were stepped.
 
+  "flush", G_TYPE_BOOLEAN
+     If the stepped frames were flushed.
+
   "intermediate", G_TYPE_BOOLEAN
      If this is an intermediate step operation that completed.
 
   "duration", G_TYPE_UINT64
      The total duration of the stepped units in GST_FORMAT_TIME.
 
- The message is emited by the element that performs the step operation.
+ The message is emited by the element that performs the step operation. The
+ purpose is to return the duration in GST_FORMAT_TIME of the stepped media. This
+ especially interesting to align other stream in case of stepping frames on the
+ video sink element.
 
 
 Direction switch
diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt
index 25ea69a..ee42b74 100644
--- a/docs/gst/gstreamer-sections.txt
+++ b/docs/gst/gstreamer-sections.txt
@@ -743,6 +743,9 @@ gst_event_new_navigation
 
 gst_event_new_latency
 gst_event_parse_latency
+
+gst_event_new_step
+gst_event_parse_step
 <SUBSECTION Standard>
 GstEventClass
 GST_EVENT
@@ -1121,6 +1124,8 @@ gst_message_parse_buffering_stats
 gst_message_new_state_changed
 gst_message_parse_state_changed
 gst_message_new_state_dirty
+gst_message_new_step_done
+gst_message_parse_step_done
 gst_message_new_clock_provide
 gst_message_parse_clock_provide
 gst_message_new_clock_lost
diff --git a/gst/gstevent.c b/gst/gstevent.c
index bf5a544..d7bc894 100644
--- a/gst/gstevent.c
+++ b/gst/gstevent.c
@@ -1093,3 +1093,91 @@ gst_event_parse_latency (GstEvent * event, GstClockTime * latency)
         g_value_get_uint64 (gst_structure_id_get_value (structure,
             GST_QUARK (LATENCY)));
 }
+
+/**
+ * gst_event_new_step:
+ * @format: the format of @amount
+ * @amount: the amount of data to step
+ * @rate: the step rate
+ * @flush: flushing steps
+ * @intermediate: intermediate steps
+ *
+ * Create a new step event. The purpose of the step event is to instruct a sink
+ * to skip @amount (expressed in @format) of media. It can be used to implement
+ * stepping through the video frame by frame or for doing fast trick modes.
+ *
+ * A rate of <= 0.0 is not allowed, pause the pipeline or reverse the playback
+ * direction of the pipeline to get the same effect.
+ *
+ * The @flush flag will clear any pending data in the pipeline before starting
+ * the step operation.
+ *
+ * The @intermediate flag instructs the pipeline that this step operation is
+ * part of a larger step operation.
+ *
+ * Returns: a new #GstEvent
+ *
+ * Since: 0.10.24
+ */
+GstEvent *
+gst_event_new_step (GstFormat format, guint64 amount, gdouble rate,
+    gboolean flush, gboolean intermediate)
+{
+  GstEvent *event;
+  GstStructure *structure;
+
+  g_return_val_if_fail (rate > 0.0, NULL);
+
+  GST_CAT_INFO (GST_CAT_EVENT, "creating step event");
+
+  structure = gst_structure_empty_new ("GstEventStep");
+  gst_structure_id_set (structure,
+      GST_QUARK (FORMAT), GST_TYPE_FORMAT, format,
+      GST_QUARK (AMOUNT), G_TYPE_UINT64, amount,
+      GST_QUARK (RATE), G_TYPE_DOUBLE, rate,
+      GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush,
+      GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate, NULL);
+  event = gst_event_new_custom (GST_EVENT_STEP, structure);
+
+  return event;
+}
+
+/**
+ * gst_event_parse_step:
+ * @event: The event to query
+ * @format: A pointer to store the format in.
+ * @amount: A pointer to store the amount in.
+ * @rate: A pointer to store the rate in.
+ * @flush: A pointer to store the flush boolean in.
+ * @intermediate: A pointer to store the intermediate boolean in.
+ *
+ * Parse the step event.
+ *
+ * Since: 0.10.24
+ */
+void
+gst_event_parse_step (GstEvent * event, GstFormat * format, guint64 * amount,
+    gdouble * rate, gboolean * flush, gboolean * intermediate)
+{
+  const GstStructure *structure;
+
+  g_return_if_fail (GST_IS_EVENT (event));
+  g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_STEP);
+
+  structure = gst_event_get_structure (event);
+  if (format)
+    *format = g_value_get_enum (gst_structure_id_get_value (structure,
+            GST_QUARK (FORMAT)));
+  if (amount)
+    *amount = g_value_get_uint64 (gst_structure_id_get_value (structure,
+            GST_QUARK (AMOUNT)));
+  if (rate)
+    *rate = g_value_get_double (gst_structure_id_get_value (structure,
+            GST_QUARK (RATE)));
+  if (flush)
+    *flush = g_value_get_boolean (gst_structure_id_get_value (structure,
+            GST_QUARK (FLUSH)));
+  if (intermediate)
+    *intermediate = g_value_get_boolean (gst_structure_id_get_value (structure,
+            GST_QUARK (INTERMEDIATE)));
+}
diff --git a/gst/gstevent.h b/gst/gstevent.h
index 1217366..89e7031 100644
--- a/gst/gstevent.h
+++ b/gst/gstevent.h
@@ -92,6 +92,7 @@ typedef enum {
  *                        user requests, such as mouse or keyboard movements,
  *                        to upstream elements.
  * @GST_EVENT_LATENCY: Notification of new latency adjustment. Since: 0.10.12
+ * @GST_EVENT_STEP: A request for stepping through the media. Since: 0.10.24
  * @GST_EVENT_CUSTOM_UPSTREAM: Upstream custom event
  * @GST_EVENT_CUSTOM_DOWNSTREAM: Downstream custom event that travels in the
  *                        data flow.
@@ -124,6 +125,7 @@ typedef enum {
   GST_EVENT_SEEK		  = GST_EVENT_MAKE_TYPE (16, FLAG(UPSTREAM)),
   GST_EVENT_NAVIGATION		  = GST_EVENT_MAKE_TYPE (17, FLAG(UPSTREAM)),
   GST_EVENT_LATENCY		  = GST_EVENT_MAKE_TYPE (18, FLAG(UPSTREAM)),
+  GST_EVENT_STEP		  = GST_EVENT_MAKE_TYPE (19, FLAG(UPSTREAM)),
 
   /* custom events start here */
   GST_EVENT_CUSTOM_UPSTREAM	  = GST_EVENT_MAKE_TYPE (32, FLAG(UPSTREAM)),
@@ -460,6 +462,12 @@ GstEvent*	gst_event_new_navigation	(GstStructure *structure);
 GstEvent*	gst_event_new_latency		(GstClockTime latency);
 void		gst_event_parse_latency		(GstEvent *event, GstClockTime *latency);
 
+/* step event */
+GstEvent*	gst_event_new_step		(GstFormat format, guint64 amount, gdouble rate,
+                                                 gboolean flush, gboolean intermediate);
+void		gst_event_parse_step		(GstEvent *event, GstFormat *format, guint64 *amount,
+                                                 gdouble *rate, gboolean *flush, gboolean *intermediate);
+
 G_END_DECLS
 
 #endif /* __GST_EVENT_H__ */
diff --git a/gst/gstmessage.c b/gst/gstmessage.c
index 848af21..68d9028 100644
--- a/gst/gstmessage.c
+++ b/gst/gstmessage.c
@@ -1648,3 +1648,93 @@ gst_message_get_stream_status_object (GstMessage * message)
 
   return result;
 }
+
+/**
+ * gst_message_new_step_done:
+ * @src: The object originating the message.
+ * @format: the format of @amount
+ * @amount: the amount of stepped data
+ * @rate: the rate of the stepped amount
+ * @flush: is this an flushing step
+ * @intermediate: is this an intermediate step
+ * @duration: the duration of the data
+ *
+ * This message is posted by elements when they complete a part, when @intermediate set
+ * to TRUE, or a complete step operation.
+ *
+ * @duration will contain the amount of time (in GST_FORMAT_TIME) of the stepped
+ * @amount of media in format @format.
+ *
+ * Returns: The new step_done message. 
+ *
+ * MT safe.
+ *
+ * Since: 0.10.24
+ */
+GstMessage *
+gst_message_new_step_done (GstObject * src, GstFormat format, guint64 amount,
+    gdouble rate, gboolean flush, gboolean intermediate, guint64 duration)
+{
+  GstMessage *message;
+  GstStructure *structure;
+
+  structure = gst_structure_empty_new ("GstMessageStepDone");
+  gst_structure_id_set (structure,
+      GST_QUARK (FORMAT), GST_TYPE_FORMAT, format,
+      GST_QUARK (AMOUNT), G_TYPE_UINT64, amount,
+      GST_QUARK (RATE), G_TYPE_DOUBLE, rate,
+      GST_QUARK (FLUSH), G_TYPE_BOOLEAN, flush,
+      GST_QUARK (INTERMEDIATE), G_TYPE_BOOLEAN, intermediate,
+      GST_QUARK (DURATION), G_TYPE_UINT64, duration, NULL);
+  message = gst_message_new_custom (GST_MESSAGE_STEP_DONE, src, structure);
+
+  return message;
+}
+
+/**
+ * gst_message_parse_step_done:
+ * @message: A valid #GstMessage of type GST_MESSAGE_STEP_DONE.
+ * @format: result location for the format
+ * @amount: result location for the amount
+ * @rate: result location for the rate
+ * @flush: result location for the flush flag
+ * @intermediate: result location for the intermediate flag
+ * @duration: result location for the duration
+ *
+ * Extract the requested state from the request_state message.
+ *
+ * MT safe.
+ *
+ * Since: 0.10.24
+ */
+void
+gst_message_parse_step_done (GstMessage * message, GstFormat * format,
+    guint64 * amount, gdouble * rate, gboolean * flush, gboolean * intermediate,
+    guint64 * duration)
+{
+  g_return_if_fail (GST_IS_MESSAGE (message));
+  g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STEP_DONE);
+
+  if (format)
+    *format = g_value_get_enum (gst_structure_id_get_value (message->structure,
+            GST_QUARK (FORMAT)));
+  if (amount)
+    *amount =
+        g_value_get_uint64 (gst_structure_id_get_value (message->structure,
+            GST_QUARK (AMOUNT)));
+  if (rate)
+    *rate = g_value_get_double (gst_structure_id_get_value (message->structure,
+            GST_QUARK (RATE)));
+  if (flush)
+    *flush =
+        g_value_get_boolean (gst_structure_id_get_value (message->structure,
+            GST_QUARK (FLUSH)));
+  if (intermediate)
+    *intermediate =
+        g_value_get_boolean (gst_structure_id_get_value (message->structure,
+            GST_QUARK (INTERMEDIATE)));
+  if (duration)
+    *duration =
+        g_value_get_uint64 (gst_structure_id_get_value (message->structure,
+            GST_QUARK (DURATION)));
+}
diff --git a/gst/gstmessage.h b/gst/gstmessage.h
index 5790315..48339b4 100644
--- a/gst/gstmessage.h
+++ b/gst/gstmessage.h
@@ -383,6 +383,13 @@ void		gst_message_parse_state_changed	(GstMessage *message, GstState *oldstate,
 /* STATE_DIRTY */
 GstMessage *	gst_message_new_state_dirty	(GstObject * src);
 
+/* STEP_DONE */
+GstMessage *    gst_message_new_step_done       (GstObject * src, GstFormat format, guint64 amount,
+                                                 gdouble rate, gboolean flush, gboolean intermediate, 
+						 guint64 duration);
+void            gst_message_parse_step_done     (GstMessage * message, GstFormat *format, guint64 *amount,
+                                                 gdouble *rate, gboolean *flush, gboolean *intermediate,
+						 guint64 *duration);
 /* CLOCK_PROVIDE */
 GstMessage *	gst_message_new_clock_provide	(GstObject * src, GstClock *clock, gboolean ready);
 void		gst_message_parse_clock_provide (GstMessage *message, GstClock **clock,
diff --git a/gst/gstquark.c b/gst/gstquark.c
index d1d69fb..3bbe935 100644
--- a/gst/gstquark.c
+++ b/gst/gstquark.c
@@ -37,7 +37,7 @@ static const gchar *_quark_strings[] = {
   "max-latency", "busy", "type", "owner", "update", "applied-rate",
   "start", "stop", "minsize", "maxsize", "async", "proportion",
   "diff", "timestamp", "flags", "cur-type", "cur", "stop-type",
-  "latency", "uri", "object"
+  "latency", "uri", "object", "amount", "flush", "intermediate"
 };
 
 GQuark _priv_gst_quark_table[GST_QUARK_MAX];
diff --git a/gst/gstquark.h b/gst/gstquark.h
index 0f29119..044d3ee 100644
--- a/gst/gstquark.h
+++ b/gst/gstquark.h
@@ -79,8 +79,11 @@ typedef enum _GstQuarkId
   GST_QUARK_LATENCY = 50,
   GST_QUARK_URI = 51,
   GST_QUARK_OBJECT = 52,
+  GST_QUARK_AMOUNT = 53,
+  GST_QUARK_FLUSH = 54,
+  GST_QUARK_INTERMEDIATE = 55,
 
-  GST_QUARK_MAX = 53
+  GST_QUARK_MAX = 56
 } GstQuarkId;
 
 extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];
diff --git a/libs/gst/base/gstbasesink.c b/libs/gst/base/gstbasesink.c
index ad9053d..4b91497 100644
--- a/libs/gst/base/gstbasesink.c
+++ b/libs/gst/base/gstbasesink.c
@@ -152,6 +152,23 @@ GST_DEBUG_CATEGORY_STATIC (gst_base_sink_debug);
 #define GST_BASE_SINK_GET_PRIVATE(obj)  \
    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_SINK, GstBaseSinkPrivate))
 
+#define GST_FLOW_STEP GST_FLOW_CUSTOM_ERROR
+
+typedef struct
+{
+  gboolean valid;               /* if this info is valid */
+  guint32 seqnum;               /* the seqnum of the STEP event */
+  GstFormat format;             /* the format of the amount */
+  guint64 amount;               /* the total amount of data to skip */
+  guint64 position;             /* the position in the stepped data */
+  guint64 duration;             /* the duration in time of the skipped data */
+  guint64 start;                /* running_time of the start */
+  gdouble rate;                 /* rate of skipping */
+  gboolean flush;               /* if this was a flushing step */
+  gboolean intermediate;        /* if this is an intermediate step */
+  gboolean need_preroll;        /* if we need preroll after this step */
+} GstStepInfo;
+
 /* FIXME, some stuff in ABI.data and other in Private...
  * Make up your mind please.
  */
@@ -224,6 +241,11 @@ struct _GstBaseSinkPrivate
   guint32 seqnum;
 
   gboolean call_preroll;
+  gboolean step_unlock;
+
+  /* we have a pending and a current step operation */
+  GstStepInfo current_step;
+  GstStepInfo pending_step;
 };
 
 #define DO_RUNNING_AVG(avg,val,size) (((val) + ((size)-1) * (avg)) / (size))
@@ -1257,6 +1279,7 @@ gst_base_sink_preroll_queue_flush (GstBaseSink * basesink, GstPad * pad)
   basesink->eos = FALSE;
   basesink->priv->received_eos = FALSE;
   basesink->have_preroll = FALSE;
+  basesink->priv->step_unlock = FALSE;
   basesink->eos_queued = FALSE;
   basesink->preroll_queued = 0;
   basesink->buffers_queued = 0;
@@ -1454,6 +1477,152 @@ async_failed:
   }
 }
 
+static void
+start_stepping (GstBaseSink * sink, GstSegment * segment,
+    GstStepInfo * pending, GstStepInfo * current)
+{
+  GST_DEBUG_OBJECT (sink, "update pending step");
+  memcpy (current, pending, sizeof (GstStepInfo));
+  pending->valid = FALSE;
+
+  /* get the running time of where we paused and remember it */
+  current->start = gst_element_get_start_time (GST_ELEMENT_CAST (sink));
+
+  /* set the new rate */
+  segment->rate = segment->rate * current->rate;
+
+  GST_DEBUG_OBJECT (sink, "step started at running_time %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (current->start));
+
+  if (current->amount == -1) {
+    GST_DEBUG_OBJECT (sink, "step amount == -1, stop stepping");
+    current->valid = FALSE;
+  } else {
+    GST_DEBUG_OBJECT (sink, "step amount: %" G_GUINT64_FORMAT ", format: %s, "
+        "rate: %f", current->amount, gst_format_get_name (current->format),
+        current->rate);
+  }
+}
+
+static void
+stop_stepping (GstBaseSink * sink, GstSegment * segment,
+    GstStepInfo * current, guint64 cstart, guint64 cstop, gint64 * rstart,
+    gint64 * rstop)
+{
+  GstMessage *message;
+
+  GST_DEBUG_OBJECT (sink, "step complete");
+
+  /* update the segment, discarding what was consumed, running time goes
+   * backwards with the duration of the data we skipped. FIXME, this only works
+   * in PAUSED. */
+  if (segment->rate > 0.0) {
+    GST_DEBUG_OBJECT (sink,
+        "step stop at running_time %" GST_TIME_FORMAT ", timestamp %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (*rstart), GST_TIME_ARGS (cstart));
+    current->duration = *rstart - current->start;
+
+    segment->time =
+        gst_segment_to_stream_time (segment, segment->format, cstart);
+    segment->start = cstart;
+
+    *rstart = current->start;
+    *rstop -= current->duration;
+  } else {
+    GST_DEBUG_OBJECT (sink,
+        "step stop at running_time %" GST_TIME_FORMAT ", timestamp %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (*rstop), GST_TIME_ARGS (cstop));
+    current->duration = *rstop - current->start;
+
+    segment->stop = cstop;
+    *rstop = current->start;
+    *rstart -= current->duration;
+  }
+  segment->accum = current->start;
+
+  GST_DEBUG_OBJECT (sink, "step elapsed running_time %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (current->duration));
+
+  /* the clip segment is used for position report in paused... */
+  memcpy (sink->abidata.ABI.clip_segment, segment, sizeof (GstSegment));
+
+  message =
+      gst_message_new_step_done (GST_OBJECT_CAST (sink), current->format,
+      current->amount, current->rate, current->flush, current->intermediate,
+      current->duration);
+  gst_message_set_seqnum (message, current->seqnum);
+  gst_element_post_message (GST_ELEMENT_CAST (sink), message);
+
+  if (!current->intermediate)
+    sink->need_preroll = current->need_preroll;
+
+  /* and the current step info finished and becomes invalid */
+  current->valid = FALSE;
+}
+
+static gboolean
+handle_stepping (GstBaseSink * sink, GstSegment * segment,
+    GstStepInfo * current, gint64 * cstart, gint64 * cstop, gint64 * rstart,
+    gint64 * rstop)
+{
+  GstBaseSinkPrivate *priv;
+  gboolean step_end = FALSE;
+
+  priv = sink->priv;
+
+  /* see if we need to skip this buffer because of stepping */
+  switch (current->format) {
+    case GST_FORMAT_TIME:
+    {
+      guint64 end;
+      gint64 last;
+
+      end = current->start + current->amount;
+
+      if (segment->rate > 0.0) {
+        current->position = *rstart - current->start;
+        last = *rstop;
+      } else {
+        current->position = *rstop - current->start;
+        last = *rstart;
+      }
+
+      GST_DEBUG_OBJECT (sink,
+          "got time step %" GST_TIME_FORMAT "/%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (current->position), GST_TIME_ARGS (current->amount));
+
+      if (current->position >= current->amount || last >= end) {
+        step_end = TRUE;
+        if (segment->rate > 0.0) {
+          *cstart += end - *rstart;
+          *rstart = end;
+        } else {
+          *cstop += end - *rstop;
+          *rstop = end;
+        }
+      }
+      break;
+    }
+    case GST_FORMAT_BUFFERS:
+      GST_DEBUG_OBJECT (sink,
+          "got default step %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+          current->position, current->amount);
+
+      if (current->position < current->amount) {
+        current->position++;
+      } else {
+        step_end = TRUE;
+      }
+      break;
+    case GST_FORMAT_DEFAULT:
+    default:
+      GST_DEBUG_OBJECT (sink,
+          "got unknown step %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+          current->position, current->amount);
+      break;
+  }
+  return step_end;
+}
 
 /* with STREAM_LOCK, PREROLL_LOCK
  *
@@ -1467,7 +1636,7 @@ static gboolean
 gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
     GstClockTime * rsstart, GstClockTime * rsstop,
     GstClockTime * rrstart, GstClockTime * rrstop, gboolean * do_sync,
-    GstSegment * segment)
+    gboolean * stepped, GstSegment * segment, GstStepInfo * step)
 {
   GstBaseSinkClass *bclass;
   GstBuffer *buffer;
@@ -1477,12 +1646,15 @@ gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
   GstClockTime sstart, sstop;   /* clipped timestamps converted to stream time */
   GstFormat format;
   GstBaseSinkPrivate *priv;
+  gboolean step_end;
 
   priv = basesink->priv;
 
   /* start with nothing */
   start = stop = -1;
 
+  step_end = FALSE;
+
   if (G_UNLIKELY (GST_IS_EVENT (obj))) {
     GstEvent *event = GST_EVENT_CAST (obj);
 
@@ -1510,7 +1682,9 @@ gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
         *do_sync = rstart != -1;
         GST_DEBUG_OBJECT (basesink, "sync times for EOS %" GST_TIME_FORMAT,
             GST_TIME_ARGS (rstart));
-        goto done;
+        /* if we are stepping, we end now */
+        step_end = step->valid;
+        goto eos_done;
       }
       default:
         /* other events do not need syncing */
@@ -1528,11 +1702,14 @@ gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
 
   bclass = GST_BASE_SINK_GET_CLASS (basesink);
 
-  /* just get the times to see if we need syncing */
+  /* just get the times to see if we need syncing, if the start returns -1 we
+   * don't sync. */
   if (bclass->get_times)
     bclass->get_times (basesink, buffer, &start, &stop);
 
   if (start == -1) {
+    /* we don't need to sync but we still want to get the timestamps for
+     * tracking the position */
     gst_base_sink_get_times (basesink, buffer, &start, &stop);
     *do_sync = FALSE;
   } else {
@@ -1546,16 +1723,17 @@ gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
   /* collect segment and format for code clarity */
   format = segment->format;
 
-  /* no timestamp clipping if we did not * get a TIME segment format */
+  /* no timestamp clipping if we did not get a TIME segment format */
   if (G_UNLIKELY (format != GST_FORMAT_TIME)) {
     cstart = start;
     cstop = stop;
     /* do running and stream time in TIME format */
     format = GST_FORMAT_TIME;
+    GST_LOG_OBJECT (basesink, "not time format, don't clip");
     goto do_times;
   }
 
-  /* clip */
+  /* clip, only when we know about time */
   if (G_UNLIKELY (!gst_segment_clip (segment, GST_FORMAT_TIME,
               (gint64) start, (gint64) stop, &cstart, &cstop)))
     goto out_of_segment;
@@ -1580,7 +1758,17 @@ do_times:
   rstart = gst_segment_to_running_time (segment, format, cstart);
   rstop = gst_segment_to_running_time (segment, format, cstop);
 
-done:
+  if (G_UNLIKELY (step->valid)) {
+    if (!(step_end = handle_stepping (basesink, segment, step, &cstart, &cstop,
+                &rstart, &rstop)))
+      *stepped = TRUE;
+  }
+
+eos_done:
+  /* done label only called when doing EOS, we also stop stepping then */
+  if (step_end)
+    stop_stepping (basesink, segment, step, cstart, cstop, &rstart, &rstop);
+
   /* save times */
   *rsstart = sstart;
   *rsstop = sstop;
@@ -1745,6 +1933,8 @@ gst_base_sink_wait_preroll (GstBaseSink * sink)
   sink->have_preroll = FALSE;
   if (G_UNLIKELY (sink->flushing))
     goto stopping;
+  if (G_UNLIKELY (sink->priv->step_unlock))
+    goto step_unlocked;
   GST_DEBUG_OBJECT (sink, "continue after preroll");
 
   return GST_FLOW_OK;
@@ -1752,9 +1942,15 @@ gst_base_sink_wait_preroll (GstBaseSink * sink)
   /* ERRORS */
 stopping:
   {
-    GST_DEBUG_OBJECT (sink, "preroll interrupted");
+    GST_DEBUG_OBJECT (sink, "preroll interrupted because of flush");
     return GST_FLOW_WRONG_STATE;
   }
+step_unlocked:
+  {
+    sink->priv->step_unlock = FALSE;
+    GST_DEBUG_OBJECT (sink, "preroll interrupted because of step");
+    return GST_FLOW_STEP;
+  }
 }
 
 /**
@@ -1791,8 +1987,12 @@ gst_base_sink_do_preroll (GstBaseSink * sink, GstMiniObject * obj)
     if (G_LIKELY (sink->need_preroll)) {
       /* block until the state changes, or we get a flush, or something */
       ret = gst_base_sink_wait_preroll (sink);
-      if (ret != GST_FLOW_OK)
-        goto preroll_failed;
+      if (ret != GST_FLOW_OK) {
+        if (ret == GST_FLOW_STEP)
+          ret = GST_FLOW_OK;
+        else
+          goto preroll_failed;
+      }
     }
   }
   return GST_FLOW_OK;
@@ -1840,8 +2040,12 @@ gst_base_sink_wait_eos (GstBaseSink * sink, GstClockTime time,
     /* first wait for the playing state before we can continue */
     if (G_UNLIKELY (sink->need_preroll)) {
       ret = gst_base_sink_wait_preroll (sink);
-      if (ret != GST_FLOW_OK)
-        goto flushing;
+      if (ret != GST_FLOW_OK) {
+        if (ret == GST_FLOW_STEP)
+          ret = GST_FLOW_OK;
+        else
+          goto flushing;
+      }
     }
 
     /* preroll done, we can sync since we are in PLAYING now. */
@@ -1915,17 +2119,29 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstPad * pad,
   gboolean do_sync;
   GstBaseSinkPrivate *priv;
   GstFlowReturn ret;
+  GstStepInfo *current, *pending;
+  gboolean stepped;
 
   priv = basesink->priv;
 
+do_step:
   sstart = sstop = rstart = rstop = -1;
   do_sync = TRUE;
+  stepped = FALSE;
 
   priv->current_rstart = -1;
 
+  /* get stepping info */
+  current = &priv->current_step;
+  pending = &priv->pending_step;
+
   /* get timing information for this object against the render segment */
   syncable = gst_base_sink_get_sync_times (basesink, obj,
-      &sstart, &sstop, &rstart, &rstop, &do_sync, &basesink->segment);
+      &sstart, &sstop, &rstart, &rstop, &do_sync, &stepped, &basesink->segment,
+      current);
+
+  if (G_UNLIKELY (stepped))
+    goto step_skipped;
 
   /* a syncable object needs to participate in preroll and
    * clocking. All buffers and EOS are syncable. */
@@ -1935,18 +2151,25 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstPad * pad,
   /* store timing info for current object */
   priv->current_rstart = rstart;
   priv->current_rstop = (rstop != -1 ? rstop : rstart);
+
   /* save sync time for eos when the previous object needed sync */
   priv->eos_rtime = (do_sync ? priv->current_rstop : -1);
 
 again:
   /* first do preroll, this makes sure we commit our state
    * to PAUSED and can continue to PLAYING. We cannot perform
-   * any clock sync in PAUSED because there is no clock. 
-   */
+   * any clock sync in PAUSED because there is no clock. */
   ret = gst_base_sink_do_preroll (basesink, obj);
   if (G_UNLIKELY (ret != GST_FLOW_OK))
     goto preroll_failed;
 
+  /* update the segment with a pending step if the current one is invalid and we
+   * have a new pending one. We only accept new step updates after a preroll */
+  if (G_UNLIKELY (pending->valid && !current->valid)) {
+    start_stepping (basesink, &basesink->segment, pending, current);
+    goto do_step;
+  }
+
   /* After rendering we store the position of the last buffer so that we can use
    * it to report the position. We need to take the lock here. */
   GST_OBJECT_LOCK (basesink);
@@ -2006,6 +2229,12 @@ done:
   return GST_FLOW_OK;
 
   /* ERRORS */
+step_skipped:
+  {
+    GST_DEBUG_OBJECT (basesink, "skipped stepped object %p", obj);
+    *late = TRUE;
+    return GST_FLOW_OK;
+  }
 not_syncable:
   {
     GST_DEBUG_OBJECT (basesink, "non syncable object %p", obj);
@@ -2308,13 +2537,18 @@ static GstFlowReturn
 gst_base_sink_render_object (GstBaseSink * basesink, GstPad * pad,
     GstMiniObject * obj)
 {
-  GstFlowReturn ret = GST_FLOW_OK;
+  GstFlowReturn ret;
   GstBaseSinkClass *bclass;
-  gboolean late = FALSE;
+  gboolean late;
+
   GstBaseSinkPrivate *priv;
 
   priv = basesink->priv;
 
+again:
+  late = FALSE;
+  ret = GST_FLOW_OK;
+
   /* synchronize this object, non syncable objects return OK
    * immediatly. */
   ret = gst_base_sink_do_sync (basesink, pad, obj, &late);
@@ -2349,10 +2583,13 @@ gst_base_sink_render_object (GstBaseSink * basesink, GstPad * pad,
 
       ret = bclass->render (basesink, buf);
 
-      priv->rendered++;
-
       if (do_qos)
         gst_base_sink_do_render_stats (basesink, FALSE);
+
+      if (ret == GST_FLOW_STEP)
+        goto again;
+
+      priv->rendered++;
     }
   } else {
     GstEvent *event = GST_EVENT_CAST (obj);
@@ -3173,6 +3410,79 @@ gst_base_sink_perform_seek (GstBaseSink * sink, GstPad * pad, GstEvent * event)
   return res;
 }
 
+static gboolean
+gst_base_sink_perform_step (GstBaseSink * sink, GstPad * pad, GstEvent * event)
+{
+  GstBaseSinkPrivate *priv;
+  GstBaseSinkClass *bclass;
+  gboolean flush, intermediate;
+  gdouble rate;
+  GstFormat format;
+  guint64 amount;
+  guint seqnum;
+  GstStepInfo *pending;
+
+  bclass = GST_BASE_SINK_GET_CLASS (sink);
+  priv = sink->priv;
+
+  GST_DEBUG_OBJECT (sink, "performing step with event %p", event);
+
+  gst_event_parse_step (event, &format, &amount, &rate, &flush, &intermediate);
+  seqnum = gst_event_get_seqnum (event);
+
+  pending = &priv->pending_step;
+
+  if (flush) {
+    /* we need to call ::unlock before locking PREROLL_LOCK
+     * since we lock it before going into ::render */
+    if (bclass->unlock)
+      bclass->unlock (sink);
+
+    GST_PAD_PREROLL_LOCK (sink->sinkpad);
+    /* now that we have the PREROLL lock, clear our unlock request */
+    if (bclass->unlock_stop)
+      bclass->unlock_stop (sink);
+
+    /* update the stepinfo and make it valid */
+    pending->seqnum = seqnum;
+    pending->format = format;
+    pending->amount = amount;
+    pending->position = 0;
+    pending->rate = rate;
+    pending->flush = flush;
+    pending->intermediate = intermediate;
+    pending->valid = TRUE;
+
+    if (sink->priv->async_enabled) {
+      /* and we need to commit our state again on the next
+       * prerolled buffer */
+      sink->playing_async = TRUE;
+      priv->pending_step.need_preroll = TRUE;
+      sink->need_preroll = FALSE;
+      gst_element_lost_state_full (GST_ELEMENT_CAST (sink), FALSE);
+    } else {
+      sink->priv->have_latency = TRUE;
+      sink->need_preroll = FALSE;
+    }
+    priv->call_preroll = TRUE;
+    gst_base_sink_set_last_buffer (sink, NULL);
+    gst_base_sink_reset_qos (sink);
+
+    if (sink->clock_id) {
+      gst_clock_id_unschedule (sink->clock_id);
+    }
+
+    if (sink->have_preroll) {
+      GST_DEBUG_OBJECT (sink, "signal waiter");
+      priv->step_unlock = TRUE;
+      GST_PAD_PREROLL_SIGNAL (sink->sinkpad);
+    }
+    GST_PAD_PREROLL_UNLOCK (sink->sinkpad);
+  }
+
+  return TRUE;
+}
+
 /* with STREAM_LOCK
  */
 static void
@@ -3595,6 +3905,10 @@ gst_base_sink_send_event (GstElement * element, GstEvent * event)
       if (mode == GST_ACTIVATE_PULL)
         result = gst_base_sink_perform_seek (basesink, pad, event);
       break;
+    case GST_EVENT_STEP:
+      result = gst_base_sink_perform_step (basesink, pad, event);
+      forward = FALSE;
+      break;
     default:
       break;
   }
@@ -4021,6 +4335,7 @@ gst_base_sink_change_state (GstElement * element, GstStateChange transition)
           GST_FORMAT_UNDEFINED);
       basesink->offset = 0;
       basesink->have_preroll = FALSE;
+      priv->step_unlock = FALSE;
       basesink->need_preroll = TRUE;
       basesink->playing_async = TRUE;
       priv->current_sstart = -1;
@@ -4032,6 +4347,8 @@ gst_base_sink_change_state (GstElement * element, GstStateChange transition)
       gst_base_sink_reset_qos (basesink);
       priv->commited = FALSE;
       priv->call_preroll = TRUE;
+      priv->current_step.valid = FALSE;
+      priv->pending_step.valid = FALSE;
       if (priv->async_enabled) {
         GST_DEBUG_OBJECT (basesink, "doing async state change");
         /* when async enabled, post async-start message and return ASYNC from
diff --git a/tests/examples/Makefile.am b/tests/examples/Makefile.am
index 87c13fe..88fbe7d 100644
--- a/tests/examples/Makefile.am
+++ b/tests/examples/Makefile.am
@@ -23,7 +23,8 @@ always_dirs = \
 	manual     \
 	metadata   \
 	streams    \
-	queue
+	queue      \
+	stepping
 
 #appreader
 #cutter
diff --git a/tests/examples/stepping/.gitignore b/tests/examples/stepping/.gitignore
new file mode 100644
index 0000000..0b661c7
--- /dev/null
+++ b/tests/examples/stepping/.gitignore
@@ -0,0 +1,5 @@
+framestep1
+*.bb
+*.bbg
+*.da
+framestep1-framestep1.gcno
diff --git a/tests/examples/stepping/Makefile.am b/tests/examples/stepping/Makefile.am
new file mode 100644
index 0000000..2a6fbc1
--- /dev/null
+++ b/tests/examples/stepping/Makefile.am
@@ -0,0 +1,4 @@
+noinst_PROGRAMS = framestep1
+
+framestep1_LDADD = $(GST_OBJ_LIBS)
+framestep1_CFLAGS = $(GST_OBJ_CFLAGS)
diff --git a/tests/examples/stepping/framestep1.c b/tests/examples/stepping/framestep1.c
new file mode 100644
index 0000000..ddf82a2
--- /dev/null
+++ b/tests/examples/stepping/framestep1.c
@@ -0,0 +1,157 @@
+#include <stdlib.h>
+#include <gst/gst.h>
+
+static void
+event_loop (GstElement * pipe)
+{
+  GstBus *bus;
+  GstMessage *message = NULL;
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipe));
+
+  while (TRUE) {
+    message = gst_bus_timed_pop_filtered (bus, GST_MESSAGE_ANY, -1);
+
+    g_assert (message != NULL);
+
+    switch (message->type) {
+      case GST_MESSAGE_EOS:
+        g_message ("got EOS");
+        gst_message_unref (message);
+        return;
+      case GST_MESSAGE_WARNING:
+      case GST_MESSAGE_ERROR:
+      {
+        GError *gerror;
+        gchar *debug;
+
+        gst_message_parse_error (message, &gerror, &debug);
+        gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
+        gst_message_unref (message);
+        g_error_free (gerror);
+        g_free (debug);
+        return;
+      }
+      case GST_MESSAGE_STEP_DONE:
+      {
+        GstFormat format;
+        guint64 amount;
+        gdouble rate;
+        gboolean flush, intermediate;
+        guint64 duration;
+
+        gst_message_parse_step_done (message, &format, &amount, &rate,
+            &flush, &intermediate, &duration);
+
+        if (format == GST_FORMAT_DEFAULT) {
+          g_message ("step done: %" GST_TIME_FORMAT " skipped in %"
+              G_GUINT64_FORMAT " frames", GST_TIME_ARGS (duration), amount);
+        } else {
+          g_message ("step done: %" GST_TIME_FORMAT " skipped",
+              GST_TIME_ARGS (duration));
+        }
+
+        return;
+      }
+      default:
+        gst_message_unref (message);
+        break;
+    }
+  }
+}
+
+/* signalled when a new preroll buffer is available */
+static void
+new_preroll (GstElement * appsink, gpointer user_data)
+{
+  GstBuffer *buffer;
+
+  g_signal_emit_by_name (appsink, "pull-preroll", &buffer);
+
+  g_message ("have new-preroll buffer %p, timestamp %" GST_TIME_FORMAT, buffer,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
+
+  gst_buffer_unref (buffer);
+}
+
+int
+main (int argc, char *argv[])
+{
+  GstElement *bin, *videotestsrc, *appsink;
+  GstFormat format;
+  gint64 pos;
+
+  gst_init (&argc, &argv);
+
+  /* create a new bin to hold the elements */
+  bin = gst_pipeline_new ("pipeline");
+  g_assert (bin);
+
+  /* create a fake source */
+  videotestsrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
+  g_assert (videotestsrc);
+  g_object_set (videotestsrc, "num-buffers", 10, NULL);
+
+  /* and a fake sink */
+  appsink = gst_element_factory_make ("appsink", "appsink");
+  g_assert (appsink);
+  g_object_set (appsink, "emit-signals", TRUE, NULL);
+  g_object_set (appsink, "sync", TRUE, NULL);
+  g_signal_connect (appsink, "new-preroll", (GCallback) new_preroll, NULL);
+
+  /* add objects to the main pipeline */
+  gst_bin_add (GST_BIN (bin), videotestsrc);
+  gst_bin_add (GST_BIN (bin), appsink);
+
+  /* link the elements */
+  gst_element_link_many (videotestsrc, appsink, NULL);
+
+  /* go to the PAUSED state and wait for preroll */
+  g_message ("prerolling first frame");
+  gst_element_set_state (bin, GST_STATE_PAUSED);
+  gst_element_get_state (bin, NULL, NULL, -1);
+
+  /* step two frames, flush so that new preroll is queued */
+  g_message ("stepping three frames");
+  g_assert (gst_element_send_event (bin,
+          gst_event_new_step (GST_FORMAT_BUFFERS, 2, 1.0, TRUE, FALSE)));
+
+  /* blocks and returns when we received the step done message */
+  event_loop (bin);
+
+  /* wait for step to really complete */
+  gst_element_get_state (bin, NULL, NULL, -1);
+
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (bin, &format, &pos);
+  g_message ("stepped two frames, now at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (pos));
+
+  /* step 3 frames, flush so that new preroll is queued */
+  g_message ("stepping 120 milliseconds ");
+  g_assert (gst_element_send_event (bin,
+          gst_event_new_step (GST_FORMAT_TIME, 120 * GST_MSECOND, 1.0, TRUE,
+              FALSE)));
+
+  /* blocks and returns when we received the step done message */
+  event_loop (bin);
+
+  /* wait for step to really complete */
+  gst_element_get_state (bin, NULL, NULL, -1);
+
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (bin, &format, &pos);
+  g_message ("stepped 120ms frames, now at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (pos));
+
+  g_message ("playing until EOS");
+  gst_element_set_state (bin, GST_STATE_PLAYING);
+  /* Run event loop listening for bus messages until EOS or ERROR */
+  event_loop (bin);
+  g_message ("finished");
+
+  /* stop the bin */
+  gst_element_set_state (bin, GST_STATE_NULL);
+
+  exit (0);
+}
diff --git a/win32/common/libgstreamer.def b/win32/common/libgstreamer.def
index 3c76a32..7edad9e 100644
--- a/win32/common/libgstreamer.def
+++ b/win32/common/libgstreamer.def
@@ -355,6 +355,7 @@ EXPORTS
 	gst_event_new_new_segment_full
 	gst_event_new_qos
 	gst_event_new_seek
+	gst_event_new_step
 	gst_event_new_tag
 	gst_event_parse_buffer_size
 	gst_event_parse_latency
@@ -362,6 +363,7 @@ EXPORTS
 	gst_event_parse_new_segment_full
 	gst_event_parse_qos
 	gst_event_parse_seek
+	gst_event_parse_step
 	gst_event_parse_tag
 	gst_event_set_seqnum
 	gst_event_type_flags_get_type
@@ -489,6 +491,7 @@ EXPORTS
 	gst_message_new_segment_start
 	gst_message_new_state_changed
 	gst_message_new_state_dirty
+	gst_message_new_step_done
 	gst_message_new_stream_status
 	gst_message_new_structure_change
 	gst_message_new_tag
@@ -507,6 +510,7 @@ EXPORTS
 	gst_message_parse_segment_done
 	gst_message_parse_segment_start
 	gst_message_parse_state_changed
+	gst_message_parse_step_done
 	gst_message_parse_stream_status
 	gst_message_parse_structure_change
 	gst_message_parse_tag
