mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-06-07 07:58:51 +00:00
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.
This commit is contained in:
parent
f835533cb4
commit
405b47a79a
5 changed files with 1074 additions and 23 deletions
|
@ -22,7 +22,8 @@ libgstplaybin_la_SOURCES = \
|
||||||
gstscreenshot.c \
|
gstscreenshot.c \
|
||||||
gststreaminfo.c \
|
gststreaminfo.c \
|
||||||
gststreamselector.c \
|
gststreamselector.c \
|
||||||
gstsubtitleoverlay.c
|
gstsubtitleoverlay.c \
|
||||||
|
gststreamsynchronizer.c
|
||||||
|
|
||||||
nodist_libgstplaybin_la_SOURCES = $(built_sources)
|
nodist_libgstplaybin_la_SOURCES = $(built_sources)
|
||||||
libgstplaybin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
libgstplaybin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||||
|
@ -63,7 +64,8 @@ noinst_HEADERS = \
|
||||||
gstscreenshot.h \
|
gstscreenshot.h \
|
||||||
gststreamselector.h \
|
gststreamselector.h \
|
||||||
gstrawcaps.h \
|
gstrawcaps.h \
|
||||||
gstsubtitleoverlay.h
|
gstsubtitleoverlay.h \
|
||||||
|
gststreamsynchronizer.h
|
||||||
|
|
||||||
noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7
|
noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
#include "gstplaysink.h"
|
#include "gstplaysink.h"
|
||||||
#include "gstscreenshot.h"
|
#include "gstscreenshot.h"
|
||||||
|
#include "gststreamsynchronizer.h"
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
|
GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
|
||||||
#define GST_CAT_DEFAULT gst_play_sink_debug
|
#define GST_CAT_DEFAULT gst_play_sink_debug
|
||||||
|
@ -136,6 +137,8 @@ struct _GstPlaySink
|
||||||
|
|
||||||
GstPlayFlags flags;
|
GstPlayFlags flags;
|
||||||
|
|
||||||
|
GstStreamSynchronizer *stream_synchronizer;
|
||||||
|
|
||||||
/* chains */
|
/* chains */
|
||||||
GstPlayAudioChain *audiochain;
|
GstPlayAudioChain *audiochain;
|
||||||
GstPlayVideoDeinterlaceChain *videodeinterlacechain;
|
GstPlayVideoDeinterlaceChain *videodeinterlacechain;
|
||||||
|
@ -146,6 +149,8 @@ struct _GstPlaySink
|
||||||
/* audio */
|
/* audio */
|
||||||
GstPad *audio_pad;
|
GstPad *audio_pad;
|
||||||
gboolean audio_pad_raw;
|
gboolean audio_pad_raw;
|
||||||
|
GstPad *audio_srcpad_stream_synchronizer;
|
||||||
|
GstPad *audio_sinkpad_stream_synchronizer;
|
||||||
/* audio tee */
|
/* audio tee */
|
||||||
GstElement *audio_tee;
|
GstElement *audio_tee;
|
||||||
GstPad *audio_tee_sink;
|
GstPad *audio_tee_sink;
|
||||||
|
@ -154,8 +159,12 @@ struct _GstPlaySink
|
||||||
/* video */
|
/* video */
|
||||||
GstPad *video_pad;
|
GstPad *video_pad;
|
||||||
gboolean video_pad_raw;
|
gboolean video_pad_raw;
|
||||||
|
GstPad *video_srcpad_stream_synchronizer;
|
||||||
|
GstPad *video_sinkpad_stream_synchronizer;
|
||||||
/* text */
|
/* text */
|
||||||
GstPad *text_pad;
|
GstPad *text_pad;
|
||||||
|
GstPad *text_srcpad_stream_synchronizer;
|
||||||
|
GstPad *text_sinkpad_stream_synchronizer;
|
||||||
|
|
||||||
/* properties */
|
/* properties */
|
||||||
GstElement *audio_sink;
|
GstElement *audio_sink;
|
||||||
|
@ -412,6 +421,11 @@ gst_play_sink_init (GstPlaySink * playsink)
|
||||||
playsink->subtitle_encoding = NULL;
|
playsink->subtitle_encoding = NULL;
|
||||||
playsink->flags = DEFAULT_FLAGS;
|
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);
|
g_static_rec_mutex_init (&playsink->lock);
|
||||||
GST_OBJECT_FLAG_SET (playsink, GST_ELEMENT_IS_SINK);
|
GST_OBJECT_FLAG_SET (playsink, GST_ELEMENT_IS_SINK);
|
||||||
}
|
}
|
||||||
|
@ -503,6 +517,8 @@ gst_play_sink_dispose (GObject * object)
|
||||||
g_free (playsink->subtitle_encoding);
|
g_free (playsink->subtitle_encoding);
|
||||||
playsink->subtitle_encoding = NULL;
|
playsink->subtitle_encoding = NULL;
|
||||||
|
|
||||||
|
playsink->stream_synchronizer = NULL;
|
||||||
|
|
||||||
G_OBJECT_CLASS (gst_play_sink_parent_class)->dispose (object);
|
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",
|
GST_DEBUG_OBJECT (playsink, "adding video, raw %d",
|
||||||
playsink->video_pad_raw);
|
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)
|
if (!playsink->videodeinterlacechain)
|
||||||
playsink->videodeinterlacechain =
|
playsink->videodeinterlacechain =
|
||||||
gen_video_deinterlace_chain (playsink);
|
gen_video_deinterlace_chain (playsink);
|
||||||
|
@ -2116,25 +2174,18 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
|
|
||||||
add_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), TRUE);
|
add_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), TRUE);
|
||||||
activate_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);
|
playsink->videodeinterlacechain->sinkpad);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
if (playsink->videodeinterlacechain) {
|
||||||
if (playsink->videochain) {
|
add_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain), FALSE);
|
||||||
/* try to reactivate the chain */
|
activate_chain (GST_PLAY_CHAIN (playsink->videodeinterlacechain),
|
||||||
if (!setup_video_chain (playsink, raw, async, queue)) {
|
FALSE);
|
||||||
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->videochain) {
|
if (playsink->videochain) {
|
||||||
GST_DEBUG_OBJECT (playsink, "adding video chain");
|
GST_DEBUG_OBJECT (playsink, "adding video chain");
|
||||||
add_chain (GST_PLAY_CHAIN (playsink->videochain), TRUE);
|
add_chain (GST_PLAY_CHAIN (playsink->videochain), TRUE);
|
||||||
|
@ -2147,7 +2198,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
gst_pad_link (playsink->videodeinterlacechain->srcpad,
|
gst_pad_link (playsink->videodeinterlacechain->srcpad,
|
||||||
playsink->videochain->sinkpad);
|
playsink->videochain->sinkpad);
|
||||||
else
|
else
|
||||||
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
|
gst_pad_link (playsink->video_srcpad_stream_synchronizer,
|
||||||
playsink->videochain->sinkpad);
|
playsink->videochain->sinkpad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2172,6 +2223,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
gst_element_get_static_pad (playsink->vischain->chain.bin, "src");
|
gst_element_get_static_pad (playsink->vischain->chain.bin, "src");
|
||||||
gst_pad_unlink (srcpad, playsink->videochain->sinkpad);
|
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);
|
add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
|
||||||
activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
|
activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
|
||||||
playsink->videochain->ts_offset = NULL;
|
playsink->videochain->ts_offset = NULL;
|
||||||
|
@ -2215,6 +2277,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
gst_object_unref (playsink->audio_tee_asrc);
|
gst_object_unref (playsink->audio_tee_asrc);
|
||||||
playsink->audio_tee_asrc = NULL;
|
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);
|
add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
|
||||||
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
|
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
|
||||||
disconnect_chain (playsink->audiochain, playsink);
|
disconnect_chain (playsink->audiochain, playsink);
|
||||||
|
@ -2232,6 +2305,21 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
playsink->audiochain = gen_audio_chain (playsink, raw, queue);
|
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) {
|
if (playsink->audiochain) {
|
||||||
GST_DEBUG_OBJECT (playsink, "adding audio chain");
|
GST_DEBUG_OBJECT (playsink, "adding audio chain");
|
||||||
if (playsink->audio_tee_asrc == NULL) {
|
if (playsink->audio_tee_asrc == NULL) {
|
||||||
|
@ -2240,7 +2328,10 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
}
|
}
|
||||||
add_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
|
add_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
|
||||||
activate_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 {
|
} else {
|
||||||
GST_DEBUG_OBJECT (playsink, "no audio needed");
|
GST_DEBUG_OBJECT (playsink, "no audio needed");
|
||||||
|
@ -2254,6 +2345,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
gst_object_unref (playsink->audio_tee_asrc);
|
gst_object_unref (playsink->audio_tee_asrc);
|
||||||
playsink->audio_tee_asrc = NULL;
|
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) {
|
if (playsink->audiochain->sink_volume) {
|
||||||
disconnect_chain (playsink->audiochain, playsink);
|
disconnect_chain (playsink->audiochain, playsink);
|
||||||
playsink->audiochain->volume = NULL;
|
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_element_get_request_pad (playsink->audio_tee, "src%d");
|
||||||
}
|
}
|
||||||
gst_pad_link (playsink->audio_tee_vissrc, playsink->vischain->sinkpad);
|
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);
|
gst_object_unref (srcpad);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2309,13 +2413,28 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
playsink->textchain = gen_text_chain (playsink);
|
playsink->textchain = gen_text_chain (playsink);
|
||||||
}
|
}
|
||||||
if (playsink->textchain) {
|
if (playsink->textchain) {
|
||||||
|
GstIterator *it;
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (playsink, "adding text chain");
|
GST_DEBUG_OBJECT (playsink, "adding text chain");
|
||||||
if (playsink->textchain->overlay)
|
if (playsink->textchain->overlay)
|
||||||
g_object_set (G_OBJECT (playsink->textchain->overlay), "silent", FALSE,
|
g_object_set (G_OBJECT (playsink->textchain->overlay), "silent", FALSE,
|
||||||
NULL);
|
NULL);
|
||||||
add_chain (GST_PLAY_CHAIN (playsink->textchain), TRUE);
|
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),
|
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);
|
playsink->textchain->textsinkpad);
|
||||||
|
|
||||||
if (need_vis) {
|
if (need_vis) {
|
||||||
|
@ -2331,7 +2450,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
gst_pad_link (playsink->videodeinterlacechain->srcpad,
|
gst_pad_link (playsink->videodeinterlacechain->srcpad,
|
||||||
playsink->textchain->videosinkpad);
|
playsink->textchain->videosinkpad);
|
||||||
else
|
else
|
||||||
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
|
gst_pad_link (playsink->video_srcpad_stream_synchronizer,
|
||||||
playsink->textchain->videosinkpad);
|
playsink->textchain->videosinkpad);
|
||||||
}
|
}
|
||||||
gst_pad_link (playsink->textchain->srcpad, playsink->videochain->sinkpad);
|
gst_pad_link (playsink->textchain->srcpad, playsink->videochain->sinkpad);
|
||||||
|
@ -2341,6 +2460,17 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
} else {
|
} else {
|
||||||
GST_DEBUG_OBJECT (playsink, "no text needed");
|
GST_DEBUG_OBJECT (playsink, "no text needed");
|
||||||
/* we have no subtitles/text or we are requested to not show them */
|
/* 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->textchain) {
|
||||||
if (playsink->text_pad == NULL) {
|
if (playsink->text_pad == NULL) {
|
||||||
/* no text pad, remove the chain entirely */
|
/* no text pad, remove the chain entirely */
|
||||||
|
@ -2355,8 +2485,20 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
|
||||||
NULL);
|
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);
|
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (playsink->text_pad && !playsink->textchain)
|
if (playsink->text_pad && !playsink->textchain)
|
||||||
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->text_pad), NULL);
|
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);
|
do_async_start (playsink);
|
||||||
ret = GST_STATE_CHANGE_ASYNC;
|
ret = GST_STATE_CHANGE_ASYNC;
|
||||||
break;
|
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:
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||||
if (playsink->audiochain && playsink->audiochain->sink_volume) {
|
if (playsink->audiochain && playsink->audiochain->sink_volume) {
|
||||||
/* remove our links to the mute and volume elements when they were
|
/* remove our links to the mute and volume elements when they were
|
||||||
|
|
788
gst/playback/gststreamsynchronizer.c
Normal file
788
gst/playback/gststreamsynchronizer.c
Normal file
|
@ -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, ×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 <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);
|
||||||
|
}
|
66
gst/playback/gststreamsynchronizer.h
Normal file
66
gst/playback/gststreamsynchronizer.h
Normal file
|
@ -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__ */
|
|
@ -93,6 +93,27 @@ eos_cb (GstBus * bus, GstMessage * msg, GMainLoop * main_loop)
|
||||||
g_main_loop_quit (main_loop);
|
g_main_loop_quit (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
|
static void
|
||||||
about_to_finish_cb (GstElement * element, gchar * uri[])
|
about_to_finish_cb (GstElement * element, gchar * uri[])
|
||||||
{
|
{
|
||||||
|
@ -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::eos", G_CALLBACK (eos_cb), loop);
|
||||||
g_signal_connect (bus, "message::error", G_CALLBACK (error_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::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);
|
g_object_set (G_OBJECT (player), "uri", argv[1], NULL);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue