playsink: Fix gapless playback in many non-simple scenarios
authorSebastian Dröge <sebastian.droege@collabora.co.uk>
Sun, 11 Jul 2010 12:44:10 +0000 (14:44 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Fri, 16 Jul 2010 15:40:46 +0000 (17:40 +0200)
Before gapless playback failed when switching between audio-only,
video-only and audio-video files, when choosing different clocks
and when the different streams had different durations.

This is now handled by a helper element, which keeps track of the
running times of all streams and synchronizes them.

Fixes bug #602437.

gst/playback/Makefile.am
gst/playback/gstplaysink.c
gst/playback/gststreamsynchronizer.c [new file with mode: 0644]
gst/playback/gststreamsynchronizer.h [new file with mode: 0644]
gst/playback/test7.c

index 7f5a900..2ba5952 100644 (file)
@@ -22,7 +22,8 @@ libgstplaybin_la_SOURCES = \
        gstscreenshot.c \
        gststreaminfo.c \
        gststreamselector.c \
-       gstsubtitleoverlay.c
+       gstsubtitleoverlay.c \
+       gststreamsynchronizer.c
 
 nodist_libgstplaybin_la_SOURCES = $(built_sources)
 libgstplaybin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
@@ -63,7 +64,8 @@ noinst_HEADERS = \
        gstscreenshot.h \
        gststreamselector.h \
        gstrawcaps.h \
-       gstsubtitleoverlay.h
+       gstsubtitleoverlay.h \
+       gststreamsynchronizer.h
 
 noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7
 
index ba7103a..774da37 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "gstplaysink.h"
 #include "gstscreenshot.h"
+#include "gststreamsynchronizer.h"
 
 GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
 #define GST_CAT_DEFAULT gst_play_sink_debug
@@ -136,6 +137,8 @@ struct _GstPlaySink
 
   GstPlayFlags flags;
 
+  GstStreamSynchronizer *stream_synchronizer;
+
   /* chains */
   GstPlayAudioChain *audiochain;
   GstPlayVideoDeinterlaceChain *videodeinterlacechain;
@@ -146,6 +149,8 @@ struct _GstPlaySink
   /* audio */
   GstPad *audio_pad;
   gboolean audio_pad_raw;
+  GstPad *audio_srcpad_stream_synchronizer;
+  GstPad *audio_sinkpad_stream_synchronizer;
   /* audio tee */
   GstElement *audio_tee;
   GstPad *audio_tee_sink;
@@ -154,8 +159,12 @@ struct _GstPlaySink
   /* video */
   GstPad *video_pad;
   gboolean video_pad_raw;
+  GstPad *video_srcpad_stream_synchronizer;
+  GstPad *video_sinkpad_stream_synchronizer;
   /* text */
   GstPad *text_pad;
+  GstPad *text_srcpad_stream_synchronizer;
+  GstPad *text_sinkpad_stream_synchronizer;
 
   /* properties */
   GstElement *audio_sink;
@@ -412,6 +421,11 @@ gst_play_sink_init (GstPlaySink * playsink)
   playsink->subtitle_encoding = NULL;
   playsink->flags = DEFAULT_FLAGS;
 
+  playsink->stream_synchronizer =
+      g_object_new (GST_TYPE_STREAM_SYNCHRONIZER, NULL);
+  gst_bin_add (GST_BIN_CAST (playsink),
+      GST_ELEMENT_CAST (playsink->stream_synchronizer));
+
   g_static_rec_mutex_init (&playsink->lock);
   GST_OBJECT_FLAG_SET (playsink, GST_ELEMENT_IS_SINK);
 }
@@ -503,6 +517,8 @@ gst_play_sink_dispose (GObject * object)
   g_free (playsink->subtitle_encoding);
   playsink->subtitle_encoding = NULL;
 
+  playsink->stream_synchronizer = NULL;
+
   G_OBJECT_CLASS (gst_play_sink_parent_class)->dispose (object);
 }
 
@@ -2104,7 +2120,49 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
     GST_DEBUG_OBJECT (playsink, "adding video, raw %d",
         playsink->video_pad_raw);
 
-    if (need_deinterlace) {
+    if (playsink->videochain) {
+      /* try to reactivate the chain */
+      if (!setup_video_chain (playsink, raw, async, queue)) {
+        if (playsink->video_sinkpad_stream_synchronizer) {
+          gst_element_release_request_pad (GST_ELEMENT_CAST
+              (playsink->stream_synchronizer),
+              playsink->video_sinkpad_stream_synchronizer);
+          gst_object_unref (playsink->video_sinkpad_stream_synchronizer);
+          playsink->video_sinkpad_stream_synchronizer = NULL;
+          gst_object_unref (playsink->video_srcpad_stream_synchronizer);
+          playsink->video_srcpad_stream_synchronizer = NULL;
+        }
+
+        add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
+        activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
+        free_chain ((GstPlayChain *) playsink->videochain);
+        playsink->videochain = NULL;
+      }
+    }
+
+    if (!playsink->videochain)
+      playsink->videochain = gen_video_chain (playsink, raw, async, queue);
+
+    if (!playsink->video_sinkpad_stream_synchronizer) {
+      GstIterator *it;
+
+      playsink->video_sinkpad_stream_synchronizer =
+          gst_element_get_request_pad (GST_ELEMENT_CAST
+          (playsink->stream_synchronizer), "sink_%d");
+      it = gst_pad_iterate_internal_links
+          (playsink->video_sinkpad_stream_synchronizer);
+      g_assert (it);
+      gst_iterator_next (it,
+          (gpointer *) & playsink->video_srcpad_stream_synchronizer);
+      g_assert (playsink->video_srcpad_stream_synchronizer);
+      gst_iterator_free (it);
+    }
+
+    if (playsink->video_pad)
+      gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
+          playsink->video_sinkpad_stream_synchronizer);
+
+    if (playsink->videochain && need_deinterlace) {
       if (!playsink->videodeinterlacechain)
         playsink->videodeinterlacechain =
             gen_video_deinterlace_chain (playsink);
@@ -2116,25 +2174,18 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
 
         add_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), TRUE);
         activate_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), TRUE);
