From: Sebastian Dröge Date: Sun, 11 Jul 2010 12:44:10 +0000 (+0200) Subject: playsink: Fix gapless playback in many non-simple scenarios X-Git-Tag: 1.19.3~511^2~8240 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=405b47a79ac92b73a4994aea73ceadc6ea043ec6;p=platform%2Fupstream%2Fgstreamer.git playsink: Fix gapless playback in many non-simple scenarios 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. --- diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 7f5a900..2ba5952 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -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 diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index ba7103a..774da37 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -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 index 0000000..abb880a --- /dev/null +++ b/gst/playback/gststreamsynchronizer.c @@ -0,0 +1,788 @@ +/* GStreamer + * Copyright (C) 2010 Sebastian Dröge + * + * 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, ×tamp); + 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 "); +} + +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 index 0000000..e4234e4 --- /dev/null +++ b/gst/playback/gststreamsynchronizer.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2010 Sebastian Dröge + * + * 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 + +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__ */ diff --git a/gst/playback/test7.c b/gst/playback/test7.c index 31b9730..530229b 100644 --- a/gst/playback/test7.c +++ b/gst/playback/test7.c @@ -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);