diff --git a/gst/gstpad.c b/gst/gstpad.c
index bd36a19..1bf1194 100644
--- a/gst/gstpad.c
+++ b/gst/gstpad.c
@@ -95,6 +95,21 @@ enum
   /* FILL ME */
 };
 
+typedef struct _GstPadPushCache GstPadPushCache;
+
+struct _GstPadPushCache
+{
+  GstPad *peer;                 /* reffed peer pad */
+  GstCaps *caps;                /* caps for this link */
+  GstPadChainFunction chainfunc;
+};
+
+static GstPadPushCache _pad_cache_invalid = { NULL, };
+
+#define PAD_CACHE_INVALID (&_pad_cache_invalid)
+
+static void pad_invalidate_cache (GstPad * pad);
+
 #define GST_PAD_GET_PRIVATE(obj)  \
    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_PAD, GstPadPrivate))
 
@@ -103,6 +118,8 @@ enum
 struct _GstPadPrivate
 {
   GstPadChainListFunction chainlistfunc;
+
+  GstPadPushCache *cache_ptr;
 };
 
 static void gst_pad_dispose (GObject * object);
@@ -617,6 +634,7 @@ pre_activate (GstPad * pad, GstActivateMode new_mode)
     case GST_ACTIVATE_NONE:
       GST_OBJECT_LOCK (pad);
       GST_DEBUG_OBJECT (pad, "setting ACTIVATE_MODE NONE, set flushing");
+      pad_invalidate_cache (pad);
       GST_PAD_SET_FLUSHING (pad);
       GST_PAD_ACTIVATE_MODE (pad) = new_mode;
       /* unlock blocked pads so element can resume and stop */
@@ -857,6 +875,7 @@ failure:
     GST_OBJECT_LOCK (pad);
     GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "failed to %s in pull mode",
         active ? "activate" : "deactivate");
+    pad_invalidate_cache (pad);
     GST_PAD_SET_FLUSHING (pad);
     GST_PAD_ACTIVATE_MODE (pad) = old;
     GST_OBJECT_UNLOCK (pad);
@@ -962,6 +981,7 @@ failure:
     GST_OBJECT_LOCK (pad);
     GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "failed to %s in push mode",
         active ? "activate" : "deactivate");
+    pad_invalidate_cache (pad);
     GST_PAD_SET_FLUSHING (pad);
     GST_PAD_ACTIVATE_MODE (pad) = old;
     GST_OBJECT_UNLOCK (pad);
@@ -1039,6 +1059,7 @@ gst_pad_set_blocked_async_full (GstPad * pad, gboolean blocked,
   if (blocked) {
     GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad, "blocking pad");
 
+    pad_invalidate_cache (pad);
     GST_OBJECT_FLAG_SET (pad, GST_PAD_BLOCKED);
 
     if (pad->block_destroy_data && pad->block_data)
@@ -1717,6 +1738,8 @@ gst_pad_unlink (GstPad * srcpad, GstPad * sinkpad)
     GST_PAD_UNLINKFUNC (sinkpad) (sinkpad);
   }
 
+  pad_invalidate_cache (srcpad);
+
   /* first clear peers */
   GST_PAD_PEER (srcpad) = NULL;
   GST_PAD_PEER (sinkpad) = NULL;
@@ -4134,7 +4157,8 @@ gst_pad_data_get_caps (gboolean is_buffer, void *data)
  * checking for that little extra speed.
  */
 static inline GstFlowReturn
-gst_pad_chain_data_unchecked (GstPad * pad, gboolean is_buffer, void *data)
+gst_pad_chain_data_unchecked (GstPad * pad, gboolean is_buffer, void *data,
+    GstPadPushCache * cache)
 {
   GstCaps *caps;
   gboolean caps_changed;
@@ -4156,6 +4180,7 @@ gst_pad_chain_data_unchecked (GstPad * pad, gboolean is_buffer, void *data)
   /* see if the signal should be emited, we emit before caps nego as
    * we might drop the buffer and do capsnego for nothing. */
   if (G_UNLIKELY (emit_signal)) {
+    cache = NULL;
     if (G_LIKELY (is_buffer)) {
       if (!gst_pad_emit_have_data_signal (pad, GST_MINI_OBJECT (data)))
         goto dropping;
@@ -4187,6 +4212,12 @@ gst_pad_chain_data_unchecked (GstPad * pad, gboolean is_buffer, void *data)
     GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad,
         "calling chainfunction &%s", GST_DEBUG_FUNCPTR_NAME (chainfunc));
 
+    if (cache) {
+      cache->peer = gst_object_ref (pad);
+      cache->caps = caps ? gst_caps_ref (caps) : NULL;
+      cache->chainfunc = chainfunc;
+    }
+
     ret = chainfunc (pad, GST_BUFFER_CAST (data));
 
     GST_CAT_LOG_OBJECT (GST_CAT_SCHEDULING, pad,
@@ -4235,11 +4266,11 @@ chain_groups:
         } else {
           GST_CAT_INFO_OBJECT (GST_CAT_SCHEDULING, pad, "chaining group");
         }
-        ret = gst_pad_chain_data_unchecked (pad, TRUE, group);
+        ret = gst_pad_chain_data_unchecked (pad, TRUE, group, NULL);
       } while (ret == GST_FLOW_OK && gst_buffer_list_iterator_next_group (it));
     } else {
       GST_CAT_INFO_OBJECT (GST_CAT_SCHEDULING, pad, "chaining empty group");
-      ret = gst_pad_chain_data_unchecked (pad, TRUE, gst_buffer_new ());
+      ret = gst_pad_chain_data_unchecked (pad, TRUE, gst_buffer_new (), NULL);
     }
 
     gst_buffer_list_iterator_free (it);