-        gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
+
+        gst_pad_link (playsink->video_srcpad_stream_synchronizer,
             playsink->videodeinterlacechain->sinkpad);
       }
-    }
-
-    if (playsink->videochain) {
-      /* try to reactivate the chain */
-      if (!setup_video_chain (playsink, raw, async, queue)) {
-        add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
-        activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
-        free_chain ((GstPlayChain *) playsink->videochain);
-        playsink->videochain = NULL;
+    } else {
+      if (playsink->videodeinterlacechain) {
+        add_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), FALSE);
+        activate_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain),
+            FALSE);
       }
     }
 
-    if (!playsink->videochain) {
-      playsink->videochain = gen_video_chain (playsink, raw, async, queue);
-    }
-
     if (playsink->videochain) {
       GST_DEBUG_OBJECT (playsink, "adding video chain");
       add_chain (GST_PLAY_CHAIN (playsink->videochain), TRUE);
@@ -2147,7 +2198,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
           gst_pad_link (playsink->videodeinterlacechain->srcpad,
               playsink->videochain->sinkpad);
         else
-          gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
+          gst_pad_link (playsink->video_srcpad_stream_synchronizer,
               playsink->videochain->sinkpad);
       }
     }
@@ -2172,6 +2223,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
             gst_element_get_static_pad (playsink->vischain->chain.bin, "src");
         gst_pad_unlink (srcpad, playsink->videochain->sinkpad);
       }
+
+      if (playsink->video_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->video_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->video_sinkpad_stream_synchronizer);
+        playsink->video_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->video_srcpad_stream_synchronizer);
+        playsink->video_srcpad_stream_synchronizer = NULL;
+      }
+
       add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
       activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
       playsink->videochain->ts_offset = NULL;
@@ -2215,6 +2277,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
           gst_object_unref (playsink->audio_tee_asrc);
           playsink->audio_tee_asrc = NULL;
         }
+
+        if (playsink->audio_sinkpad_stream_synchronizer) {
+          gst_element_release_request_pad (GST_ELEMENT_CAST
+              (playsink->stream_synchronizer),
+              playsink->audio_sinkpad_stream_synchronizer);
+          gst_object_unref (playsink->audio_sinkpad_stream_synchronizer);
+          playsink->audio_sinkpad_stream_synchronizer = NULL;
+          gst_object_unref (playsink->audio_srcpad_stream_synchronizer);
+          playsink->audio_srcpad_stream_synchronizer = NULL;
+        }
+
         add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
         activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
         disconnect_chain (playsink->audiochain, playsink);
@@ -2232,6 +2305,21 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
       playsink->audiochain = gen_audio_chain (playsink, raw, queue);
     }
 
+    if (!playsink->audio_sinkpad_stream_synchronizer) {
+      GstIterator *it;
+
+      playsink->audio_sinkpad_stream_synchronizer =
+          gst_element_get_request_pad (GST_ELEMENT_CAST
+          (playsink->stream_synchronizer), "sink_%d");
+      it = gst_pad_iterate_internal_links
+          (playsink->audio_sinkpad_stream_synchronizer);
+      g_assert (it);
+      gst_iterator_next (it,
+          (gpointer *) & playsink->audio_srcpad_stream_synchronizer);
+      g_assert (playsink->audio_srcpad_stream_synchronizer);
+      gst_iterator_free (it);
+    }
+
     if (playsink->audiochain) {
       GST_DEBUG_OBJECT (playsink, "adding audio chain");
       if (playsink->audio_tee_asrc == NULL) {
@@ -2240,7 +2328,10 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
       }
       add_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
       activate_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
-      gst_pad_link (playsink->audio_tee_asrc, playsink->audiochain->sinkpad);
+      gst_pad_link (playsink->audio_tee_asrc,
+          playsink->audio_sinkpad_stream_synchronizer);
+      gst_pad_link (playsink->audio_srcpad_stream_synchronizer,
+          playsink->audiochain->sinkpad);
     }
   } else {
     GST_DEBUG_OBJECT (playsink, "no audio needed");
@@ -2254,6 +2345,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
         gst_object_unref (playsink->audio_tee_asrc);
         playsink->audio_tee_asrc = NULL;
       }
+
+      if (playsink->audio_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->audio_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->audio_sinkpad_stream_synchronizer);
+        playsink->audio_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->audio_srcpad_stream_synchronizer);
+        playsink->audio_srcpad_stream_synchronizer = NULL;
+      }
+
       if (playsink->audiochain->sink_volume) {
         disconnect_chain (playsink->audiochain, playsink);
         playsink->audiochain->volume = NULL;
@@ -2284,7 +2386,9 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
             gst_element_get_request_pad (playsink->audio_tee, "src%d");
       }
       gst_pad_link (playsink->audio_tee_vissrc, playsink->vischain->sinkpad);
-      gst_pad_link (srcpad, playsink->videochain->sinkpad);
+      gst_pad_link (srcpad, playsink->video_sinkpad_stream_synchronizer);
+      gst_pad_link (playsink->video_srcpad_stream_synchronizer,
+          playsink->videochain->sinkpad);
       gst_object_unref (srcpad);
     }
   } else {
@@ -2309,13 +2413,28 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
       playsink->textchain = gen_text_chain (playsink);
     }
     if (playsink->textchain) {
+      GstIterator *it;
+
       GST_DEBUG_OBJECT (playsink, "adding text chain");
       if (playsink->textchain->overlay)
         g_object_set (G_OBJECT (playsink->textchain->overlay), "silent", FALSE,
             NULL);
       add_chain (GST_PLAY_CHAIN (playsink->textchain), TRUE);
 
+      playsink->text_sinkpad_stream_synchronizer =
+          gst_element_get_request_pad (GST_ELEMENT_CAST
+          (playsink->stream_synchronizer), "sink_%d");
+      it = gst_pad_iterate_internal_links
+          (playsink->text_sinkpad_stream_synchronizer);
+      g_assert (it);
+      gst_iterator_next (it,
+          (gpointer *) & playsink->text_srcpad_stream_synchronizer);
+      g_assert (playsink->text_srcpad_stream_synchronizer);
+      gst_iterator_free (it);
+
       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->text_pad),
+          playsink->text_sinkpad_stream_synchronizer);
+      gst_pad_link (playsink->text_srcpad_stream_synchronizer,
           playsink->textchain->textsinkpad);
 
       if (need_vis) {
@@ -2331,7 +2450,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
           gst_pad_link (playsink->videodeinterlacechain->srcpad,
               playsink->textchain->videosinkpad);
         else
-          gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
+          gst_pad_link (playsink->video_srcpad_stream_synchronizer,
               playsink->textchain->videosinkpad);
       }
       gst_pad_link (playsink->textchain->srcpad, playsink->videochain->sinkpad);
@@ -2341,6 +2460,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
   } else {
     GST_DEBUG_OBJECT (playsink, "no text needed");
     /* we have no subtitles/text or we are requested to not show them */
+
+    if (playsink->text_sinkpad_stream_synchronizer) {
+      gst_element_release_request_pad (GST_ELEMENT_CAST
+          (playsink->stream_synchronizer),
+          playsink->text_sinkpad_stream_synchronizer);
+      gst_object_unref (playsink->text_sinkpad_stream_synchronizer);
+      playsink->text_sinkpad_stream_synchronizer = NULL;
+      gst_object_unref (playsink->text_srcpad_stream_synchronizer);
+      playsink->text_srcpad_stream_synchronizer = NULL;
+    }
+
     if (playsink->textchain) {
       if (playsink->text_pad == NULL) {
         /* no text pad, remove the chain entirely */
@@ -2355,8 +2485,20 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
               NULL);
       }
     }
-    if (!need_video && playsink->video_pad)
+    if (!need_video && playsink->video_pad) {
+      if (playsink->video_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->video_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->video_sinkpad_stream_synchronizer);
+        playsink->video_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->video_srcpad_stream_synchronizer);
+        playsink->video_srcpad_stream_synchronizer = NULL;
+      }
+
       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), NULL);
+    }
+
     if (playsink->text_pad && !playsink->textchain)
       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->text_pad), NULL);
   }