@@ -4319,7 +4350,7 @@ gst_pad_chain (GstPad * pad, GstBuffer * buffer)
   g_return_val_if_fail (GST_PAD_IS_SINK (pad), GST_FLOW_ERROR);
   g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
 
-  return gst_pad_chain_data_unchecked (pad, TRUE, buffer);
+  return gst_pad_chain_data_unchecked (pad, TRUE, buffer, NULL);
 }
 
 /**
@@ -4357,11 +4388,12 @@ gst_pad_chain_list (GstPad * pad, GstBufferList * list)
   g_return_val_if_fail (GST_PAD_IS_SINK (pad), GST_FLOW_ERROR);
   g_return_val_if_fail (GST_IS_BUFFER_LIST (list), GST_FLOW_ERROR);
 
-  return gst_pad_chain_data_unchecked (pad, FALSE, list);
+  return gst_pad_chain_data_unchecked (pad, FALSE, list, NULL);
 }
 
 static GstFlowReturn
-gst_pad_push_data (GstPad * pad, gboolean is_buffer, void *data)
+gst_pad_push_data (GstPad * pad, gboolean is_buffer, void *data,
+    GstPadPushCache * cache)
 {
   GstPad *peer;
   GstFlowReturn ret;
@@ -4379,6 +4411,7 @@ gst_pad_push_data (GstPad * pad, gboolean is_buffer, void *data)
   /* we emit signals on the pad arg, the peer will have a chance to
    * emit in the _chain() function */
   if (G_UNLIKELY (GST_PAD_DO_BUFFER_SIGNALS (pad) > 0)) {
+    cache = NULL;
     /* unlock before emitting */
     GST_OBJECT_UNLOCK (pad);
 
@@ -4404,7 +4437,6 @@ gst_pad_push_data (GstPad * pad, gboolean is_buffer, void *data)
 
   /* take ref to peer pad before releasing the lock */
   gst_object_ref (peer);
-
   GST_OBJECT_UNLOCK (pad);
 
   /* we got a new datatype from the pad, it had better handle it */
@@ -4416,7 +4448,7 @@ gst_pad_push_data (GstPad * pad, gboolean is_buffer, void *data)
       goto not_negotiated;
   }
 
-  ret = gst_pad_chain_data_unchecked (peer, is_buffer, data);
+  ret = gst_pad_chain_data_unchecked (peer, is_buffer, data, cache);
 
   gst_object_unref (peer);
 
@@ -4442,11 +4474,11 @@ push_groups:
         } else {
           GST_CAT_INFO_OBJECT (GST_CAT_SCHEDULING, pad, "pushing group");
         }
-        ret = gst_pad_push_data (pad, TRUE, group);
+        ret = gst_pad_push_data (pad, TRUE, group, NULL);
       } while (ret == GST_FLOW_OK && gst_buffer_list_iterator_next_group (it));
     } else {
       GST_CAT_INFO_OBJECT (GST_CAT_SCHEDULING, pad, "pushing empty group");
-      ret = gst_pad_push_data (pad, TRUE, gst_buffer_new ());
+      ret = gst_pad_push_data (pad, TRUE, gst_buffer_new (), NULL);
     }
 
     gst_buffer_list_iterator_free (it);
@@ -4487,6 +4519,76 @@ not_negotiated:
   }
 }
 