@@ -2936,7 +3078,36 @@ gst_play_sink_change_state (GstElement * element, GstStateChange transition)
       do_async_start (playsink);
       ret = GST_STATE_CHANGE_ASYNC;
       break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case GST_STATE_CHANGE_PAUSED_TO_READY:{
+      if (playsink->video_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->video_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->video_sinkpad_stream_synchronizer);
+        playsink->video_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->video_srcpad_stream_synchronizer);
+        playsink->video_srcpad_stream_synchronizer = NULL;
+      }
+      if (playsink->audio_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->audio_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->audio_sinkpad_stream_synchronizer);
+        playsink->audio_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->audio_srcpad_stream_synchronizer);
+        playsink->audio_srcpad_stream_synchronizer = NULL;
+      }
+      if (playsink->text_sinkpad_stream_synchronizer) {
+        gst_element_release_request_pad (GST_ELEMENT_CAST
+            (playsink->stream_synchronizer),
+            playsink->text_sinkpad_stream_synchronizer);
+        gst_object_unref (playsink->text_sinkpad_stream_synchronizer);
+        playsink->text_sinkpad_stream_synchronizer = NULL;
+        gst_object_unref (playsink->text_srcpad_stream_synchronizer);
+        playsink->text_srcpad_stream_synchronizer = NULL;
+      }
+    }
+      /* fall through */
     case GST_STATE_CHANGE_READY_TO_NULL:
       if (playsink->audiochain && playsink->audiochain->sink_volume) {
         /* remove our links to the mute and volume elements when they were
diff --git a/gst/playback/gststreamsynchronizer.c b/gst/playback/gststreamsynchronizer.c
new file mode 100644 (file)
index 0000000..abb880a
--- /dev/null
@@ -0,0 +1,788 @@
+/* GStreamer
+ * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gststreamsynchronizer.h"
+
+GST_DEBUG_CATEGORY_STATIC (stream_synchronizer_debug);
+#define GST_CAT_DEFAULT stream_synchronizer_debug
+
+#define GST_STREAM_SYNCHRONIZER_LOCK(obj) G_STMT_START {                   \
+    GST_LOG_OBJECT (obj,                                                \
+                    "locking from thread %p",                           \
+                    g_thread_self ());                                  \
+    g_mutex_lock (GST_STREAM_SYNCHRONIZER_CAST(obj)->lock);                \
+    GST_LOG_OBJECT (obj,                                                \
+                    "locked from thread %p",                            \
+                    g_thread_self ());                                  \
+} G_STMT_END
+
+#define GST_STREAM_SYNCHRONIZER_UNLOCK(obj) G_STMT_START {                 \
+    GST_LOG_OBJECT (obj,                                                \
+                    "unlocking from thread %p",                         \
+                    g_thread_self ());                                  \
+    g_mutex_unlock (GST_STREAM_SYNCHRONIZER_CAST(obj)->lock);              \
+} G_STMT_END
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%d",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink_%d",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+GST_BOILERPLATE (GstStreamSynchronizer, gst_stream_synchronizer,
+    GstElement, GST_TYPE_ELEMENT);
+
+typedef struct
+{
+  GstStreamSynchronizer *transform;
+  guint stream_number;
+  GstPad *srcpad;
+  GstPad *sinkpad;
+  GstSegment segment;
+
+  gboolean wait;
+  gboolean new_stream;
+
+  gint64 running_time_diff;
+} GstStream;
+
+/* Must be called with lock! */
+static GstPad *
+gst_stream_get_other_pad (GstStream * stream, GstPad * pad)
+{
+  if (stream->sinkpad == pad)
+    return gst_object_ref (stream->srcpad);
+  else if (stream->srcpad == pad)
+    return gst_object_ref (stream->sinkpad);
+
+  return NULL;
+}
+
+static GstPad *
+gst_stream_get_other_pad_from_pad (GstPad * pad)
+{
+  GstStreamSynchronizer *self =
+      GST_STREAM_SYNCHRONIZER (gst_pad_get_parent (pad));
+  GstStream *stream;
+  GstPad *opad = NULL;
+
+  GST_STREAM_SYNCHRONIZER_LOCK (self);
+  stream = gst_pad_get_element_private (pad);
+  if (!stream)
+    goto out;
+
+  opad = gst_stream_get_other_pad (stream, pad);
+
+out:
+  GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+  gst_object_unref (self);
+
+  if (!opad)
+    GST_WARNING_OBJECT (pad, "Trying to get other pad after releasing");
+
+  return opad;
+}
+
+/* Generic pad functions */
+static GstIterator *
+gst_stream_synchronizer_iterate_internal_links (GstPad * pad)
+{
+  GstIterator *it = NULL;
+  GstPad *opad;
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    it = gst_iterator_new_single (GST_TYPE_PAD, opad,
+        (GstCopyFunction) gst_object_ref, (GFreeFunc) gst_object_unref);
+    gst_object_unref (opad);
+  }
+
+  return it;
+}
+
+static gboolean
+gst_stream_synchronizer_query (GstPad * pad, GstQuery * query)
+{
+  GstPad *opad;
+  gboolean ret = FALSE;
+
+  GST_LOG_OBJECT (pad, "Handling query %s", GST_QUERY_TYPE_NAME (query));
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_peer_query (opad, query);
+    gst_object_unref (opad);
+  }
+
+  return ret;
+}
+
+static GstCaps *
+gst_stream_synchronizer_getcaps (GstPad * pad)
+{
+  GstPad *opad;
+  GstCaps *ret = NULL;
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_peer_get_caps (opad);
+    gst_object_unref (opad);
+  }
+
+  if (ret == NULL)
+    ret = gst_caps_new_any ();
+
+  GST_LOG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
+
+  return ret;
+}
+
+static gboolean
+gst_stream_synchronizer_acceptcaps (GstPad * pad, GstCaps * caps)
+{
+  GstPad *opad;
+  gboolean ret = FALSE;
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_peer_accept_caps (opad, caps);
+    gst_object_unref (opad);
+  }
+
+  GST_LOG_OBJECT (pad, "Caps%s accepted: %" GST_PTR_FORMAT, (ret ? "" : " not"),
+      caps);
+
+  return ret;
+}
+
+/* srcpad functions */
+static gboolean
+gst_stream_synchronizer_src_event (GstPad * pad, GstEvent * event)
+{
+  GstStreamSynchronizer *self =
+      GST_STREAM_SYNCHRONIZER (gst_pad_get_parent (pad));
+  GstPad *opad;
+  gboolean ret = FALSE;
+
+  GST_LOG_OBJECT (pad, "Handling event %s: %" GST_PTR_FORMAT,
+      GST_EVENT_TYPE_NAME (event), event->structure);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_QOS:{
+      gdouble proportion;
+      GstClockTimeDiff diff;
+      GstClockTime timestamp;
+      gint64 running_time_diff;
+      GstStream *stream;
+
+      gst_event_parse_qos (event, &proportion, &diff, &timestamp);
+      gst_event_unref (event);
+
+      GST_STREAM_SYNCHRONIZER_LOCK (self);
+      stream = gst_pad_get_element_private (pad);
+      if (stream)
+        running_time_diff = stream->running_time_diff;
+      else
+        running_time_diff = -1;
+      GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+
+      if (running_time_diff == -1) {
+        GST_WARNING_OBJECT (pad, "QOS event before group start");
+        goto out;
+      } else if (timestamp < running_time_diff) {
+        GST_DEBUG_OBJECT (pad, "QOS event from previous group");
+        goto out;
+      }
+
+      GST_LOG_OBJECT (pad,
+          "Adjusting QOS event: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT " = %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (timestamp),
+          GST_TIME_ARGS (running_time_diff),
+          GST_TIME_ARGS (timestamp - running_time_diff));
+
+      timestamp -= running_time_diff;
+
+      /* That case is invalid for QoS events */
+      if (diff < 0 && -diff > timestamp) {
+        GST_DEBUG_OBJECT (pad, "QOS event from previous group");
+        ret = TRUE;
+        goto out;
+      }
+
+      event = gst_event_new_qos (proportion, diff, timestamp);
+      break;
+    }
+    default:
+      break;
+  }
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_push_event (opad, event);
+    gst_object_unref (opad);
+  }
+
+out:
+  gst_object_unref (self);
+
+  return ret;
+}
+
+/* sinkpad functions */
+static gboolean
+gst_stream_synchronizer_sink_event (GstPad * pad, GstEvent * event)
+{
+  GstStreamSynchronizer *self =
+      GST_STREAM_SYNCHRONIZER (gst_pad_get_parent (pad));
+  GstPad *opad;
+  gboolean ret = FALSE;
+
+  GST_LOG_OBJECT (pad, "Handling event %s: %" GST_PTR_FORMAT,
+      GST_EVENT_TYPE_NAME (event), event->structure);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SINK_MESSAGE:{
+      GstMessage *message;
+
+      gst_event_parse_sink_message (event, &message);
+      if (message->structure
+          && gst_structure_has_name (message->structure,
+              "playbin2-stream-changed")) {
+        GstStream *stream;
+
+        GST_STREAM_SYNCHRONIZER_LOCK (self);
+        stream = gst_pad_get_element_private (pad);
+        if (stream) {
+          GList *l;
+          gboolean all_wait = TRUE;
+
+          GST_DEBUG_OBJECT (pad, "Stream %d changed", stream->stream_number);
+
+          stream->wait = TRUE;
+          stream->new_stream = TRUE;
+
+          for (l = self->streams; l; l = l->next) {
+            GstStream *ostream = l->data;
+
+            all_wait = all_wait && ostream->wait;
+            if (!all_wait)
+              break;
+          }
+          if (all_wait) {
+            gint64 last_stop = 0;
+
+            GST_DEBUG_OBJECT (self, "All streams have changed -- unblocking");
+
+            for (l = self->streams; l; l = l->next) {
+              GstStream *ostream = l->data;
+              gint64 stop_running_time;
+              gint64 last_stop_running_time;
+
+              ostream->wait = FALSE;
+
+              stop_running_time =
+                  gst_segment_to_running_time (&ostream->segment,
+                  GST_FORMAT_TIME, ostream->segment.stop);
+              last_stop_running_time =
+                  gst_segment_to_running_time (&ostream->segment,
+                  GST_FORMAT_TIME, ostream->segment.last_stop);
+              last_stop =
+                  MAX (last_stop, MAX (stop_running_time,
+                      last_stop_running_time));
+            }
+            last_stop = MAX (0, last_stop);
+            self->group_start_time = MAX (self->group_start_time, last_stop);
+
+            GST_DEBUG_OBJECT (self, "New group start time: %" GST_TIME_FORMAT,
+                GST_TIME_ARGS (self->group_start_time));
+
+            g_cond_broadcast (self->stream_finish_cond);
+          }
+        }
+        GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+      }
+      gst_message_unref (message);
+      break;
+    }
+    case GST_EVENT_NEWSEGMENT:{
+      GstStream *stream;
+      gboolean update;
+      gdouble rate, applied_rate;
+      GstFormat format;
+      gint64 start, stop, position;
+
+      gst_event_parse_new_segment_full (event,
+          &update, &rate, &applied_rate, &format, &start, &stop, &position);
+
+      GST_STREAM_SYNCHRONIZER_LOCK (self);
+      stream = gst_pad_get_element_private (pad);
+      if (stream) {
+        if (stream->wait) {
+          GST_DEBUG_OBJECT (pad, "Stream %d is waiting", stream->stream_number);
+          g_cond_wait (self->stream_finish_cond, self->lock);
+          stream = gst_pad_get_element_private (pad);
+          if (stream)
+            stream->wait = FALSE;
+        }
+      }
+
+      if (stream && format == GST_FORMAT_TIME) {
+        if (stream->new_stream) {
+          gint64 last_stop_running_time = 0;
+          gint64 stop_running_time = 0;
+
+          if (stream->segment.format == GST_FORMAT_TIME) {
+            last_stop_running_time =
+                gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+                stream->segment.last_stop);
+            last_stop_running_time = MAX (last_stop_running_time, 0);
+            stop_running_time =
+                gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+                stream->segment.stop);
+            stop_running_time = MAX (last_stop_running_time, 0);
+
+            if (stop_running_time != last_stop_running_time) {
+              GST_WARNING_OBJECT (pad,
+                  "Gap between last_stop and segment stop: %" GST_TIME_FORMAT
+                  " != %" GST_TIME_FORMAT, GST_TIME_ARGS (stop_running_time),
+                  GST_TIME_ARGS (last_stop_running_time));
+            }
+
+            if (stop_running_time < last_stop_running_time) {
+              GST_DEBUG_OBJECT (pad, "Updating stop position");
+              gst_pad_push_event (stream->srcpad,
+                  gst_event_new_new_segment_full (TRUE, stream->segment.rate,
+                      stream->segment.applied_rate, GST_FORMAT_TIME,
+                      stream->segment.start, stream->segment.last_stop,
+                      stream->segment.time));
+              gst_segment_set_newsegment_full (&stream->segment, TRUE,
+                  stream->segment.rate, stream->segment.applied_rate,
+                  GST_FORMAT_TIME, stream->segment.start,
+                  stream->segment.last_stop, stream->segment.time);
+            }
+            stop_running_time = MAX (stop_running_time, last_stop_running_time);
+            GST_DEBUG_OBJECT (pad,
+                "Stop running time of last group: %" GST_TIME_FORMAT,
+                GST_TIME_ARGS (stop_running_time));
+          }
+          stream->new_stream = FALSE;
+
+          if (stop_running_time < self->group_start_time) {
+            gint64 diff = self->group_start_time - stop_running_time;
+
+            GST_DEBUG_OBJECT (pad,
+                "Advancing running time for other streams by: %"
+                GST_TIME_FORMAT, GST_TIME_ARGS (diff));
+            gst_pad_push_event (stream->srcpad,
+                gst_event_new_new_segment_full (FALSE, 1.0, 1.0,
+                    GST_FORMAT_TIME, 0, diff, 0));
+            gst_segment_set_newsegment_full (&stream->segment, FALSE, 1.0, 1.0,
+                GST_FORMAT_TIME, 0, diff, 0);
+          }
+        }
+
+        GST_DEBUG_OBJECT (pad, "Segment was: %" GST_SEGMENT_FORMAT,
+            &stream->segment);
+        gst_segment_set_newsegment_full (&stream->segment, update, rate,
+            applied_rate, format, start, stop, position);
+        GST_DEBUG_OBJECT (pad, "Segment now is: %" GST_SEGMENT_FORMAT,
+            &stream->segment);
+
+        GST_DEBUG_OBJECT (pad, "Stream start running time: %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (stream->segment.accum));
+        stream->running_time_diff = stream->segment.accum;
+      } else if (stream) {
+        GST_ERROR_OBJECT (pad, "Non-TIME segment");
+        gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
+      }
+      GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+      break;
+    }
+    case GST_EVENT_FLUSH_STOP:{
+      GstStream *stream;
+
+      GST_STREAM_SYNCHRONIZER_LOCK (self);
+      stream = gst_pad_get_element_private (pad);
+      if (stream) {
+        GST_DEBUG_OBJECT (pad, "Resetting segment for stream %d",
+            stream->stream_number);
+        gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
+      }
+      GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+      break;
+    }
+    default:
+      break;
+  }
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_push_event (opad, event);
+    gst_object_unref (opad);
+  }
+
+  gst_object_unref (self);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_stream_synchronizer_sink_bufferalloc (GstPad * pad, guint64 offset,
+    guint size, GstCaps * caps, GstBuffer ** buf)
+{
+  GstPad *opad;
+  GstFlowReturn ret = GST_FLOW_ERROR;
+
+  GST_LOG_OBJECT (pad, "Allocating buffer: size=%u", size);
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_alloc_buffer (opad, offset, size, caps, buf);
+    gst_object_unref (opad);
+  }
+
+  GST_LOG_OBJECT (pad, "Allocation: %s", gst_flow_get_name (ret));
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_stream_synchronizer_sink_chain (GstPad * pad, GstBuffer * buffer)
+{
+  GstStreamSynchronizer *self =
+      GST_STREAM_SYNCHRONIZER (gst_pad_get_parent (pad));
+  GstPad *opad;
+  GstFlowReturn ret = GST_FLOW_ERROR;
+  GstStream *stream;
+  GstClockTime timestamp = GST_CLOCK_TIME_NONE;
+  GstClockTime timestamp_end = GST_CLOCK_TIME_NONE;
+
+  GST_LOG_OBJECT (pad, "Handling buffer %p: size=%u, timestamp=%"
+      GST_TIME_FORMAT " duration=%" GST_TIME_FORMAT
+      " offset=%" G_GUINT64_FORMAT " offset_end=%" G_GUINT64_FORMAT,
+      buffer, GST_BUFFER_SIZE (buffer),
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+      GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)),
+      GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET_END (buffer));
+
+  timestamp = GST_BUFFER_TIMESTAMP (buffer);
+  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)
+      && GST_BUFFER_DURATION_IS_VALID (buffer))
+    timestamp_end = timestamp + GST_BUFFER_DURATION (buffer);
+
+  GST_STREAM_SYNCHRONIZER_LOCK (self);
+  stream = gst_pad_get_element_private (pad);
+
+  if (stream && stream->segment.format == GST_FORMAT_TIME
+      && GST_CLOCK_TIME_IS_VALID (timestamp)) {
+    GST_LOG_OBJECT (pad,
+        "Updating last-stop from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (stream->segment.last_stop), GST_TIME_ARGS (timestamp));
+    gst_segment_set_last_stop (&stream->segment, GST_FORMAT_TIME, timestamp);
+  }
+  GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+
+  opad = gst_stream_get_other_pad_from_pad (pad);
+  if (opad) {
+    ret = gst_pad_push (opad, buffer);
+    gst_object_unref (opad);
+  }
+
+  GST_LOG_OBJECT (pad, "Push returned: %s", gst_flow_get_name (ret));
+  if (ret == GST_FLOW_OK) {
+    GST_STREAM_SYNCHRONIZER_LOCK (self);
+    stream = gst_pad_get_element_private (pad);
+    if (stream && stream->segment.format == GST_FORMAT_TIME
+        && GST_CLOCK_TIME_IS_VALID (timestamp_end)) {
+      GST_LOG_OBJECT (pad,
+          "Updating last-stop from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (stream->segment.last_stop),
+          GST_TIME_ARGS (timestamp_end));
+      gst_segment_set_last_stop (&stream->segment, GST_FORMAT_TIME,
+          timestamp_end);
+    }
+    GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+  }
+
+  gst_object_unref (self);
+
+  return ret;
+}
+
+/* GstElement vfuncs */
+static GstPad *
+gst_stream_synchronizer_request_new_pad (GstElement * element,
+    GstPadTemplate * temp, const gchar * name)
+{
+  GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
+  GstStream *stream;
+  gchar *tmp;
+
+  GST_STREAM_SYNCHRONIZER_LOCK (self);
+  GST_DEBUG_OBJECT (self, "Requesting new pad for stream %d",
+      self->current_stream_number);
+
+  stream = g_slice_new0 (GstStream);
+  stream->transform = self;
+  stream->stream_number = self->current_stream_number;
+
+  tmp = g_strdup_printf ("sink_%d", self->current_stream_number);
+  stream->sinkpad = gst_pad_new_from_static_template (&sinktemplate, tmp);
+  g_free (tmp);
+  gst_pad_set_element_private (stream->sinkpad, stream);
+  gst_pad_set_iterate_internal_links_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_iterate_internal_links));
+  gst_pad_set_query_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_query));
+  gst_pad_set_getcaps_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_getcaps));
+  gst_pad_set_acceptcaps_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_acceptcaps));
+  gst_pad_set_event_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_sink_event));
+  gst_pad_set_chain_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_sink_chain));
+  gst_pad_set_bufferalloc_function (stream->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_sink_bufferalloc));
+
+  tmp = g_strdup_printf ("src_%d", self->current_stream_number);
+  stream->srcpad = gst_pad_new_from_static_template (&srctemplate, tmp);
+  g_free (tmp);
+  gst_pad_set_element_private (stream->srcpad, stream);
+  gst_pad_set_iterate_internal_links_function (stream->srcpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_iterate_internal_links));
+  gst_pad_set_query_function (stream->srcpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_query));
+  gst_pad_set_getcaps_function (stream->srcpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_getcaps));
+  gst_pad_set_acceptcaps_function (stream->srcpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_acceptcaps));
+  gst_pad_set_event_function (stream->srcpad,
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_src_event));
+
+  gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
+
+  self->streams = g_list_prepend (self->streams, stream);
+  self->current_stream_number++;
+  GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+
+  /* Add pads and activate unless we're going to NULL */
+  g_static_rec_mutex_lock (GST_STATE_GET_LOCK (self));
+  if (GST_STATE_TARGET (self) != GST_STATE_NULL) {
+    gst_pad_set_active (stream->srcpad, TRUE);
+    gst_pad_set_active (stream->sinkpad, TRUE);
+  }
+  gst_element_add_pad (GST_ELEMENT_CAST (self), stream->srcpad);
+  gst_element_add_pad (GST_ELEMENT_CAST (self), stream->sinkpad);
+  g_static_rec_mutex_unlock (GST_STATE_GET_LOCK (self));
+
+  return stream->sinkpad;
+}
+
+/* Must be called with lock! */
+static void
+gst_stream_synchronizer_release_stream (GstStreamSynchronizer * self,
+    GstStream * stream)
+{
+  GList *l;
+
+  GST_DEBUG_OBJECT (self, "Releasing stream %d", stream->stream_number);
+
+  for (l = self->streams; l; l = l->next) {
+    if (l->data == stream) {
+      self->streams = g_list_delete_link (self->streams, l);
+      break;
+    }
+  }
+  g_assert (l != NULL);
+
+  gst_pad_set_element_private (stream->srcpad, NULL);
+  gst_pad_set_element_private (stream->sinkpad, NULL);
+  gst_pad_set_active (stream->srcpad, FALSE);
+  gst_pad_set_active (stream->sinkpad, FALSE);
+  gst_element_remove_pad (GST_ELEMENT_CAST (self), stream->srcpad);
+  gst_element_remove_pad (GST_ELEMENT_CAST (self), stream->sinkpad);
+
+  if (stream->segment.format == GST_FORMAT_TIME) {
+    gint64 stop_running_time;
+    gint64 last_stop_running_time;
+
+    stop_running_time =
+        gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+        stream->segment.stop);
+    last_stop_running_time =
+        gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+        stream->segment.last_stop);
+    stop_running_time = MAX (stop_running_time, last_stop_running_time);
+
+    GST_DEBUG_OBJECT (stream->sinkpad,
+        "Stop running time was: %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (stop_running_time));
+
+    self->group_start_time = MAX (self->group_start_time, stop_running_time);
+  }
+
+  g_slice_free (GstStream, stream);
+}
+
+static void
+gst_stream_synchronizer_release_pad (GstElement * element, GstPad * pad)
+{
+  GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
+  GstStream *stream;
+
+  GST_STREAM_SYNCHRONIZER_LOCK (self);
+  stream = gst_pad_get_element_private (pad);
+  if (stream) {
+    g_assert (stream->sinkpad == pad);
+
+    gst_stream_synchronizer_release_stream (self, stream);
+  }
+  GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+}
+
+static GstStateChangeReturn
+gst_stream_synchronizer_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      GST_DEBUG_OBJECT (self, "State change NULL->READY");
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      GST_DEBUG_OBJECT (self, "State change READY->PAUSED");
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      GST_DEBUG_OBJECT (self, "State change PAUSED->PLAYING");
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:{
+      GST_DEBUG_OBJECT (self, "State change READY->NULL");
+
+      GST_STREAM_SYNCHRONIZER_LOCK (self);
+      g_cond_broadcast (self->stream_finish_cond);
+      while (self->streams)
+        gst_stream_synchronizer_release_stream (self, self->streams->data);
+      self->current_stream_number = 0;
+      GST_STREAM_SYNCHRONIZER_UNLOCK (self);
+      break;
+    }
+    default:
+      break;
+  }
+
+  {
+    GstStateChangeReturn bret;
+
+    bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+    GST_DEBUG_OBJECT (self, "Base class state changed returned: %d", bret);
+    if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE))
+      return ret;
+  }
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      GST_DEBUG_OBJECT (self, "State change PLAYING->PAUSED");
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      GST_DEBUG_OBJECT (self, "State change PAUSED->READY");
+      self->group_start_time = 0;
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:{
+      GST_DEBUG_OBJECT (self, "State change READY->NULL");
+      break;
+    }
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+/* GObject vfuncs */
+static void
+gst_stream_synchronizer_finalize (GObject * object)
+{
+  GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (object);
+
+  if (self->lock) {
+    g_mutex_free (self->lock);
+    self->lock = NULL;
+  }
+
+  if (self->stream_finish_cond) {
+    g_cond_free (self->stream_finish_cond);
+    self->stream_finish_cond = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* GObject type initialization */
+static void
+gst_stream_synchronizer_init (GstStreamSynchronizer * self,
+    GstStreamSynchronizerClass * klass)
+{
+  self->lock = g_mutex_new ();
+  self->stream_finish_cond = g_cond_new ();
+}
+
+static void
+gst_stream_synchronizer_base_init (gpointer g_class)
+{
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&srctemplate));
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&sinktemplate));
+
+  gst_element_class_set_details_simple (gstelement_class,
+      "Stream Synchronizer", "Generic",
+      "Synchronizes a group of streams to have equal durations and starting points",
+      "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
+}
+
+static void
+gst_stream_synchronizer_class_init (GstStreamSynchronizerClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstElementClass *element_class = (GstElementClass *) klass;
+
+  GST_DEBUG_CATEGORY_INIT (stream_synchronizer_debug,
+      "streamsynchronizer", 0, "Stream Synchronizer");
+
+  gobject_class->finalize = gst_stream_synchronizer_finalize;
+
+  element_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_change_state);
+  element_class->request_new_pad =
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_request_new_pad);
+  element_class->release_pad =
+      GST_DEBUG_FUNCPTR (gst_stream_synchronizer_release_pad);
+}
diff --git a/gst/playback/gststreamsynchronizer.h b/gst/playback/gststreamsynchronizer.h
new file mode 100644 (file)
index 0000000..e4234e4
--- /dev/null
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@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.
+ */
+
+#ifndef __GST_STREAM_SYNCHRONIZER_H__
+#define __GST_STREAM_SYNCHRONIZER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_STREAM_SYNCHRONIZER \
+  (gst_stream_synchronizer_get_type())
+#define GST_STREAM_SYNCHRONIZER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_STREAM_SYNCHRONIZER, GstStreamSynchronizer))
+#define GST_STREAM_SYNCHRONIZER_CAST(obj) \
+  ((GstStreamSynchronizer *) obj)
+#define GST_STREAM_SYNCHRONIZER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_STREAM_SYNCHRONIZER, GstStreamSynchronizerClass))
+#define GST_IS_STREAM_SYNCHRONIZER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_STREAM_SYNCHRONIZER))
+#define GST_IS_STREAM_SYNCHRONIZER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_STREAM_SYNCHRONIZER))
+
+typedef struct _GstStreamSynchronizer GstStreamSynchronizer;
+typedef struct _GstStreamSynchronizerClass GstStreamSynchronizerClass;
+
+struct _GstStreamSynchronizer
+{
+  GstElement parent;
+
+  /* < private > */
+  GMutex *lock;
+  GCond *stream_finish_cond;
+
+  GList *streams;
+  guint current_stream_number;
+
+  GstClockTime group_start_time;
+};
+
+struct _GstStreamSynchronizerClass
+{
+  GstElementClass parent;
+};
+
+GType gst_stream_synchronizer_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_STREAM_SYNCHRONIZER_H__ */
index 31b9730..530229b 100644 (file)
@@ -94,6 +94,27 @@ eos_cb (GstBus * bus, GstMessage * msg, GMainLoop * main_loop)
 }
 
 static void
+new_clock_cb (GstBus * bus, GstMessage * msg, gpointer nothing)
+{
+  GstClock *clock;
+
+  gst_message_parse_new_clock (msg, &clock);
+  g_print ("NEW CLOCK: %s\n", GST_OBJECT_NAME (clock));
+}
+
+static void
+clock_lost_cb (GstBus * bus, GstMessage * msg, GstElement * playbin)
+{
+  GstClock *clock;
+
+  gst_message_parse_clock_lost (msg, &clock);
+  g_print ("CLOCK LOST: %s\n", GST_OBJECT_NAME (clock));
+
+  gst_element_set_state (playbin, GST_STATE_PAUSED);
+  gst_element_set_state (playbin, GST_STATE_PLAYING);
+}
+
+static void
 about_to_finish_cb (GstElement * element, gchar * uri[])
 {
   if (arg_count < max_count) {
@@ -128,6 +149,9 @@ main (gint argc, gchar * argv[])
   g_signal_connect (bus, "message::eos", G_CALLBACK (eos_cb), loop);
   g_signal_connect (bus, "message::error", G_CALLBACK (error_cb), loop);
   g_signal_connect (bus, "message::warning", G_CALLBACK (warning_cb), NULL);
+  g_signal_connect (bus, "message::new-clock", G_CALLBACK (new_clock_cb), NULL);
+  g_signal_connect (bus, "message::clock-lost", G_CALLBACK (clock_lost_cb),
+      player);
 
   g_object_set (G_OBJECT (player), "uri", argv[1], NULL);