+static GstPadPushCache *
+pad_take_cache (GstPad * pad, gpointer * cache_ptr)
+{
+  GstPadPushCache *cache;
+
+  /* try to get the cached data */
+  do {
+    cache = g_atomic_pointer_get (cache_ptr);
+    /* now try to replace the pointer with NULL to mark that we are busy
+     * with it */
+  } while (!g_atomic_pointer_compare_and_exchange (cache_ptr, cache, NULL));
+
+  /* we could have a leftover invalid entry */
+  if (G_UNLIKELY (cache == PAD_CACHE_INVALID))
+    cache = NULL;
+
+  return cache;
+}
+
+static void
+pad_free_cache (GstPadPushCache * cache)
+{
+  gst_object_unref (cache->peer);
+  if (cache->caps)
+    gst_caps_unref (cache->caps);
+  g_slice_free (GstPadPushCache, cache);
+}
+
+static void
+pad_put_cache (GstPad * pad, GstPadPushCache * cache, gpointer * cache_ptr)
+{
+  /* put it back */
+  if (!g_atomic_pointer_compare_and_exchange (cache_ptr, NULL, cache)) {
+    /* something changed, clean up our cache */
+    pad_free_cache (cache);
+  }
+}
+
+/* must be called with the pad lock */
+static void
+pad_invalidate_cache (GstPad * pad)
+{
+  GstPadPushCache *cache;
+  gpointer *cache_ptr;
+
+  GST_LOG_OBJECT (pad, "Invalidating pad cache");
+
+  /* we hold the pad lock here so we can get the peer and it stays
+   * alive during this call */
+  if (GST_PAD_IS_SINK (pad)) {
+    if (!(pad = GST_PAD_PEER (pad)))
+      return;
+  }
+
+  cache_ptr = (gpointer *) & pad->abidata.ABI.priv->cache_ptr;
+
+  /* try to get the cached data */
+  do {
+    cache = g_atomic_pointer_get (cache_ptr);
+    /* now try to replace the pointer with INVALID. If nothing is busy with this
+     * caps, we get the cache and clean it up. If something is busy, we replace
+     * with INVALID so that when the function finishes and tries to put the
+     * cache back, it'll fail and cleanup */
+  } while (!g_atomic_pointer_compare_and_exchange (cache_ptr, cache,
+          PAD_CACHE_INVALID));
+
+  if (G_LIKELY (cache && cache != PAD_CACHE_INVALID))
+    pad_free_cache (cache);
+}
+
 /**
  * gst_pad_push:
  * @pad: a source #GstPad, returns #GST_FLOW_ERROR if not.
@@ -4516,11 +4618,64 @@ not_negotiated:
 GstFlowReturn
 gst_pad_push (GstPad * pad, GstBuffer * buffer)
 {
+  GstPadPushCache *cache;
+  GstFlowReturn ret;
+  gpointer *cache_ptr;
+  GstPad *peer;
+  GstCaps *caps;
+
   g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR);
   g_return_val_if_fail (GST_PAD_IS_SRC (pad), GST_FLOW_ERROR);
   g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
 
-  return gst_pad_push_data (pad, TRUE, buffer);
+  cache_ptr = (gpointer *) & pad->abidata.ABI.priv->cache_ptr;
+
+  cache = pad_take_cache (pad, cache_ptr);
+
+  if (G_UNLIKELY (cache == NULL))
+    goto slow_path;
+
+  /* check caps */
+  caps = GST_BUFFER_CAPS (buffer);
+  if (G_UNLIKELY (caps && caps != cache->caps)) {
+    pad_free_cache (cache);
+    goto slow_path;
+  }
+
+  peer = cache->peer;
+
+  GST_PAD_STREAM_LOCK (peer);
+
+  ret = cache->chainfunc (peer, buffer);
+
+  GST_PAD_STREAM_UNLOCK (peer);
+
+  pad_put_cache (pad, cache, cache_ptr);
+
+  return ret;
+
+  /* slow path */
+slow_path:
+  {
+    GstPadPushCache scache = { NULL, };
+
+    GST_LOG_OBJECT (pad, "Taking slow path");
+
+    ret = gst_pad_push_data (pad, TRUE, buffer, &scache);
+
+    if (scache.peer) {
+      GstPadPushCache *ncache;
+
+      GST_LOG_OBJECT (pad, "Caching push data");
+
+      /* make cache structure */
+      ncache = g_slice_new (GstPadPushCache);
+      *ncache = scache;
+
+      pad_put_cache (pad, ncache, cache_ptr);
+    }
+    return ret;
+  }
 }
 
 /**
@@ -4565,7 +4720,7 @@ gst_pad_push_list (GstPad * pad, GstBufferList * list)
   g_return_val_if_fail (GST_PAD_IS_SRC (pad), GST_FLOW_ERROR);
   g_return_val_if_fail (GST_IS_BUFFER_LIST (list), GST_FLOW_ERROR);
 
-  return gst_pad_push_data (pad, FALSE, list);
+  return gst_pad_push_data (pad, FALSE, list, NULL);
 }
 
 /**
@@ -4924,6 +5079,7 @@ gst_pad_push_event (GstPad * pad, GstEvent * event)
    * . handle pad blocking */
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_FLUSH_START:
+      pad_invalidate_cache (pad);
       GST_PAD_SET_FLUSHING (pad);
 
       if (G_UNLIKELY (GST_PAD_IS_BLOCKED (pad))) {
@@ -5078,6 +5234,8 @@ gst_pad_send_event (GstPad * pad, GstEvent * event)
       /* can't even accept a flush begin event when flushing */
       if (GST_PAD_IS_FLUSHING (pad))
         goto flushing;
+
+      pad_invalidate_cache (pad);
       GST_PAD_SET_FLUSHING (pad);
       GST_CAT_DEBUG_OBJECT (GST_CAT_EVENT, pad, "set flush flag");
       break;
