diff options
author | Thibault Saunier <tsaunier@gnome.org> | 2014-10-21 10:35:48 +0200 |
---|---|---|
committer | Thibault Saunier <tsaunier@gnome.org> | 2014-10-31 11:58:07 +0100 |
commit | 81a0ee66c1266ed5b514b77b9cd00858b1dbceee (patch) | |
tree | 0210770d4953083b45ebca792a5ca4340231f7ae | |
parent | a09caa3da235ef3b85f9c54256f7c991d3e8c3dc (diff) |
Import GNL from 978332e7c4c3bba1949421d28b492540ab471450 'Release 1.4.0'
-rw-r--r-- | gnl/Makefile.am | 46 | ||||
-rw-r--r-- | gnl/gnl.c | 60 | ||||
-rw-r--r-- | gnl/gnl.h | 36 | ||||
-rw-r--r-- | gnl/gnlcomposition.c | 3074 | ||||
-rw-r--r-- | gnl/gnlcomposition.h | 63 | ||||
-rw-r--r-- | gnl/gnlghostpad.c | 770 | ||||
-rw-r--r-- | gnl/gnlghostpad.h | 47 | ||||
-rw-r--r-- | gnl/gnlmarshal.list | 2 | ||||
-rw-r--r-- | gnl/gnlobject.c | 656 | ||||
-rw-r--r-- | gnl/gnlobject.h | 160 | ||||
-rw-r--r-- | gnl/gnloperation.c | 792 | ||||
-rw-r--r-- | gnl/gnloperation.h | 90 | ||||
-rw-r--r-- | gnl/gnlsource.c | 590 | ||||
-rw-r--r-- | gnl/gnlsource.h | 68 | ||||
-rw-r--r-- | gnl/gnltypes.h | 42 | ||||
-rw-r--r-- | gnl/gnlurisource.c | 166 | ||||
-rw-r--r-- | gnl/gnlurisource.h | 57 | ||||
-rw-r--r-- | tests/check/gnl/common.c | 348 | ||||
-rw-r--r-- | tests/check/gnl/common.h | 73 | ||||
-rw-r--r-- | tests/check/gnl/complex.c | 834 | ||||
-rw-r--r-- | tests/check/gnl/gnlcomposition.c | 560 | ||||
-rw-r--r-- | tests/check/gnl/gnloperation.c | 698 | ||||
-rw-r--r-- | tests/check/gnl/gnlsource.c | 220 | ||||
-rw-r--r-- | tests/check/gnl/seek.c | 764 | ||||
-rw-r--r-- | tests/check/gnl/simple.c | 791 |
25 files changed, 11007 insertions, 0 deletions
diff --git a/gnl/Makefile.am b/gnl/Makefile.am new file mode 100644 index 00000000..0c4876ea --- /dev/null +++ b/gnl/Makefile.am @@ -0,0 +1,46 @@ +plugin_LTLIBRARIES = libgnl.la + +libgnl_la_SOURCES = \ + gnl.c \ + gnlobject.c \ + gnlcomposition.c \ + gnlghostpad.c \ + gnloperation.c \ + gnlsource.c \ + gnlurisource.c +libgnl_la_CFLAGS = $(GST_CFLAGS) +libgnl_la_LIBADD = $(GST_LIBS) +libgnl_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgnl_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +gnl_headers = \ + gnl.h \ + gnlobject.h \ + gnlcomposition.h \ + gnltypes.h \ + gnlghostpad.h \ + gnloperation.h \ + gnlsource.h \ + gnltypes.h \ + gnlurisource.h + +DISTCLEANFILE = $(CLEANFILES) + +#files built on make all/check/instal +BUILT_SOURCES = $(built_header_configure) + +noinst_HEADERS = $(gnl_headers) $(built_header_configure) + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgnl -:SHARED libgnl \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgnl_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgnl_la_CFLAGS) \ + -:LDFLAGS $(libgnl_la_LDFLAGS) \ + $(libgnl_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-@GST_API_VERSION@' \ + > $@ diff --git a/gnl/gnl.c b/gnl/gnl.c new file mode 100644 index 00000000..5a986a1d --- /dev/null +++ b/gnl/gnl.c @@ -0,0 +1,60 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" + +struct _elements_entry +{ + const gchar *name; + GType (*type) (void); +}; + +static struct _elements_entry _elements[] = { + {"gnlsource", gnl_source_get_type}, + {"gnlcomposition", gnl_composition_get_type}, + {"gnloperation", gnl_operation_get_type}, + {"gnlurisource", gnl_urisource_get_type}, + {NULL, 0} +}; + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gint i = 0; + + for (; _elements[i].name; i++) + if (!(gst_element_register (plugin, + _elements[i].name, GST_RANK_NONE, (_elements[i].type) ()))) + return FALSE; + + gnl_init_ghostpad_category (); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + gnonlin, + "Standard elements for non-linear multimedia editing", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gnl/gnl.h b/gnl/gnl.h new file mode 100644 index 00000000..303f4b1d --- /dev/null +++ b/gnl/gnl.h @@ -0,0 +1,36 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GNL_H__ +#define __GNL_H__ + +#include <gst/gst.h> + +#include "gnltypes.h" + +#include "gnlobject.h" +#include "gnlghostpad.h" +#include "gnlsource.h" +#include "gnlcomposition.h" +#include "gnloperation.h" + +#include "gnlurisource.h" + +#endif /* __GST_H__ */ diff --git a/gnl/gnlcomposition.c b/gnl/gnlcomposition.c new file mode 100644 index 00000000..d9a08779 --- /dev/null +++ b/gnl/gnlcomposition.c @@ -0,0 +1,3074 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" + +/** + * SECTION:element-gnlcomposition + * + * A GnlComposition contains GnlObjects such as GnlSources and GnlOperations, + * and connects them dynamically to create a composition timeline. + */ + +static GstStaticPadTemplate gnl_composition_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gnlcomposition_debug); +#define GST_CAT_DEFAULT gnlcomposition_debug + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gnlcomposition_debug,"gnlcomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Composition"); +#define gnl_composition_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GnlComposition, gnl_composition, GNL_TYPE_OBJECT, + _do_init); + + +enum +{ + PROP_0, + PROP_DEACTIVATED_ELEMENTS_STATE, + PROP_LAST, +}; + +/* Properties from GnlObject */ +enum +{ + GNLOBJECT_PROP_START, + GNLOBJECT_PROP_STOP, + GNLOBJECT_PROP_DURATION, + GNLOBJECT_PROP_LAST +}; + +enum +{ + COMMIT_SIGNAL, + LAST_SIGNAL +}; + +typedef struct _GnlCompositionEntry GnlCompositionEntry; + +struct _GnlCompositionPrivate +{ + gboolean dispose_has_run; + + /* + Sorted List of GnlObjects , ThreadSafe + objects_start : sorted by start-time then priority + objects_stop : sorted by stop-time then priority + objects_hash : contains signal handlers id for controlled objects + objects_lock : mutex to acces/modify any of those lists/hashtable + */ + GList *objects_start; + GList *objects_stop; + GHashTable *objects_hash; + GMutex objects_lock; + + /* + thread-safe Seek handling. + flushing_lock : mutex to access flushing and pending_idle + flushing : + */ + GMutex flushing_lock; + gboolean flushing; + + /* source top-level ghostpad, probe and entry */ + GstPad *ghostpad; + gulong ghosteventprobe; + GnlCompositionEntry *toplevelentry; + + /* current stack, list of GnlObject* */ + GNode *current; + + /* List of GnlObject whose start/duration will be the same as the composition */ + GList *expandables; + + /* TRUE if the stack is valid. + * This is meant to prevent the top-level pad to be unblocked before the stack + * is fully done. Protected by OBJECTS_LOCK */ + gboolean stackvalid; + + /* + current segment seek start/stop time. + Reconstruct pipeline ONLY if seeking outside of those values + FIXME : segment_start isn't always the earliest time before which the + timeline doesn't need to be modified + */ + GstClockTime segment_start; + GstClockTime segment_stop; + + /* pending child seek */ + GstEvent *childseek; + + /* Seek segment handler */ + GstSegment *segment; + GstSegment *outside_segment; + + /* Next running base_time to set on outgoing segment */ + guint64 next_base_time; + + /* number of pads we are waiting to appear so be can do proper linking */ + guint waitingpads; + + /* + OUR sync_handler on the child_bus + We are called before gnl_object_sync_handler + */ + GstPadEventFunction gnl_event_pad_func; + gboolean send_stream_start; + + GThread *update_pipeline_thread; + GCond update_pipeline_cond; + GMutex update_pipeline_mutex; + + gboolean reset_time; + + gboolean running; + + GstState deactivated_elements_state; +}; + +static guint _signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *gnlobject_properties[GNLOBJECT_PROP_LAST]; +static GParamSpec *_properties[PROP_LAST]; + +#define OBJECT_IN_ACTIVE_SEGMENT(comp,element) \ + ((GNL_OBJECT_START(element) < comp->priv->segment_stop) && \ + (GNL_OBJECT_STOP(element) >= comp->priv->segment_start)) + +static void gnl_composition_dispose (GObject * object); +static void gnl_composition_finalize (GObject * object); +static void gnl_composition_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspsec); +static void gnl_composition_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspsec); +static void gnl_composition_reset (GnlComposition * comp); + +static gboolean gnl_composition_add_object (GstBin * bin, GstElement * element); + +static void gnl_composition_handle_message (GstBin * bin, GstMessage * message); + +static gboolean +gnl_composition_remove_object (GstBin * bin, GstElement * element); + +static GstStateChangeReturn +gnl_composition_change_state (GstElement * element, GstStateChange transition); + +static GstPad *get_src_pad (GstElement * element); +static GstPadProbeReturn pad_blocked (GstPad * pad, GstPadProbeInfo * info, + GnlComposition * comp); +static inline void gnl_composition_remove_ghostpad (GnlComposition * comp); + +static gboolean +seek_handling (GnlComposition * comp, gboolean initial, gboolean update); +static gint objects_start_compare (GnlObject * a, GnlObject * b); +static gint objects_stop_compare (GnlObject * a, GnlObject * b); +static GstClockTime get_current_position (GnlComposition * comp); + +static gboolean update_pipeline (GnlComposition * comp, + GstClockTime currenttime, gboolean initial, gboolean modify); +static void no_more_pads_object_cb (GstElement * element, + GnlComposition * comp); +static gboolean gnl_composition_commit_func (GnlObject * object, + gboolean recurse); +static void update_start_stop_duration (GnlComposition * comp); + + +/* COMP_REAL_START: actual position to start current playback at. */ +#define COMP_REAL_START(comp) \ + (MAX (comp->priv->segment->start, GNL_OBJECT_START (comp))) + +#define COMP_REAL_STOP(comp) \ + (GST_CLOCK_TIME_IS_VALID (comp->priv->segment->stop) ? \ + (MIN (comp->priv->segment->stop, GNL_OBJECT_STOP (comp))) : \ + GNL_OBJECT_STOP (comp)) + +#define COMP_ENTRY(comp, object) \ + (g_hash_table_lookup (comp->priv->objects_hash, (gconstpointer) object)) + +#define COMP_OBJECTS_LOCK(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "locking objects_lock from thread %p", \ + g_thread_self()); \ + g_mutex_lock (&comp->priv->objects_lock); \ + GST_LOG_OBJECT (comp, "locked objects_lock from thread %p", \ + g_thread_self()); \ + } G_STMT_END + +#define COMP_OBJECTS_UNLOCK(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "unlocking objects_lock from thread %p", \ + g_thread_self()); \ + g_mutex_unlock (&comp->priv->objects_lock); \ + } G_STMT_END + + +#define COMP_FLUSHING_LOCK(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "locking flushing_lock from thread %p", \ + g_thread_self()); \ + g_mutex_lock (&comp->priv->flushing_lock); \ + GST_LOG_OBJECT (comp, "locked flushing_lock from thread %p", \ + g_thread_self()); \ + } G_STMT_END + +#define COMP_FLUSHING_UNLOCK(comp) G_STMT_START { \ + GST_LOG_OBJECT (comp, "unlocking flushing_lock from thread %p", \ + g_thread_self()); \ + g_mutex_unlock (&comp->priv->flushing_lock); \ + } G_STMT_END + +#define WAIT_FOR_UPDATE_PIPELINE(comp) G_STMT_START { \ + GST_INFO_OBJECT (comp, "waiting for EOS from thread %p", \ + g_thread_self()); \ + g_mutex_lock(&(comp->priv->update_pipeline_mutex)); \ + g_cond_wait(&(comp->priv->update_pipeline_cond), \ + &(comp->priv->update_pipeline_mutex)); \ + g_mutex_unlock(&(comp->priv->update_pipeline_mutex)); \ + } G_STMT_END + +#define SIGNAL_UPDATE_PIPELINE(comp) { \ + GST_INFO_OBJECT (comp, "signaling EOS from thread %p", \ + g_thread_self()); \ + g_mutex_lock(&(comp->priv->update_pipeline_mutex)); \ + g_cond_signal(&(comp->priv->update_pipeline_cond)); \ + g_mutex_unlock(&(comp->priv->update_pipeline_mutex)); \ + } G_STMT_END + + + +struct _GnlCompositionEntry +{ + GnlObject *object; + GnlComposition *comp; + + /* handler id for 'no-more-pads' signal */ + gulong nomorepadshandler; + gulong padaddedhandler; + gulong padremovedhandler; + + /* handler id for block probe */ + gulong probeid; + gulong dataprobeid; + + gboolean seeked; +}; + +static void +gnl_composition_class_init (GnlCompositionClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + GnlObjectClass *gnlobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbin_class = (GstBinClass *) klass; + gnlobject_class = (GnlObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GnlCompositionPrivate)); + + gst_element_class_set_static_metadata (gstelement_class, + "GNonLin Composition", "Filter/Editor", "Combines GNL objects", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>"); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_composition_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gnl_composition_finalize); + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gnl_composition_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gnl_composition_get_property); + + gstelement_class->change_state = gnl_composition_change_state; + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_composition_add_object); + gstbin_class->remove_element = + GST_DEBUG_FUNCPTR (gnl_composition_remove_object); + gstbin_class->handle_message = + GST_DEBUG_FUNCPTR (gnl_composition_handle_message); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gnl_composition_src_template)); + + /* Get the paramspec of the GnlObject klass so we can do + * fast notifies */ + gnlobject_properties[GNLOBJECT_PROP_START] = + g_object_class_find_property (gobject_class, "start"); + gnlobject_properties[GNLOBJECT_PROP_STOP] = + g_object_class_find_property (gobject_class, "stop"); + gnlobject_properties[GNLOBJECT_PROP_DURATION] = + g_object_class_find_property (gobject_class, "duration"); + + /** + * GnlComposition:deactivated-elements-state + * + * Get or set the #GstState in which elements that are not used + * in the currently configured pipeline should be set. + * By default the state is GST_STATE_READY to lower memory usage and avoid + * using all the avalaible threads from the kernel but that means that in + * certain case gapless will be more 'complicated' than if the state was set + * to GST_STATE_PAUSED. + */ + _properties[PROP_DEACTIVATED_ELEMENTS_STATE] = + g_param_spec_enum ("deactivated-elements-state", + "Deactivate elements state", "The state in which elements" + " not used in the currently configured pipeline should" + " be set", GST_TYPE_STATE, GST_STATE_READY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, _properties); + + /** + * GnlComposition::commit + * @comp: a #GnlComposition + * @recurse: Whether to commit recursiverly into (GnlComposition) children of + * @object. This is used in case we have composition inside + * a gnlsource composition, telling it to commit the included + * composition state. + * + * Action signal to commit all the pending changes of the composition and + * its children timing properties + * + * Returns: %TRUE if changes have been commited, %FALSE if nothing had to + * be commited + */ + _signals[COMMIT_SIGNAL] = g_signal_new ("commit", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GnlObjectClass, commit_signal_handler), NULL, NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN); + + gnlobject_class->commit = gnl_composition_commit_func; +} + +static void +hash_value_destroy (GnlCompositionEntry * entry) +{ + GstPad *srcpad; + GstElement *element = GST_ELEMENT (entry->object); + + g_signal_handler_disconnect (entry->object, entry->padremovedhandler); + g_signal_handler_disconnect (entry->object, entry->padaddedhandler); + + if (entry->nomorepadshandler) + g_signal_handler_disconnect (entry->object, entry->nomorepadshandler); + + if ((srcpad = get_src_pad (element))) { + if (entry->probeid) { + gst_pad_remove_probe (srcpad, entry->probeid); + entry->probeid = 0; + } + + if (entry->dataprobeid) { + gst_pad_remove_probe (srcpad, entry->dataprobeid); + entry->dataprobeid = 0; + } + gst_object_unref (srcpad); + } + + g_slice_free (GnlCompositionEntry, entry); +} + +static void +gnl_composition_init (GnlComposition * comp) +{ + GnlCompositionPrivate *priv; + + GST_OBJECT_FLAG_SET (comp, GNL_OBJECT_SOURCE); + GST_OBJECT_FLAG_SET (comp, GNL_OBJECT_COMPOSITION); + + priv = G_TYPE_INSTANCE_GET_PRIVATE (comp, GNL_TYPE_COMPOSITION, + GnlCompositionPrivate); + g_mutex_init (&priv->objects_lock); + priv->objects_start = NULL; + priv->objects_stop = NULL; + + g_mutex_init (&priv->flushing_lock); + priv->flushing = FALSE; + + priv->segment = gst_segment_new (); + priv->outside_segment = gst_segment_new (); + + priv->waitingpads = 0; + + priv->reset_time = FALSE; + + priv->objects_hash = g_hash_table_new_full + (g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) hash_value_destroy); + + priv->deactivated_elements_state = GST_STATE_READY; + + comp->priv = priv; + + gnl_composition_reset (comp); +} + +static void +gnl_composition_dispose (GObject * object) +{ + GnlComposition *comp = GNL_COMPOSITION (object); + GnlCompositionPrivate *priv = comp->priv; + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + if (priv->ghostpad) + gnl_composition_remove_ghostpad (comp); + + if (priv->childseek) { + gst_event_unref (priv->childseek); + priv->childseek = NULL; + } + + if (priv->current) { + g_node_destroy (priv->current); + priv->current = NULL; + } + + if (priv->expandables) { + g_list_free (priv->expandables); + priv->expandables = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gnl_composition_finalize (GObject * object) +{ + GnlComposition *comp = GNL_COMPOSITION (object); + GnlCompositionPrivate *priv = comp->priv; + + GST_INFO ("finalize"); + + COMP_OBJECTS_LOCK (comp); + g_list_free (priv->objects_start); + g_list_free (priv->objects_stop); + if (priv->current) + g_node_destroy (priv->current); + g_hash_table_destroy (priv->objects_hash); + COMP_OBJECTS_UNLOCK (comp); + + gst_segment_free (priv->segment); + gst_segment_free (priv->outside_segment); + + g_mutex_clear (&priv->objects_lock); + g_mutex_clear (&priv->flushing_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gnl_composition_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GnlComposition *comp = GNL_COMPOSITION (object); + + switch (prop_id) { + case PROP_DEACTIVATED_ELEMENTS_STATE: + comp->priv->deactivated_elements_state = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gnl_composition_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GnlComposition *comp = GNL_COMPOSITION (object); + + switch (prop_id) { + case PROP_DEACTIVATED_ELEMENTS_STATE: + g_value_set_enum (value, comp->priv->deactivated_elements_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +wait_no_more_pads (GnlComposition * comp, gpointer object, + GnlCompositionEntry * entry, gboolean wait) +{ + if (wait) { + GST_INFO_OBJECT (object, "no existing pad, connecting to 'no-more-pads'"); + entry->nomorepadshandler = g_signal_connect + (G_OBJECT (object), "no-more-pads", + G_CALLBACK (no_more_pads_object_cb), comp); + comp->priv->waitingpads++; + } else { + GST_INFO_OBJECT (object, "disconnecting from 'no-more-pads'"); + g_signal_handler_disconnect (object, entry->nomorepadshandler); + entry->nomorepadshandler = 0; + comp->priv->waitingpads--; + } + + GST_INFO_OBJECT (comp, "the number of waiting pads is now %d", + comp->priv->waitingpads); +} + +/* signal_duration_change + * Creates a new GST_MESSAGE_DURATION_CHANGED with the currently configured + * composition duration and sends that on the bus. + */ + +static inline void +signal_duration_change (GnlComposition * comp) +{ + gst_element_post_message (GST_ELEMENT_CAST (comp), + gst_message_new_duration_changed (GST_OBJECT_CAST (comp))); +} + +static gboolean +unblock_child_pads (GValue * item, GValue * ret G_GNUC_UNUSED, + GnlComposition * comp) +{ + GstPad *pad; + GstElement *child = g_value_get_object (item); + GnlCompositionEntry *entry = COMP_ENTRY (comp, child); + + GST_DEBUG_OBJECT (child, "unblocking pads"); + + pad = get_src_pad (child); + if (pad) { + if (entry->probeid) { + gst_pad_remove_probe (pad, entry->probeid); + entry->probeid = 0; + } + gst_object_unref (pad); + } + return TRUE; +} + +static void +unblock_children (GnlComposition * comp) +{ + GstIterator *children; + + children = gst_bin_iterate_elements (GST_BIN (comp)); + +retry: + if (G_UNLIKELY (gst_iterator_fold (children, + (GstIteratorFoldFunction) unblock_child_pads, NULL, + comp) == GST_ITERATOR_RESYNC)) { + gst_iterator_resync (children); + goto retry; + } + gst_iterator_free (children); +} + + +static gboolean +reset_child (GValue * item, GValue * ret G_GNUC_UNUSED, gpointer user_data) +{ + GnlCompositionEntry *entry; + GstElement *child = g_value_get_object (item); + GnlComposition *comp = GNL_COMPOSITION (user_data); + + GST_DEBUG_OBJECT (child, "unlocking state"); + gst_element_set_locked_state (child, FALSE); + + entry = COMP_ENTRY (comp, child); + if (entry->nomorepadshandler) + wait_no_more_pads (comp, child, entry, FALSE); + + return TRUE; +} + +static gboolean +lock_child_state (GValue * item, GValue * ret G_GNUC_UNUSED, + gpointer udata G_GNUC_UNUSED) +{ + GstElement *child = g_value_get_object (item); + + GST_DEBUG_OBJECT (child, "locking state"); + gst_element_set_locked_state (child, TRUE); + + return TRUE; +} + +static void +reset_children (GnlComposition * comp) +{ + GstIterator *children; + + children = gst_bin_iterate_elements (GST_BIN (comp)); +retry: + if (G_UNLIKELY (gst_iterator_fold (children, + (GstIteratorFoldFunction) reset_child, NULL, + comp) == GST_ITERATOR_RESYNC)) { + gst_iterator_resync (children); + goto retry; + } + gst_iterator_free (children); +} + +static void +gnl_composition_reset (GnlComposition * comp) +{ + GnlCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "resetting"); + + priv->segment_start = GST_CLOCK_TIME_NONE; + priv->segment_stop = GST_CLOCK_TIME_NONE; + priv->next_base_time = 0; + + gst_segment_init (priv->segment, GST_FORMAT_TIME); + gst_segment_init (priv->outside_segment, GST_FORMAT_TIME); + + if (priv->current) + g_node_destroy (priv->current); + priv->current = NULL; + + priv->stackvalid = FALSE; + + if (priv->ghostpad) + gnl_composition_remove_ghostpad (comp); + + if (priv->childseek) { + gst_event_unref (priv->childseek); + priv->childseek = NULL; + } + + reset_children (comp); + + COMP_FLUSHING_LOCK (comp); + + priv->flushing = FALSE; + + COMP_FLUSHING_UNLOCK (comp); + + priv->reset_time = FALSE; + + priv->send_stream_start = TRUE; + + GST_DEBUG_OBJECT (comp, "Composition now resetted"); +} + +static GstPadProbeReturn +ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED, + GstPadProbeInfo * info, GnlComposition * comp) +{ + GstPadProbeReturn retval = GST_PAD_PROBE_OK; + GnlCompositionPrivate *priv = comp->priv; + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GList *tmp; + + GST_DEBUG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + GST_DEBUG_OBJECT (comp, + "replacing flush stop event with a flush stop event with 'reset_time' = %d", + priv->reset_time); + GST_PAD_PROBE_INFO_DATA (info) = + gst_event_new_flush_stop (priv->reset_time); + gst_event_unref (event); + break; + case GST_EVENT_STREAM_START: + if (g_atomic_int_compare_and_exchange (&priv->send_stream_start, TRUE, + FALSE)) { + /* FIXME: Do we want to create a new stream ID here? */ + GST_DEBUG_OBJECT (comp, "forward stream-start %p", event); + } else { + GST_DEBUG_OBJECT (comp, "dropping stream-start %p", event); + retval = GST_PAD_PROBE_DROP; + } + break; + case GST_EVENT_SEGMENT: + { + guint64 rstart, rstop; + const GstSegment *segment; + GstSegment copy; + GstEvent *event2; + /* next_base_time */ + + COMP_FLUSHING_LOCK (comp); + + priv->flushing = FALSE; + COMP_FLUSHING_UNLOCK (comp); + + gst_event_parse_segment (event, &segment); + gst_segment_copy_into (segment, ©); + + rstart = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, + segment->start); + rstop = + gst_segment_to_running_time (segment, GST_FORMAT_TIME, segment->stop); + copy.base = comp->priv->next_base_time; + GST_DEBUG_OBJECT (comp, + "Updating base time to %" GST_TIME_FORMAT ", next:%" GST_TIME_FORMAT, + GST_TIME_ARGS (comp->priv->next_base_time), + GST_TIME_ARGS (comp->priv->next_base_time + rstop - rstart)); + comp->priv->next_base_time += rstop - rstart; + + event2 = gst_event_new_segment (©); + GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event); + GST_PAD_PROBE_INFO_DATA (info) = event2; + gst_event_unref (event); + } + break; + case GST_EVENT_EOS: + { + gboolean reverse = (comp->priv->segment->rate < 0); + gboolean should_check_objects = FALSE; + + COMP_FLUSHING_LOCK (comp); + if (priv->flushing) { + GST_DEBUG_OBJECT (comp, "flushing, bailing out"); + COMP_FLUSHING_UNLOCK (comp); + retval = GST_PAD_PROBE_DROP; + break; + } + COMP_FLUSHING_UNLOCK (comp); + + if (reverse && GST_CLOCK_TIME_IS_VALID (comp->priv->segment_start)) + should_check_objects = TRUE; + else if (!reverse && GST_CLOCK_TIME_IS_VALID (comp->priv->segment_stop)) + should_check_objects = TRUE; + + if (should_check_objects) { + retval = GST_PAD_PROBE_OK; + COMP_OBJECTS_LOCK (comp); + for (tmp = comp->priv->objects_stop; tmp; tmp = g_list_next (tmp)) { + GnlObject *object = (GnlObject *) tmp->data; + + if (!GNL_IS_SOURCE (object)) + continue; + + if ((!reverse && comp->priv->segment_stop < object->stop) || + (reverse && comp->priv->segment_start > object->start)) { + retval = GST_PAD_PROBE_DROP; + break; + } + } + COMP_OBJECTS_UNLOCK (comp); + } + + if (retval == GST_PAD_PROBE_OK) { + GST_DEBUG_OBJECT (comp, "Got EOS for real, fowarding it"); + + return GST_PAD_PROBE_OK; + } + + SIGNAL_UPDATE_PIPELINE (comp); + + retval = GST_PAD_PROBE_DROP; + } + break; + default: + break; + } + + return retval; +} + + + +/* Warning : Don't take the objects lock in this method */ +static void +gnl_composition_handle_message (GstBin * bin, GstMessage * message) +{ + GnlComposition *comp = (GnlComposition *) bin; + gboolean dropit = FALSE; + + GST_DEBUG_OBJECT (comp, "message:%s from %s", + gst_message_type_get_name (GST_MESSAGE_TYPE (message)), + GST_MESSAGE_SRC (message) ? GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)) : + "UNKNOWN"); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + case GST_MESSAGE_WARNING: + { + /* FIXME / HACK + * There is a massive issue with reverse negotiation and dynamic pipelines. + * + * Since we're not waiting for the pads of the previous stack to block before + * re-switching, we might end up switching sources in the middle of a downstrea + * negotiation which will do reverse negotiation... with the new source (which + * is no longer the one that issues the request). That negotiation will fail + * and the original source will emit an ERROR message. + * + * In order to avoid those issues, we just ignore error messages from elements + * which aren't in the currently configured stack + */ + if (GST_MESSAGE_SRC (message) && GNL_IS_OBJECT (GST_MESSAGE_SRC (message)) + && !OBJECT_IN_ACTIVE_SEGMENT (comp, GST_MESSAGE_SRC (message))) { + GST_DEBUG_OBJECT (comp, + "HACK Dropping error message from object not in currently configured stack !"); + dropit = TRUE; + } + } + default: + break; + } + + if (dropit) + gst_message_unref (message); + else + GST_BIN_CLASS (parent_class)->handle_message (bin, message); +} + +static gint +priority_comp (GnlObject * a, GnlObject * b) +{ + if (a->priority < b->priority) + return -1; + + if (a->priority > b->priority) + return 1; + + return 0; +} + +static inline gboolean +have_to_update_pipeline (GnlComposition * comp) +{ + GnlCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, + "segment[%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT "] current[%" + GST_TIME_FORMAT "--%" GST_TIME_FORMAT "]", + GST_TIME_ARGS (priv->segment->start), + GST_TIME_ARGS (priv->segment->stop), + GST_TIME_ARGS (priv->segment_start), GST_TIME_ARGS (priv->segment_stop)); + + if (priv->segment->start < priv->segment_start) + return TRUE; + + if (priv->segment->start >= priv->segment_stop) + return TRUE; + + return FALSE; +} + +/* OBJECTS LOCK must be taken when calling this ! */ +static gboolean +update_pipeline_at_current_position (GnlComposition * comp) +{ + GstClockTime curpos; + + /* Get current position */ + if ((curpos = get_current_position (comp)) == GST_CLOCK_TIME_NONE) { + if (GST_CLOCK_TIME_IS_VALID (comp->priv->segment_start)) + curpos = comp->priv->segment->start = comp->priv->segment_start; + else + curpos = 0; + } + + update_start_stop_duration (comp); + + return update_pipeline (comp, curpos, TRUE, TRUE); +} + +static gboolean +gnl_composition_commit_func (GnlObject * object, gboolean recurse) +{ + GList *tmp; + gboolean commited = FALSE; + GnlComposition *comp = GNL_COMPOSITION (object); + GnlCompositionPrivate *priv = comp->priv; + + + GST_DEBUG_OBJECT (object, "Commiting state"); + COMP_OBJECTS_LOCK (comp); + for (tmp = priv->objects_start; tmp; tmp = tmp->next) { + if (gnl_object_commit (tmp->data, recurse)) + commited = TRUE; + } + + GST_DEBUG_OBJECT (object, "Linking up commit vmethod"); + if (commited == FALSE && + (GNL_OBJECT_CLASS (parent_class)->commit (object, recurse) == FALSE)) { + COMP_OBJECTS_UNLOCK (comp); + GST_DEBUG_OBJECT (object, "Nothing to commit, leaving"); + return FALSE; + } + + /* The topology of the composition might have changed, update the lists */ + priv->objects_start = g_list_sort + (priv->objects_start, (GCompareFunc) objects_start_compare); + priv->objects_stop = g_list_sort + (priv->objects_stop, (GCompareFunc) objects_stop_compare); + + /* And update the pipeline at current position if needed */ + update_pipeline_at_current_position (comp); + COMP_OBJECTS_UNLOCK (comp); + + GST_DEBUG_OBJECT (object, "Done commiting"); + return TRUE; +} + +/* + * get_new_seek_event: + * + * Returns a seek event for the currently configured segment + * and start/stop values + * + * The GstSegment and segment_start|stop must have been configured + * before calling this function. + */ +static GstEvent * +get_new_seek_event (GnlComposition * comp, gboolean initial, + gboolean updatestoponly) +{ + GstSeekFlags flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH; + gint64 start, stop; + GstSeekType starttype = GST_SEEK_TYPE_SET; + GnlCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "initial:%d", initial); + /* remove the seek flag */ + if (!initial) + flags |= (GstSeekFlags) priv->segment->flags; + + GST_DEBUG_OBJECT (comp, + "private->segment->start:%" GST_TIME_FORMAT " segment_start%" + GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->start), + GST_TIME_ARGS (priv->segment_start)); + + GST_DEBUG_OBJECT (comp, + "private->segment->stop:%" GST_TIME_FORMAT " segment_stop%" + GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->stop), + GST_TIME_ARGS (priv->segment_stop)); + + start = MAX (priv->segment->start, priv->segment_start); + stop = GST_CLOCK_TIME_IS_VALID (priv->segment->stop) + ? MIN (priv->segment->stop, priv->segment_stop) + : priv->segment_stop; + + if (updatestoponly) { + starttype = GST_SEEK_TYPE_NONE; + start = GST_CLOCK_TIME_NONE; + } + + GST_DEBUG_OBJECT (comp, + "Created new seek event. Flags:%d, start:%" GST_TIME_FORMAT ", stop:%" + GST_TIME_FORMAT ", rate:%lf", flags, GST_TIME_ARGS (start), + GST_TIME_ARGS (stop), priv->segment->rate); + + return gst_event_new_seek (priv->segment->rate, + priv->segment->format, flags, starttype, start, GST_SEEK_TYPE_SET, stop); +} + +/* OBJECTS LOCK must be taken when calling this ! */ +static GstClockTime +get_current_position (GnlComposition * comp) +{ + GstPad *pad; + GnlObject *obj; + GnlCompositionPrivate *priv = comp->priv; + gboolean res; + gint64 value = GST_CLOCK_TIME_NONE; + + /* 1. Try querying position downstream */ + if (priv->ghostpad) { + GstPad *peer = gst_pad_get_peer (priv->ghostpad); + + if (peer) { + res = gst_pad_query_position (peer, GST_FORMAT_TIME, &value); + gst_object_unref (peer); + + if (res) { + GST_LOG_OBJECT (comp, + "Successfully got downstream position %" GST_TIME_FORMAT, + GST_TIME_ARGS ((guint64) value)); + goto beach; + } + } + + GST_DEBUG_OBJECT (comp, "Downstream position query failed"); + + /* resetting format/value */ + value = GST_CLOCK_TIME_NONE; + } + + /* 2. If downstream fails , try within the current stack */ + if (!priv->current) { + GST_DEBUG_OBJECT (comp, "No current stack, can't send query"); + goto beach; + } + + obj = (GnlObject *) priv->current->data; + + if (!(pad = get_src_pad ((GstElement *) obj))) + goto beach; + + res = gst_pad_query_position (pad, GST_FORMAT_TIME, &value); + + if (G_UNLIKELY (res == FALSE)) { + GST_WARNING_OBJECT (comp, + "query failed or returned a format different from TIME"); + value = GST_CLOCK_TIME_NONE; + } else { + GST_LOG_OBJECT (comp, "Query returned %" GST_TIME_FORMAT, + GST_TIME_ARGS ((guint64) value)); + } + +beach: + return (guint64) value; +} + +static gboolean +update_base_time (GNode * node, GstClockTime * timestamp) +{ + if (GNL_IS_OPERATION (node->data)) + gnl_operation_update_base_time (GNL_OPERATION (node->data), *timestamp); + + return FALSE; +} + +/* WITH OBJECTS LOCK TAKEN */ +static void +update_operations_base_time (GnlComposition * comp, gboolean reverse) +{ + GstClockTime timestamp; + + if (reverse) + timestamp = comp->priv->segment->stop; + else + timestamp = comp->priv->segment->start; + + g_node_traverse (comp->priv->current, G_IN_ORDER, G_TRAVERSE_ALL, -1, + (GNodeTraverseFunc) update_base_time, ×tamp); +} + +/* + Figures out if pipeline needs updating. + Updates it and sends the seek event. + Sends flush events downstream if needed. + can be called by user_seek or segment_done + + initial : FIXME : ???? Always seems to be TRUE + update : TRUE from EOS, FALSE from seek +*/ + +static gboolean +seek_handling (GnlComposition * comp, gboolean initial, gboolean update) +{ + GST_DEBUG_OBJECT (comp, "initial:%d, update:%d", initial, update); + + COMP_FLUSHING_LOCK (comp); + GST_DEBUG_OBJECT (comp, "Setting flushing to TRUE"); + comp->priv->flushing = TRUE; + COMP_FLUSHING_UNLOCK (comp); + + COMP_OBJECTS_LOCK (comp); + if (update || have_to_update_pipeline (comp)) { + if (comp->priv->segment->rate >= 0.0) + update_pipeline (comp, comp->priv->segment->start, initial, !update); + else + update_pipeline (comp, comp->priv->segment->stop, initial, !update); + } else { + update_operations_base_time (comp, !(comp->priv->segment->rate >= 0.0)); + } + COMP_OBJECTS_UNLOCK (comp); + + return TRUE; +} + +static void +handle_seek_event (GnlComposition * comp, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + GnlCompositionPrivate *priv = comp->priv; + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + GST_DEBUG_OBJECT (comp, + "start:%" GST_TIME_FORMAT " -- stop:%" GST_TIME_FORMAT " flags:%d", + GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), flags); + + gst_segment_do_seek (priv->segment, + rate, format, flags, cur_type, cur, stop_type, stop, NULL); + gst_segment_do_seek (priv->outside_segment, + rate, format, flags, cur_type, cur, stop_type, stop, NULL); + + GST_DEBUG_OBJECT (comp, "Segment now has flags:%d", priv->segment->flags); + + /* crop the segment start/stop values */ + /* Only crop segment start value if we don't have a default object */ + if (priv->expandables == NULL) + priv->segment->start = MAX (priv->segment->start, GNL_OBJECT_START (comp)); + priv->segment->stop = MIN (priv->segment->stop, GNL_OBJECT_STOP (comp)); + + comp->priv->next_base_time = 0; + + seek_handling (comp, TRUE, FALSE); +} + +static gboolean +gnl_composition_event_handler (GstPad * ghostpad, GstObject * parent, + GstEvent * event) +{ + GnlComposition *comp = (GnlComposition *) parent; + GnlCompositionPrivate *priv = comp->priv; + gboolean res = TRUE; + + GST_DEBUG_OBJECT (comp, "event type:%s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + GstEvent *nevent; + + handle_seek_event (comp, event); + + /* the incoming event might not be quite correct, we get a new proper + * event to pass on to the children. */ + COMP_OBJECTS_LOCK (comp); + nevent = get_new_seek_event (comp, FALSE, FALSE); + COMP_OBJECTS_UNLOCK (comp); + gst_event_unref (event); + event = nevent; + priv->reset_time = TRUE; + break; + } + case GST_EVENT_QOS: + { + gdouble prop; + GstQOSType qostype; + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, &qostype, &prop, &diff, ×tamp); + + GST_INFO_OBJECT (comp, + "timestamp:%" GST_TIME_FORMAT " segment.start:%" GST_TIME_FORMAT + " segment.stop:%" GST_TIME_FORMAT " segment_start%" GST_TIME_FORMAT + " segment_stop:%" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (priv->outside_segment->start), + GST_TIME_ARGS (priv->outside_segment->stop), + GST_TIME_ARGS (priv->segment_start), + GST_TIME_ARGS (priv->segment_stop)); + + /* The problem with QoS events is the following: + * At each new internal segment (i.e. when we re-arrange our internal + * elements) we send flushing seeks to those elements (to properly + * configure their playback range) but don't let the FLUSH events get + * downstream. + * + * The problem is that the QoS running timestamps we receive from + * downstream will not have taken into account those flush. + * + * What we need to do is to translate to our internal running timestamps + * which for each configured segment starts at 0 for those elements. + * + * The generic algorithm for the incoming running timestamp translation + * is therefore: + * (original_seek_time : original seek position received from usptream) + * (current_segment_start : Start position of the currently configured + * timeline segment) + * + * difference = original_seek_time - current_segment_start + * new_qos_position = upstream_qos_position - difference + * + * The new_qos_position is only valid when: + * * it applies to the current segment (difference > 0) + * * The QoS difference + timestamp is greater than the difference + * + */ + + if (GST_CLOCK_TIME_IS_VALID (priv->outside_segment->start)) { + GstClockTimeDiff curdiff; + + /* We'll either create a new event or discard it */ + gst_event_unref (event); + + if (priv->segment->rate < 0.0) + curdiff = priv->outside_segment->stop - priv->segment_stop; + else + curdiff = priv->segment_start - priv->outside_segment->start; + GST_DEBUG ("curdiff %" GST_TIME_FORMAT, GST_TIME_ARGS (curdiff)); + if ((curdiff != 0) && ((timestamp < curdiff) + || (curdiff > timestamp + diff))) { + GST_DEBUG_OBJECT (comp, + "QoS event outside of current segment, discarding"); + /* The QoS timestamp is before the currently set-up pipeline */ + goto beach; + } + + /* Substract the amount of running time we've already outputted + * until the currently configured pipeline from the QoS timestamp.*/ + timestamp -= curdiff; + GST_INFO_OBJECT (comp, + "Creating new QoS event with timestamp %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp)); + event = gst_event_new_qos (qostype, prop, diff, timestamp); + } + break; + } + default: + break; + } + + if (res && priv->ghostpad) { + COMP_OBJECTS_LOCK (comp); + + /* If the timeline isn't entirely reconstructed, we silently ignore the + * event. In the case of seeks the pipeline will already be correctly + * configured at this point*/ + if (priv->waitingpads == 0) { + COMP_OBJECTS_UNLOCK (comp); + GST_DEBUG_OBJECT (comp, "About to call gnl_event_pad_func()"); + res = priv->gnl_event_pad_func (priv->ghostpad, parent, event); + priv->reset_time = FALSE; + GST_DEBUG_OBJECT (comp, "Done calling gnl_event_pad_func() %d", res); + } else { + COMP_OBJECTS_UNLOCK (comp); + gst_event_unref (event); + } + + } + +beach: + return res; +} + +static GstPadProbeReturn +pad_blocked (GstPad * pad, GstPadProbeInfo * info, GnlComposition * comp) +{ + GST_DEBUG_OBJECT (comp, "Pad : %s:%s", GST_DEBUG_PAD_NAME (pad)); + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn +drop_data (GstPad * pad, GstPadProbeInfo * info, GnlCompositionEntry * entry) +{ + /* When updating the pipeline, do not let data flowing */ + if (!GST_IS_EVENT (info->data)) { + GST_LOG_OBJECT (pad, "Dropping data while updating pipeline"); + return GST_PAD_PROBE_DROP; + } else { + GstEvent *event = GST_EVENT (info->data); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + entry->seeked = TRUE; + GST_DEBUG_OBJECT (pad, "Got SEEK event"); + } else if (entry->seeked == TRUE && + GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + entry->seeked = FALSE; + entry->dataprobeid = 0; + + GST_DEBUG_OBJECT (pad, "Already seeked and got segment," + " removing probe"); + return GST_PAD_PROBE_REMOVE; + } + } + + return GST_PAD_PROBE_OK; +} + +static inline void +gnl_composition_remove_ghostpad (GnlComposition * comp) +{ + GnlCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "Removing ghostpad"); + + if (priv->ghosteventprobe) { + GstPad *target; + + target = gst_ghost_pad_get_target ((GstGhostPad *) priv->ghostpad); + if (target) + gst_pad_remove_probe (target, priv->ghosteventprobe); + priv->ghosteventprobe = 0; + } + + gnl_object_remove_ghost_pad (GNL_OBJECT (comp), priv->ghostpad); + priv->ghostpad = NULL; + priv->toplevelentry = NULL; + priv->send_stream_start = TRUE; +} + +/* gnl_composition_ghost_pad_set_target: + * target: The target #GstPad. The refcount will be decremented (given to the ghostpad). + * entry: The GnlCompositionEntry to which the pad belongs + */ + +static void +gnl_composition_ghost_pad_set_target (GnlComposition * comp, GstPad * target, + GnlCompositionEntry * entry) +{ + GnlCompositionPrivate *priv = comp->priv; + gboolean hadghost = priv->ghostpad ? TRUE : FALSE; + + if (target) + GST_DEBUG_OBJECT (comp, "target:%s:%s , hadghost:%d", + GST_DEBUG_PAD_NAME (target), hadghost); + else + GST_DEBUG_OBJECT (comp, "Removing target, hadghost:%d", hadghost); + + if (!hadghost) { + /* Create new ghostpad */ + GstPad *ghostpad = + gnl_object_ghost_pad_no_target ((GnlObject *) comp, "src", GST_PAD_SRC); + + if (!priv->gnl_event_pad_func) { + GST_DEBUG_OBJECT (ghostpad, "About to replace event_pad_func"); + priv->gnl_event_pad_func = GST_PAD_EVENTFUNC (ghostpad); + } + + gst_pad_set_event_function (ghostpad, + GST_DEBUG_FUNCPTR (gnl_composition_event_handler)); + GST_DEBUG_OBJECT (ghostpad, "eventfunc is now %s", + GST_DEBUG_FUNCPTR_NAME (GST_PAD_EVENTFUNC (ghostpad))); + + priv->ghostpad = ghostpad; + } else { + GstPad *ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (priv->ghostpad)); + + if (ptarget && ptarget == target) { + GST_DEBUG_OBJECT (comp, + "Target of ghostpad is the same as existing one, not changing"); + gst_object_unref (ptarget); + return; + } + + /* Unset previous target */ + if (ptarget) { + GST_DEBUG_OBJECT (comp, "Previous target was %s:%s", + GST_DEBUG_PAD_NAME (ptarget)); + + if (!priv->toplevelentry->probeid) { + /* If it's not blocked, block it */ + priv->toplevelentry->probeid = + gst_pad_add_probe (ptarget, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) pad_blocked, comp, NULL); + } + + if (!priv->toplevelentry->dataprobeid) { + priv->toplevelentry->dataprobeid = gst_pad_add_probe (ptarget, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST | + GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, + priv->toplevelentry, NULL); + } + + /* remove event probe */ + if (priv->ghosteventprobe) { + gst_pad_remove_probe (ptarget, priv->ghosteventprobe); + priv->ghosteventprobe = 0; + } + gst_object_unref (ptarget); + + } + } + + /* Actually set the target */ + gnl_object_ghost_pad_set_target ((GnlObject *) comp, priv->ghostpad, target); + + /* Set top-level entry (will be NULL if unsetting) */ + priv->toplevelentry = entry; + + if (target && (priv->ghosteventprobe == 0)) { + priv->ghosteventprobe = + gst_pad_add_probe (target, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH, + (GstPadProbeCallback) ghost_event_probe_handler, comp, NULL); + GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe); + } + + if (!hadghost) { + gst_pad_set_active (priv->ghostpad, TRUE); + + COMP_OBJECTS_UNLOCK (comp); + if (!gst_element_add_pad (GST_ELEMENT (comp), priv->ghostpad)) + GST_WARNING ("Couldn't add the ghostpad"); + else + gst_element_no_more_pads (GST_ELEMENT (comp)); + COMP_OBJECTS_LOCK (comp); + } + + GST_DEBUG_OBJECT (comp, "END"); +} + +static void +refine_start_stop_in_region_above_priority (GnlComposition * composition, + GstClockTime timestamp, GstClockTime start, + GstClockTime stop, + GstClockTime * rstart, GstClockTime * rstop, guint32 priority) +{ + GList *tmp; + GnlObject *object; + GstClockTime nstart = start, nstop = stop; + + GST_DEBUG_OBJECT (composition, + "timestamp:%" GST_TIME_FORMAT " start: %" GST_TIME_FORMAT " stop: %" + GST_TIME_FORMAT " priority:%u", GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), priority); + + for (tmp = composition->priv->objects_start; tmp; tmp = tmp->next) { + object = (GnlObject *) tmp->data; + + GST_LOG_OBJECT (object, "START %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop)); + + if ((object->priority >= priority) || (!object->active)) + continue; + + if (object->start <= timestamp) + continue; + + if (object->start >= nstop) + continue; + + nstop = object->start; + + GST_DEBUG_OBJECT (composition, + "START Found %s [prio:%u] at %" GST_TIME_FORMAT, + GST_OBJECT_NAME (object), object->priority, + GST_TIME_ARGS (object->start)); + + break; + } + + for (tmp = composition->priv->objects_stop; tmp; tmp = tmp->next) { + object = (GnlObject *) tmp->data; + + GST_LOG_OBJECT (object, "STOP %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop)); + + if ((object->priority >= priority) || (!object->active)) + continue; + + if (object->stop >= timestamp) + continue; + + if (object->stop <= nstart) + continue; + + nstart = object->stop; + + GST_DEBUG_OBJECT (composition, + "STOP Found %s [prio:%u] at %" GST_TIME_FORMAT, + GST_OBJECT_NAME (object), object->priority, + GST_TIME_ARGS (object->start)); + + break; + } + + if (*rstart) + *rstart = nstart; + + if (*rstop) + *rstop = nstop; +} + + +/* + * Converts a sorted list to a tree + * Recursive + * + * stack will be set to the next item to use in the parent. + * If operations number of sinks is limited, it will only use that number. + */ + +static GNode * +convert_list_to_tree (GList ** stack, GstClockTime * start, + GstClockTime * stop, guint32 * highprio) +{ + GNode *ret; + guint nbsinks; + gboolean limit; + GList *tmp; + GnlObject *object; + + if (!stack || !*stack) + return NULL; + + object = (GnlObject *) (*stack)->data; + + GST_DEBUG ("object:%s , *start:%" GST_TIME_FORMAT ", *stop:%" + GST_TIME_FORMAT " highprio:%d", + GST_ELEMENT_NAME (object), GST_TIME_ARGS (*start), + GST_TIME_ARGS (*stop), *highprio); + + /* update earliest stop */ + if (GST_CLOCK_TIME_IS_VALID (*stop)) { + if (GST_CLOCK_TIME_IS_VALID (object->stop) && (*stop > object->stop)) + *stop = object->stop; + } else { + *stop = object->stop; + } + + if (GST_CLOCK_TIME_IS_VALID (*start)) { + if (GST_CLOCK_TIME_IS_VALID (object->start) && (*start < object->start)) + *start = object->start; + } else { + *start = object->start; + } + + if (GNL_OBJECT_IS_SOURCE (object)) { + *stack = g_list_next (*stack); + + /* update highest priority. + * We do this here, since it's only used with sources (leafs of the tree) */ + if (object->priority > *highprio) + *highprio = object->priority; + + ret = g_node_new (object); + + goto beach; + } else { + /* GnlOperation */ + GnlOperation *oper = (GnlOperation *) object; + + GST_LOG_OBJECT (oper, "operation, num_sinks:%d", oper->num_sinks); + + ret = g_node_new (object); + limit = (oper->dynamicsinks == FALSE); + nbsinks = oper->num_sinks; + + /* FIXME : if num_sinks == -1 : request the proper number of pads */ + for (tmp = g_list_next (*stack); tmp && (!limit || nbsinks);) { + g_node_append (ret, convert_list_to_tree (&tmp, start, stop, highprio)); + if (limit) + nbsinks--; + } + + *stack = tmp; + } + +beach: + GST_DEBUG_OBJECT (object, + "*start:%" GST_TIME_FORMAT " *stop:%" GST_TIME_FORMAT + " priority:%u", GST_TIME_ARGS (*start), GST_TIME_ARGS (*stop), *highprio); + + return ret; +} + +/* + * get_stack_list: + * @comp: The #GnlComposition + * @timestamp: The #GstClockTime to look at + * @priority: The priority level to start looking from + * @activeonly: Only look for active elements if TRUE + * @start: The biggest start time of the objects in the stack + * @stop: The smallest stop time of the objects in the stack + * @highprio: The highest priority in the stack + * + * Not MT-safe, you should take the objects lock before calling it. + * Returns: A tree of #GNode sorted in priority order, corresponding + * to the given search arguments. The returned value can be #NULL. + * + * WITH OBJECTS LOCK TAKEN + */ +static GNode * +get_stack_list (GnlComposition * comp, GstClockTime timestamp, + guint32 priority, gboolean activeonly, GstClockTime * start, + GstClockTime * stop, guint * highprio) +{ + GList *tmp; + GList *stack = NULL; + GNode *ret = NULL; + GstClockTime nstart = GST_CLOCK_TIME_NONE; + GstClockTime nstop = GST_CLOCK_TIME_NONE; + GstClockTime first_out_of_stack = GST_CLOCK_TIME_NONE; + guint32 highest = 0; + gboolean reverse = (comp->priv->segment->rate < 0.0); + + GST_DEBUG_OBJECT (comp, + "timestamp:%" GST_TIME_FORMAT ", priority:%u, activeonly:%d", + GST_TIME_ARGS (timestamp), priority, activeonly); + + GST_LOG ("objects_start:%p objects_stop:%p", comp->priv->objects_start, + comp->priv->objects_stop); + + if (reverse) { + for (tmp = comp->priv->objects_stop; tmp; tmp = g_list_next (tmp)) { + GnlObject *object = (GnlObject *) tmp->data; + + GST_LOG_OBJECT (object, + "start: %" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT " , duration:%" + GST_TIME_FORMAT ", priority:%u, active:%d", + GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop), + GST_TIME_ARGS (object->duration), object->priority, object->active); + + if (object->stop >= timestamp) { + if ((object->start < timestamp) && + (object->priority >= priority) && + ((!activeonly) || (object->active))) { + GST_LOG_OBJECT (comp, "adding %s: sorted to the stack", + GST_OBJECT_NAME (object)); + stack = g_list_insert_sorted (stack, object, + (GCompareFunc) priority_comp); + if (GNL_IS_OPERATION (object)) + gnl_operation_update_base_time (GNL_OPERATION (object), timestamp); + } + } else { + GST_LOG_OBJECT (comp, "too far, stopping iteration"); + first_out_of_stack = object->stop; + break; + } + } + } else { + for (tmp = comp->priv->objects_start; tmp; tmp = g_list_next (tmp)) { + GnlObject *object = (GnlObject *) tmp->data; + + GST_LOG_OBJECT (object, + "start: %" GST_TIME_FORMAT " , stop:%" GST_TIME_FORMAT " , duration:%" + GST_TIME_FORMAT ", priority:%u", GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->duration), + object->priority); + + if (object->start <= timestamp) { + if ((object->stop > timestamp) && + (object->priority >= priority) && + ((!activeonly) || (object->active))) { + GST_LOG_OBJECT (comp, "adding %s: sorted to the stack", + GST_OBJECT_NAME (object)); + stack = g_list_insert_sorted (stack, object, + (GCompareFunc) priority_comp); + if (GNL_IS_OPERATION (object)) + gnl_operation_update_base_time (GNL_OPERATION (object), timestamp); + } + } else { + GST_LOG_OBJECT (comp, "too far, stopping iteration"); + first_out_of_stack = object->start; + break; + } + } + } + + /* Insert the expandables */ + if (G_LIKELY (timestamp < GNL_OBJECT_STOP (comp))) + for (tmp = comp->priv->expandables; tmp; tmp = tmp->next) { + GST_DEBUG_OBJECT (comp, "Adding expandable %s sorted to the list", + GST_OBJECT_NAME (tmp->data)); + stack = g_list_insert_sorted (stack, tmp->data, + (GCompareFunc) priority_comp); + if (GNL_IS_OPERATION (tmp->data)) + gnl_operation_update_base_time (GNL_OPERATION (tmp->data), timestamp); + } + + /* convert that list to a stack */ + tmp = stack; + ret = convert_list_to_tree (&tmp, &nstart, &nstop, &highest); + if (GST_CLOCK_TIME_IS_VALID (first_out_of_stack)) { + if (reverse && nstart < first_out_of_stack) + nstart = first_out_of_stack; + else if (!reverse && nstop > first_out_of_stack) + nstop = first_out_of_stack; + } + + GST_DEBUG ("nstart:%" GST_TIME_FORMAT ", nstop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (nstart), GST_TIME_ARGS (nstop)); + + if (*stop) + *stop = nstop; + if (*start) + *start = nstart; + if (highprio) + *highprio = highest; + + g_list_free (stack); + + return ret; +} + +/* + * get_clean_toplevel_stack: + * @comp: The #GnlComposition + * @timestamp: The #GstClockTime to look at + * @stop_time: Pointer to a #GstClockTime for min stop time of returned stack + * @start_time: Pointer to a #GstClockTime for greatest start time of returned stack + * + * Returns: The new current stack for the given #GnlComposition and @timestamp. + * + * WITH OBJECTS LOCK TAKEN + */ +static GNode * +get_clean_toplevel_stack (GnlComposition * comp, GstClockTime * timestamp, + GstClockTime * start_time, GstClockTime * stop_time) +{ + GNode *stack = NULL; + GstClockTime start = G_MAXUINT64; + GstClockTime stop = G_MAXUINT64; + guint highprio; + gboolean reverse = (comp->priv->segment->rate < 0.0); + + GST_DEBUG_OBJECT (comp, "timestamp:%" GST_TIME_FORMAT, + GST_TIME_ARGS (*timestamp)); + GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + stack = get_stack_list (comp, *timestamp, 0, TRUE, &start, &stop, &highprio); + + if (!stack && + ((reverse && (*timestamp > COMP_REAL_START (comp))) || + (!reverse && (*timestamp < COMP_REAL_STOP (comp))))) { + GST_ELEMENT_ERROR (comp, STREAM, WRONG_TYPE, + ("Gaps ( at %" GST_TIME_FORMAT + ") in the stream is not supported, the application is responsible" + " for filling them", GST_TIME_ARGS (*timestamp)), + ("Gap in the composition this should never" + "append, make sure to fill them")); + + return NULL; + } + + GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + if (stack) { + guint32 top_priority = GNL_OBJECT_PRIORITY (stack->data); + + /* Figure out if there's anything blocking us with smaller priority */ + refine_start_stop_in_region_above_priority (comp, *timestamp, start, + stop, &start, &stop, (highprio == 0) ? top_priority : highprio); + } + + if (*stop_time) { + if (stack) + *stop_time = stop; + else + *stop_time = 0; + } + + if (*start_time) { + if (stack) + *start_time = start; + else + *start_time = 0; + } + + GST_DEBUG_OBJECT (comp, + "Returning timestamp:%" GST_TIME_FORMAT " , start_time:%" + GST_TIME_FORMAT " , stop_time:%" GST_TIME_FORMAT, + GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (*start_time), + GST_TIME_ARGS (*stop_time)); + + return stack; +} + + +/* + * + * UTILITY FUNCTIONS + * + */ + +/* + * get_src_pad: + * element: a #GstElement + * + * Returns: The src pad for the given element. A reference was added to the + * returned pad, remove it when you don't need that pad anymore. + * Returns NULL if there's no source pad. + */ +static GstPad * +get_src_pad (GstElement * element) +{ + GstIterator *it; + GstIteratorResult itres; + GValue item = { 0, }; + GstPad *srcpad; + + it = gst_element_iterate_src_pads (element); + + itres = gst_iterator_next (it, &item); + if (itres != GST_ITERATOR_OK) { + GST_DEBUG ("%s doesn't have a src pad !", GST_ELEMENT_NAME (element)); + srcpad = NULL; + } else { + srcpad = g_value_get_object (&item); + gst_object_ref (srcpad); + g_value_reset (&item); + } + + gst_iterator_free (it); + + return srcpad; +} + + +/* + * + * END OF UTILITY FUNCTIONS + * + */ + +static gboolean +set_child_caps (GValue * item, GValue * ret G_GNUC_UNUSED, GnlObject * comp) +{ + GstElement *child = g_value_get_object (item); + + gnl_object_set_caps ((GnlObject *) child, comp->caps); + + return TRUE; +} + +static gpointer +update_pipeline_func (GnlComposition * comp) +{ + while (comp->priv->running) { + GnlCompositionPrivate *priv; + gboolean reverse; + + WAIT_FOR_UPDATE_PIPELINE (comp); + + /* Set up a non-initial seek on segment_stop */ + priv = comp->priv; + reverse = (priv->segment->rate < 0.0); + if (!reverse) { + GST_DEBUG_OBJECT (comp, + "Setting segment->start to segment_stop:%" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->segment_stop)); + priv->segment->start = priv->segment_stop; + } else { + GST_DEBUG_OBJECT (comp, + "Setting segment->stop to segment_start:%" GST_TIME_FORMAT, + GST_TIME_ARGS (priv->segment_start)); + priv->segment->stop = priv->segment_start; + } + + seek_handling (comp, TRUE, TRUE); + + if (!priv->current) { + /* If we're at the end, post SEGMENT_DONE, or push EOS */ + GST_DEBUG_OBJECT (comp, "Nothing else to play"); + + if (!(priv->segment->flags & GST_SEEK_FLAG_SEGMENT) + && priv->ghostpad) { + GST_DEBUG_OBJECT (comp, "Real EOS should be sent now"); + } else if (priv->segment->flags & GST_SEEK_FLAG_SEGMENT) { + gint64 epos; + + if (GST_CLOCK_TIME_IS_VALID (priv->segment->stop)) + epos = (MIN (priv->segment->stop, GNL_OBJECT_STOP (comp))); + else + epos = GNL_OBJECT_STOP (comp); + + GST_LOG_OBJECT (comp, "Emitting segment done pos %" GST_TIME_FORMAT, + GST_TIME_ARGS (epos)); + gst_element_post_message (GST_ELEMENT_CAST (comp), + gst_message_new_segment_done (GST_OBJECT (comp), + priv->segment->format, epos)); + gst_pad_push_event (priv->ghostpad, + gst_event_new_segment_done (priv->segment->format, epos)); + } + } + + } + + return NULL; +} + +static GstStateChangeReturn +gnl_composition_change_state (GstElement * element, GstStateChange transition) +{ + GnlComposition *comp = (GnlComposition *) element; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (comp, "%s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + comp->priv->running = TRUE; + comp->priv->update_pipeline_thread = + g_thread_new ("update_pipeline_thread", + (GThreadFunc) update_pipeline_func, comp); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + GstIterator *children; + + gnl_composition_reset (comp); + + /* state-lock all elements */ + GST_DEBUG_OBJECT (comp, + "Setting all children to READY and locking their state"); + + children = gst_bin_iterate_elements (GST_BIN (comp)); + + retry_lock: + if (G_UNLIKELY (gst_iterator_fold (children, + (GstIteratorFoldFunction) lock_child_state, NULL, + NULL) == GST_ITERATOR_RESYNC)) { + gst_iterator_resync (children); + goto retry_lock; + } + + gst_iterator_free (children); + + /* Set caps on all objects */ + if (G_UNLIKELY (!gst_caps_is_any (GNL_OBJECT (comp)->caps))) { + children = gst_bin_iterate_elements (GST_BIN (comp)); + + retry_caps: + if (G_UNLIKELY (gst_iterator_fold (children, + (GstIteratorFoldFunction) set_child_caps, NULL, + comp) == GST_ITERATOR_RESYNC)) { + gst_iterator_resync (children); + goto retry_caps; + } + gst_iterator_free (children); + } + + + /* set ghostpad target */ + COMP_OBJECTS_LOCK (comp); + if (!(update_pipeline (comp, COMP_REAL_START (comp), TRUE, TRUE))) { + ret = GST_STATE_CHANGE_FAILURE; + COMP_OBJECTS_UNLOCK (comp); + goto beach; + } + + COMP_OBJECTS_UNLOCK (comp); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gnl_composition_reset (comp); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gnl_composition_reset (comp); + comp->priv->running = FALSE; + SIGNAL_UPDATE_PIPELINE (comp); + g_thread_join (comp->priv->update_pipeline_thread); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + unblock_children (comp); + break; + default: + break; + } + +beach: + return ret; +} + +static gint +objects_start_compare (GnlObject * a, GnlObject * b) +{ + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; +} + +static gint +objects_stop_compare (GnlObject * a, GnlObject * b) +{ + if (a->stop == b->stop) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (b->stop < a->stop) + return -1; + if (b->stop > a->stop) + return 1; + return 0; +} + +/* WITH OBJECTS LOCK TAKEN */ +static void +update_start_stop_duration (GnlComposition * comp) +{ + GnlObject *obj; + GnlObject *cobj = (GnlObject *) comp; + GnlCompositionPrivate *priv = comp->priv; + + if (!priv->objects_start) { + GST_LOG ("no objects, resetting everything to 0"); + + if (cobj->start) { + cobj->start = cobj->pending_start = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_START]); + } + + if (cobj->duration) { + cobj->pending_duration = cobj->duration = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_DURATION]); + signal_duration_change (comp); + } + + if (cobj->stop) { + cobj->stop = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_STOP]); + } + + return; + } + + /* If we have a default object, the start position is 0 */ + if (priv->expandables) { + GST_LOG_OBJECT (cobj, + "Setting start to 0 because we have a default object"); + + if (cobj->start != 0) { + cobj->pending_start = cobj->start = 0; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_START]); + } + + } else { + + /* Else it's the first object's start value */ + obj = (GnlObject *) priv->objects_start->data; + + if (obj->start != cobj->start) { + GST_LOG_OBJECT (obj, "setting start from %s to %" GST_TIME_FORMAT, + GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->start)); + cobj->pending_start = cobj->start = obj->start; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_START]); + } + + } + + obj = (GnlObject *) priv->objects_stop->data; + + if (obj->stop != cobj->stop) { + GST_LOG_OBJECT (obj, "setting stop from %s to %" GST_TIME_FORMAT, + GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->stop)); + + if (priv->expandables) { + GList *tmp; + + GST_INFO_OBJECT (comp, "RE-setting all expandables duration and commit"); + for (tmp = priv->expandables; tmp; tmp = tmp->next) { + g_object_set (tmp->data, "duration", obj->stop, NULL); + gnl_object_commit (GNL_OBJECT (tmp->data), FALSE); + } + } + + priv->segment->stop = obj->stop; + cobj->stop = obj->stop; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_STOP]); + } + + if ((cobj->stop - cobj->start) != cobj->duration) { + cobj->pending_duration = cobj->duration = cobj->stop - cobj->start; + g_object_notify_by_pspec (G_OBJECT (cobj), + gnlobject_properties[GNLOBJECT_PROP_DURATION]); + signal_duration_change (comp); + } + + GST_LOG_OBJECT (comp, + "start:%" GST_TIME_FORMAT + " stop:%" GST_TIME_FORMAT + " duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (cobj->start), + GST_TIME_ARGS (cobj->stop), GST_TIME_ARGS (cobj->duration)); +} + +static void +no_more_pads_object_cb (GstElement * element, GnlComposition * comp) +{ + GnlCompositionPrivate *priv = comp->priv; + GnlObject *object = (GnlObject *) element; + GNode *tmp; + GstPad *pad = NULL; + GnlCompositionEntry *entry; + + GST_LOG_OBJECT (comp, "no more pads on element %s", + GST_ELEMENT_NAME (element)); + + if (!(pad = get_src_pad (element))) + goto no_source; + + COMP_OBJECTS_LOCK (comp); + + if (G_UNLIKELY (priv->current == NULL)) { + GST_DEBUG_OBJECT (comp, "current stack is empty !"); + goto done; + } + + tmp = g_node_find (priv->current, G_IN_ORDER, G_TRAVERSE_ALL, object); + + if (G_UNLIKELY (tmp == NULL)) + goto not_in_stack; + + entry = COMP_ENTRY (comp, object); + wait_no_more_pads (comp, object, entry, FALSE); + + if (tmp->parent) { + GstElement *parent = (GstElement *) tmp->parent->data; + GstPad *sinkpad; + + /* Get an unlinked sinkpad from the parent */ + sinkpad = get_unlinked_sink_ghost_pad ((GnlOperation *) parent); + + if (G_UNLIKELY (sinkpad == NULL)) { + GST_WARNING_OBJECT (comp, + "Couldn't find an unlinked sinkpad from %s", + GST_ELEMENT_NAME (parent)); + goto done; + } + + /* Link pad to parent sink pad */ + if (G_UNLIKELY (gst_pad_link_full (pad, sinkpad, + GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (comp, "Failed to link pads %s:%s - %s:%s", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (sinkpad)); + gst_object_unref (sinkpad); + goto done; + } + + /* inform operation of incoming stream priority */ + gnl_operation_signal_input_priority_changed ((GnlOperation *) parent, + sinkpad, object->priority); + gst_object_unref (sinkpad); + gst_pad_remove_probe (pad, entry->probeid); + entry->probeid = 0; + } + + /* If there are no more waiting pads, activate the current stack */ + if (priv->current && (priv->waitingpads == 0) + && priv->stackvalid) { + GnlCompositionEntry *topentry = COMP_ENTRY (comp, priv->current->data); + GstPad *tpad = NULL; + + /* There are no more waiting pads for the currently configured timeline */ + /* stack. */ + tpad = get_src_pad (GST_ELEMENT (priv->current->data)); + GST_LOG_OBJECT (comp, + "top-level pad %s:%s, Setting target of ghostpad to it", + GST_DEBUG_PAD_NAME (tpad)); + + /* 2. send pending seek */ + if (priv->childseek) { + GstEvent *childseek = priv->childseek; + + priv->childseek = NULL; + GST_INFO_OBJECT (comp, "Sending pending seek on %s:%s", + GST_DEBUG_PAD_NAME (tpad)); + + COMP_OBJECTS_UNLOCK (comp); + + if (!(gst_pad_send_event (tpad, childseek))) + GST_ERROR_OBJECT (comp, "Sending seek event failed!"); + + COMP_OBJECTS_LOCK (comp); + } + priv->childseek = NULL; + + /* 1. set target of ghostpad to toplevel element src pad */ + gnl_composition_ghost_pad_set_target (comp, tpad, topentry); + + /* Check again if the top-level element is still in the stack */ + if (priv->current && + g_node_find (priv->current, G_IN_ORDER, G_TRAVERSE_ALL, object)) { + + /* 3. unblock ghostpad */ + if (topentry->probeid) { + GST_LOG_OBJECT (comp, "About to unblock top-level pad : %s:%s", + GST_DEBUG_PAD_NAME (tpad)); + gst_pad_remove_probe (tpad, topentry->probeid); + topentry->probeid = 0; + GST_LOG_OBJECT (comp, "Unblocked top-level pad"); + } + } else + GST_DEBUG ("Element went away from currently configured stack"); + + if (tpad) + gst_object_unref (tpad); + } + +done: + COMP_OBJECTS_UNLOCK (comp); + + if (pad) + gst_object_unref (pad); + + GST_DEBUG_OBJECT (comp, "end"); + + return; + +no_source: + { + GST_LOG_OBJECT (comp, "no source pad"); + return; + } + +not_in_stack: + { + GST_LOG_OBJECT (comp, + "The following object is not in currently configured stack : %s", + GST_ELEMENT_NAME (object)); + goto done; + } +} + +/* + * recursive depth-first relink stack function on new stack + * + * _ relink nodes with changed parent/order + * _ links new nodes with parents + * _ unblocks available source pads (except for toplevel) + * + * WITH OBJECTS LOCK TAKEN + */ + +static void +compare_relink_single_node (GnlComposition * comp, GNode * node, + GNode * oldstack) +{ + GNode *child; + GNode *oldnode = NULL; + GnlObject *newobj; + GnlObject *newparent; + GnlObject *oldparent = NULL; + GstPad *srcpad = NULL, *sinkpad = NULL; + GnlCompositionEntry *entry; + + if (G_UNLIKELY (!node)) + return; + + newparent = G_NODE_IS_ROOT (node) ? NULL : (GnlObject *) node->parent->data; + newobj = (GnlObject *) node->data; + if (oldstack) { + oldnode = g_node_find (oldstack, G_IN_ORDER, G_TRAVERSE_ALL, newobj); + if (oldnode) + oldparent = + G_NODE_IS_ROOT (oldnode) ? NULL : (GnlObject *) oldnode->parent->data; + } + + GST_DEBUG_OBJECT (comp, "newobj:%s", + GST_ELEMENT_NAME ((GstElement *) newobj)); + srcpad = get_src_pad ((GstElement *) newobj); + + /* 1. Make sure the source pad is blocked for new objects */ + if (G_UNLIKELY (!oldnode && srcpad)) { + GnlCompositionEntry *oldentry = COMP_ENTRY (comp, newobj); + if (!oldentry->probeid) { + GST_LOG_OBJECT (comp, "block_async(%s:%s, TRUE)", + GST_DEBUG_PAD_NAME (srcpad)); + oldentry->probeid = + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) pad_blocked, comp, NULL); + } + if (!oldentry->dataprobeid) { + oldentry->dataprobeid = gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST | + GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, + oldentry, NULL); + } + } + + entry = COMP_ENTRY (comp, newobj); + /* 2. link to parent if needed. + * + * If entry->nomorepadshandler is not zero, it means that srcpad didn't exist + * before and so we connected to no-more-pads. This can happen since there's a + * window of time between gnlsource adds its srcpad and then emits + * no-more-pads. In that case, we just wait for no-more-pads to be emitted. + */ + if (srcpad && entry->nomorepadshandler == 0) { + GST_LOG_OBJECT (comp, "has a valid source pad"); + /* POST PROCESSING */ + if ((oldparent != newparent) || + (oldparent && newparent && + (g_node_child_index (node, + newobj) != g_node_child_index (oldnode, newobj)))) { + GST_LOG_OBJECT (comp, + "not same parent, or same parent but in different order"); + /* relink to new parent in required order */ + if (newparent) { + GstPad *sinkpad; + GST_LOG_OBJECT (comp, "Linking %s and %s", + GST_ELEMENT_NAME (GST_ELEMENT (newobj)), + GST_ELEMENT_NAME (GST_ELEMENT (newparent))); + sinkpad = get_unlinked_sink_ghost_pad ((GnlOperation *) newparent); + if (G_UNLIKELY (sinkpad == NULL)) { + GST_WARNING_OBJECT (comp, + "Couldn't find an unlinked sinkpad from %s", + GST_ELEMENT_NAME (newparent)); + } else { + if (G_UNLIKELY (gst_pad_link_full (srcpad, sinkpad, + GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (comp, "Failed to link pads %s:%s - %s:%s", + GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad)); + } + gst_object_unref (sinkpad); + } + } + } else + GST_LOG_OBJECT (newobj, "Same parent and same position in the new stack"); + /* If there's an operation, inform it about priority changes */ + if (newparent) { + sinkpad = gst_pad_get_peer (srcpad); + gnl_operation_signal_input_priority_changed ((GnlOperation *) + newparent, sinkpad, newobj->priority); + gst_object_unref (sinkpad); + } + + } else if (entry->nomorepadshandler) { + GST_INFO_OBJECT (newobj, + "we have a pad but we are connected to 'no-more-pads'"); + } else { + wait_no_more_pads (comp, newobj, entry, TRUE); + } + + /* 3. Handle children */ + if (GNL_IS_OPERATION (newobj)) { + guint nbchildren = g_node_n_children (node); + GnlOperation *oper = (GnlOperation *) newobj; + GST_LOG_OBJECT (newobj, "is a %s operation, analyzing the %d children", + oper->dynamicsinks ? "dynamic" : "regular", nbchildren); + /* Update the operation's number of sinks, that will make it have the proper + * number of sink pads to connect the children to. */ + if (oper->dynamicsinks) + g_object_set (G_OBJECT (newobj), "sinks", nbchildren, NULL); + for (child = node->children; child; child = child->next) + compare_relink_single_node (comp, child, oldstack); + if (G_UNLIKELY (nbchildren < oper->num_sinks)) + GST_ERROR + ("Not enough sinkpads to link all objects to the operation ! %d / %d", + oper->num_sinks, nbchildren); + if (G_UNLIKELY (nbchildren == 0)) + GST_ERROR ("Operation has no child objects to be connected to !!!"); + /* Make sure we have enough sinkpads */ + } else { + /* FIXME : do we need to do something specific for sources ? */ + } + + /* 4. Unblock source pad */ + if ((srcpad && entry->nomorepadshandler == 0) && !G_NODE_IS_ROOT (node) && + entry->probeid) { + GST_LOG_OBJECT (comp, "Unblocking pad %s:%s", GST_DEBUG_PAD_NAME (srcpad)); + gst_pad_remove_probe (srcpad, entry->probeid); + entry->probeid = 0; + } + + if (G_LIKELY (srcpad)) + gst_object_unref (srcpad); + GST_LOG_OBJECT (comp, "done with object %s", + GST_ELEMENT_NAME (GST_ELEMENT (newobj))); +} + +/* + * recursive depth-first compare stack function on old stack + * + * _ Add no-longer used objects to the deactivate list + * _ unlink child-parent relations that have changed (not same parent, or not same order) + * _ blocks available source pads + * + * FIXME : modify is only used for the root element. + * It is TRUE all the time except when the update is done from a seek + * + * WITH OBJECTS LOCK TAKEN + */ + +static GList * +compare_deactivate_single_node (GnlComposition * comp, GNode * node, + GNode * newstack, gboolean modify) +{ + GNode *child; + GNode *newnode = NULL; /* Same node in newstack */ + GnlObject *oldparent; + GList *deactivate = NULL; + GnlObject *oldobj = NULL; + GstPad *srcpad = NULL; + + if (G_UNLIKELY (!node)) + return NULL; + + /* The former parent GnlObject (i.e. downstream) of the given node */ + oldparent = G_NODE_IS_ROOT (node) ? NULL : (GnlObject *) node->parent->data; + + /* The former GnlObject */ + oldobj = (GnlObject *) node->data; + + /* The node corresponding to oldobj in the new stack */ + if (newstack) + newnode = g_node_find (newstack, G_IN_ORDER, G_TRAVERSE_ALL, oldobj); + + GST_DEBUG_OBJECT (comp, "oldobj:%s", + GST_ELEMENT_NAME ((GstElement *) oldobj)); + srcpad = get_src_pad ((GstElement *) oldobj); + + if (G_LIKELY (srcpad)) { + GstPad *peerpad = NULL; + GnlCompositionEntry *entry = COMP_ENTRY (comp, oldobj); + + /* 1. Block source pad + * This makes sure that no data/event flow will come out of this element after this + * point. + * + * If entry is NULL, this means the element is in the process of being removed. + */ + if (entry && !entry->probeid) { + GST_LOG_OBJECT (comp, "Setting BLOCKING probe on %s:%s", + GST_DEBUG_PAD_NAME (srcpad)); + entry->probeid = + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) pad_blocked, comp, NULL); + } + if (entry && !entry->dataprobeid) { + entry->dataprobeid = gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST | + GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, entry, + NULL); + } + + /* 2. If we have to modify or we have a parent, flush downstream + * This ensures the streaming thread going through the current object has + * either stopped or is blocking against the source pad. */ + if ((modify || oldparent) && (peerpad = gst_pad_get_peer (srcpad))) { + GST_LOG_OBJECT (comp, "Sending flush start/stop downstream "); + gst_pad_send_event (peerpad, gst_event_new_flush_start ()); + gst_pad_send_event (peerpad, gst_event_new_flush_stop (TRUE)); + GST_DEBUG_OBJECT (comp, "DONE Sending flush events downstream"); + gst_object_unref (peerpad); + } + + } else { + GST_LOG_OBJECT (comp, "No source pad available"); + } + + /* 3. Unlink from the parent if we've changed position */ + + GST_LOG_OBJECT (comp, + "Checking if we need to unlink from downstream element"); + if (G_UNLIKELY (!oldparent)) { + GST_LOG_OBJECT (comp, "Top-level object"); + /* for top-level objects we just set the ghostpad target to NULL */ + if (comp->priv->ghostpad) { + GST_LOG_OBJECT (comp, "Setting ghostpad target to NULL"); + gnl_composition_ghost_pad_set_target (comp, NULL, NULL); + } else + GST_LOG_OBJECT (comp, "No ghostpad"); + } else { + GnlObject *newparent = NULL; + + GST_LOG_OBJECT (comp, "non-toplevel object"); + + if (newnode) + newparent = + G_NODE_IS_ROOT (newnode) ? NULL : (GnlObject *) newnode->parent->data; + + if ((!newnode) || (oldparent != newparent) || + (newparent && + (g_node_child_index (node, + oldobj) != g_node_child_index (newnode, oldobj)))) { + GstPad *peerpad = NULL; + + GST_LOG_OBJECT (comp, "Topology changed, unlinking from downstream"); + + if (srcpad && (peerpad = gst_pad_get_peer (srcpad))) { + GST_LOG_OBJECT (peerpad, "Sending flush start/stop"); + gst_pad_send_event (peerpad, gst_event_new_flush_start ()); + gst_pad_send_event (peerpad, gst_event_new_flush_stop (TRUE)); + gst_pad_unlink (srcpad, peerpad); + gst_object_unref (peerpad); + } + } else + GST_LOG_OBJECT (comp, "Topology unchanged"); + } + + /* 4. If we're dealing with an operation, call this method recursively on it */ + if (G_UNLIKELY (GNL_IS_OPERATION (oldobj))) { + GST_LOG_OBJECT (comp, + "Object is an operation, recursively calling on children"); + for (child = node->children; child; child = child->next) { + GList *newdeac = + compare_deactivate_single_node (comp, child, newstack, modify); + + if (newdeac) + deactivate = g_list_concat (deactivate, newdeac); + } + } + + /* 5. If object isn't used anymore, add it to the list of objects to deactivate */ + if (G_LIKELY (!newnode)) { + GST_LOG_OBJECT (comp, "Object doesn't exist in new stack"); + deactivate = g_list_prepend (deactivate, oldobj); + } + + if (G_LIKELY (srcpad)) + gst_object_unref (srcpad); + GST_LOG_OBJECT (comp, "done with object %s", + GST_ELEMENT_NAME (GST_ELEMENT (oldobj))); + + return deactivate; +} + +/* + * compare_relink_stack: + * @comp: The #GnlComposition + * @stack: The new stack + * @modify: TRUE if the timeline has changed and needs downstream flushes. + * + * Compares the given stack to the current one and relinks it if needed. + * + * WITH OBJECTS LOCK TAKEN + * + * Returns: The #GList of #GnlObject no longer used + */ + +static GList * +compare_relink_stack (GnlComposition * comp, GNode * stack, gboolean modify) +{ + GList *deactivate = NULL; + + /* 1. Traverse old stack to deactivate no longer used objects */ + deactivate = + compare_deactivate_single_node (comp, comp->priv->current, stack, modify); + + /* 2. Traverse new stack to do needed (re)links */ + compare_relink_single_node (comp, stack, comp->priv->current); + + return deactivate; +} + +static void +unlock_activate_stack (GnlComposition * comp, GNode * node, GstState state) +{ + GNode *child; + + GST_LOG_OBJECT (comp, "object:%s", + GST_ELEMENT_NAME ((GstElement *) (node->data))); + + gst_element_set_locked_state ((GstElement *) (node->data), FALSE); + gst_element_set_state (GST_ELEMENT (node->data), state); + + for (child = node->children; child; child = child->next) + unlock_activate_stack (comp, child, state); +} + +static gboolean +are_same_stacks (GNode * stack1, GNode * stack2) +{ + gboolean res = FALSE; + + /* TODO : FIXME : we should also compare start/inpoint */ + /* stacks are not equal if one of them is NULL but not the other */ + if ((!stack1 && stack2) || (stack1 && !stack2)) + goto beach; + + if (stack1 && stack2) { + GNode *child1, *child2; + + /* if they don't contain the same source, not equal */ + if (!(stack1->data == stack2->data)) + goto beach; + + /* if they don't have the same number of children, not equal */ + if (!(g_node_n_children (stack1) == g_node_n_children (stack2))) + goto beach; + + child1 = stack1->children; + child2 = stack2->children; + while (child1 && child2) { + if (!(are_same_stacks (child1, child2))) + goto beach; + child1 = g_node_next_sibling (child1); + child2 = g_node_next_sibling (child2); + } + + /* if there's a difference in child number, stacks are not equal */ + if (child1 || child2) + goto beach; + } + + /* if stack1 AND stack2 are NULL, then they're equal (both empty) */ + res = TRUE; + +beach: + GST_LOG ("Stacks are equal : %d", res); + + return res; +} + +/* + * update_pipeline: + * @comp: The #GnlComposition + * @currenttime: The #GstClockTime to update at, can be GST_CLOCK_TIME_NONE. + * @initial: TRUE if this is the first setup + * @change_state: Change the state of the (de)activated objects if TRUE. + * @modify: Flush downstream if TRUE. Needed for modified timelines. + * + * Updates the internal pipeline and properties. If @currenttime is + * GST_CLOCK_TIME_NONE, it will not modify the current pipeline + * + * Returns: FALSE if there was an error updating the pipeline. + * + * WITH OBJECTS LOCK TAKEN + */ +static gboolean +update_pipeline (GnlComposition * comp, GstClockTime currenttime, + gboolean initial, gboolean modify) +{ + gboolean startchanged, stopchanged; + + GNode *stack = NULL; + gboolean ret = TRUE; + GList *todeactivate = NULL; + gboolean samestack = FALSE; + GstState state = GST_STATE (comp); + GnlCompositionPrivate *priv = comp->priv; + GstClockTime new_stop = GST_CLOCK_TIME_NONE; + GstClockTime new_start = GST_CLOCK_TIME_NONE; + GstState nextstate = (GST_STATE_NEXT (comp) == GST_STATE_VOID_PENDING) ? + GST_STATE (comp) : GST_STATE_NEXT (comp); + + GST_DEBUG_OBJECT (comp, + "currenttime:%" GST_TIME_FORMAT + " initial:%d , modify:%d", GST_TIME_ARGS (currenttime), initial, modify); + + if (!GST_CLOCK_TIME_IS_VALID (currenttime)) + return FALSE; + + if (state == GST_STATE_NULL && nextstate == GST_STATE_NULL) { + GST_DEBUG_OBJECT (comp, "STATE_NULL: not updating pipeline"); + return FALSE; + } + + + GST_DEBUG_OBJECT (comp, + "now really updating the pipeline, current-state:%s", + gst_element_state_get_name (state)); + + /* 1. Get new stack and compare it to current one */ + stack = get_clean_toplevel_stack (comp, ¤ttime, &new_start, &new_stop); + samestack = are_same_stacks (priv->current, stack); + + /* invalidate the stack while modifying it */ + priv->stackvalid = FALSE; + + /* 2. If stacks are different, unlink/relink objects */ + if (!samestack) + todeactivate = compare_relink_stack (comp, stack, modify); + + if (priv->segment->rate >= 0.0) { + startchanged = priv->segment_start != currenttime; + stopchanged = priv->segment_stop != new_stop; + } else { + startchanged = priv->segment_start != new_start; + stopchanged = priv->segment_stop != currenttime; + } + + /* 3. set new segment_start/stop (the current zone over which the new stack + * is valid) */ + if (priv->segment->rate >= 0.0) { + priv->segment_start = currenttime; + priv->segment_stop = new_stop; + } else { + priv->segment_start = new_start; + priv->segment_stop = currenttime; + } + + /* 4. Clear pending child seek + * We'll be creating a new one */ + if (priv->childseek) { + GST_DEBUG ("unreffing event %p", priv->childseek); + gst_event_unref (priv->childseek); + priv->childseek = NULL; + } + + /* Invalidate current stack */ + if (priv->current) + g_node_destroy (priv->current); + priv->current = NULL; + + /* 5. deactivate unused elements */ + if (todeactivate) { + GList *tmp; + GnlCompositionEntry *entry; + GstElement *element; + + GST_DEBUG_OBJECT (comp, "De-activating objects no longer used"); + + /* state-lock elements no more used */ + for (tmp = todeactivate; tmp; tmp = tmp->next) { + element = GST_ELEMENT_CAST (tmp->data); + + gst_element_set_state (element, priv->deactivated_elements_state); + gst_element_set_locked_state (element, TRUE); + entry = COMP_ENTRY (comp, element); + + /* entry can be NULL here if update_pipeline was called by + * gnl_composition_remove_object (comp, tmp->data) + */ + if (entry && entry->nomorepadshandler) + wait_no_more_pads (comp, element, entry, FALSE); + } + + g_list_free (todeactivate); + + GST_DEBUG_OBJECT (comp, "Finished de-activating objects no longer used"); + } + + /* 6. Unlock all elements in new stack */ + GST_DEBUG_OBJECT (comp, "Setting current stack"); + priv->current = stack; + + if (!samestack && stack) { + GST_DEBUG_OBJECT (comp, "activating objects in new stack to %s", + gst_element_state_get_name (nextstate)); + unlock_activate_stack (comp, stack, nextstate); + GST_DEBUG_OBJECT (comp, "Finished activating objects in new stack"); + } + + /* 7. Activate stack (might happen asynchronously) */ + if (priv->current) { + GstEvent *event; + + priv->stackvalid = TRUE; + + /* 7.1. Create new seek event for newly configured timeline stack */ + if (samestack && (startchanged || stopchanged)) + event = + get_new_seek_event (comp, + (state == GST_STATE_PLAYING) ? FALSE : TRUE, !startchanged); + else + event = get_new_seek_event (comp, initial, FALSE); + + /* 7.2.a If the stack entirely ready, send seek out synchronously */ + if (priv->waitingpads == 0) { + GstPad *pad; + GstElement *topelement = GST_ELEMENT (priv->current->data); + + /* Get toplevel object source pad */ + if ((pad = get_src_pad (topelement))) { + GnlCompositionEntry *topentry = COMP_ENTRY (comp, topelement); + + GST_DEBUG_OBJECT (comp, + "We have a valid toplevel element pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + /* Send seek event */ + GST_LOG_OBJECT (comp, "sending seek event"); + if (gst_pad_send_event (pad, event)) { + /* Unconditionnaly set the ghostpad target to pad */ + GST_LOG_OBJECT (comp, + "Setting the composition's ghostpad target to %s:%s", + GST_DEBUG_PAD_NAME (pad)); + + gnl_composition_ghost_pad_set_target (comp, pad, topentry); + + if (topentry->probeid) { + + /* unblock top-level pad */ + GST_LOG_OBJECT (comp, "About to unblock top-level srcpad"); + gst_pad_remove_probe (pad, topentry->probeid); + topentry->probeid = 0; + } + } else { + ret = FALSE; + } + gst_object_unref (pad); + + } else { + GST_WARNING_OBJECT (comp, + "Timeline is entirely linked, but couldn't get top-level element's source pad"); + + ret = FALSE; + } + } else { + /* 7.2.b. Stack isn't entirely ready, save seek event for later on */ + GST_LOG_OBJECT (comp, + "The timeline stack isn't entirely linked, delaying sending seek event (waitingpads:%d)", + priv->waitingpads); + + priv->childseek = event; + ret = TRUE; + } + } else { + if ((!priv->objects_start) && priv->ghostpad) { + GST_DEBUG_OBJECT (comp, "composition is now empty, removing ghostpad"); + gnl_composition_remove_ghostpad (comp); + priv->segment_start = 0; + priv->segment_stop = GST_CLOCK_TIME_NONE; + } + } + + GST_DEBUG_OBJECT (comp, "Returning %d", ret); + return ret; +} + +/* + * Child modification updates + */ + +static void +object_pad_removed (GnlObject * object, GstPad * pad, GnlComposition * comp) +{ + GnlCompositionPrivate *priv = comp->priv; + + GST_DEBUG_OBJECT (comp, "pad %s:%s was removed", GST_DEBUG_PAD_NAME (pad)); + + if (GST_PAD_IS_SRC (pad)) { + /* remove ghostpad if it's the current top stack object */ + if (priv->current && GNL_OBJECT (priv->current->data) == object + && priv->ghostpad) + gnl_composition_remove_ghostpad (comp); + else { + GnlCompositionEntry *entry = COMP_ENTRY (comp, object); + + if (entry && entry->probeid) { + gst_pad_remove_probe (pad, entry->probeid); + entry->probeid = 0; + } + if (entry && entry->dataprobeid) { + gst_pad_remove_probe (pad, entry->dataprobeid); + entry->dataprobeid = 0; + } + } + } +} + +static void +object_pad_added (GnlObject * object G_GNUC_UNUSED, GstPad * pad, + GnlComposition * comp) +{ + GnlCompositionEntry *entry; + + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) + return; + + entry = COMP_ENTRY (comp, object); + + if (!entry->probeid) { + GST_DEBUG_OBJECT (comp, "pad %s:%s was added, blocking it", + GST_DEBUG_PAD_NAME (pad)); + entry->probeid = + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE, + (GstPadProbeCallback) pad_blocked, comp, NULL); + } + + if (!entry->dataprobeid) { + entry->dataprobeid = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST | + GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, entry, + NULL); + } +} + +static gboolean +gnl_composition_add_object (GstBin * bin, GstElement * element) +{ + gboolean ret; + GnlCompositionEntry *entry; + GnlComposition *comp = (GnlComposition *) bin; + GnlCompositionPrivate *priv = comp->priv; + + /* we only accept GnlObject */ + g_return_val_if_fail (GNL_IS_OBJECT (element), FALSE); + + GST_DEBUG_OBJECT (bin, "element %s", GST_OBJECT_NAME (element)); + GST_DEBUG_OBJECT (element, "%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT, + GST_TIME_ARGS (GNL_OBJECT_START (element)), + GST_TIME_ARGS (GNL_OBJECT_STOP (element))); + + gst_object_ref (element); + + COMP_OBJECTS_LOCK (comp); + + if ((GNL_OBJECT_IS_EXPANDABLE (element)) && + g_list_find (priv->expandables, element)) { + GST_WARNING_OBJECT (comp, + "We already have an expandable, remove it before adding new one"); + ret = FALSE; + + goto chiringuito; + } + + /* Call parent class ::add_element() */ + ret = GST_BIN_CLASS (parent_class)->add_element (bin, element); + + gnl_object_set_commit_needed (GNL_OBJECT (comp)); + + if (!ret) { + GST_WARNING_OBJECT (bin, "couldn't add element"); + goto chiringuito; + } + + /* lock state of child ! */ + GST_LOG_OBJECT (bin, "Locking state of %s", GST_ELEMENT_NAME (element)); + gst_element_set_locked_state (element, TRUE); + + /* wrap new element in a GnlCompositionEntry ... */ + entry = g_slice_new0 (GnlCompositionEntry); + entry->object = (GnlObject *) element; + entry->comp = comp; + + if (GNL_OBJECT_IS_EXPANDABLE (element)) { + /* Only react on non-default objects properties */ + g_object_set (element, + "start", (GstClockTime) 0, + "inpoint", (GstClockTime) 0, + "duration", (GstClockTimeDiff) GNL_OBJECT_STOP (comp), NULL); + + GST_INFO_OBJECT (element, "Used as expandable, commiting now"); + gnl_object_commit (GNL_OBJECT (element), FALSE); + } + + /* ...and add it to the hash table */ + g_hash_table_insert (priv->objects_hash, element, entry); + + entry->padremovedhandler = g_signal_connect (G_OBJECT (element), + "pad-removed", G_CALLBACK (object_pad_removed), comp); + entry->padaddedhandler = g_signal_connect (G_OBJECT (element), + "pad-added", G_CALLBACK (object_pad_added), comp); + + /* Set the caps of the composition */ + if (G_UNLIKELY (!gst_caps_is_any (((GnlObject *) comp)->caps))) + gnl_object_set_caps ((GnlObject *) element, ((GnlObject *) comp)->caps); + + /* Special case for default source. */ + if (GNL_OBJECT_IS_EXPANDABLE (element)) { + /* It doesn't get added to objects_start and objects_stop. */ + priv->expandables = g_list_prepend (priv->expandables, element); + goto beach; + } + + /* add it sorted to the objects list */ + priv->objects_start = g_list_insert_sorted + (priv->objects_start, element, (GCompareFunc) objects_start_compare); + + if (priv->objects_start) + GST_LOG_OBJECT (comp, + "Head of objects_start is now %s [%" GST_TIME_FORMAT "--%" + GST_TIME_FORMAT "]", + GST_OBJECT_NAME (priv->objects_start->data), + GST_TIME_ARGS (GNL_OBJECT_START (priv->objects_start->data)), + GST_TIME_ARGS (GNL_OBJECT_STOP (priv->objects_start->data))); + + priv->objects_stop = g_list_insert_sorted + (priv->objects_stop, element, (GCompareFunc) objects_stop_compare); + + /* Now the object is ready to be commited and then used */ + +beach: + COMP_OBJECTS_UNLOCK (comp); + + gst_object_unref (element); + return ret; + +chiringuito: + { + update_start_stop_duration (comp); + goto beach; + } +} + + +static gboolean +gnl_composition_remove_object (GstBin * bin, GstElement * element) +{ + GnlComposition *comp = (GnlComposition *) bin; + GnlCompositionPrivate *priv = comp->priv; + gboolean ret = FALSE; + gboolean update_required; + GnlCompositionEntry *entry; + + GST_DEBUG_OBJECT (bin, "element %s", GST_OBJECT_NAME (element)); + + /* we only accept GnlObject */ + g_return_val_if_fail (GNL_IS_OBJECT (element), FALSE); + COMP_OBJECTS_LOCK (comp); + entry = COMP_ENTRY (comp, element); + if (entry == NULL) { + COMP_OBJECTS_UNLOCK (comp); + goto out; + } + + if (entry->nomorepadshandler) + wait_no_more_pads (comp, element, entry, FALSE); + gst_object_ref (element); + gst_element_set_locked_state (element, FALSE); + + /* handle default source */ + if (GNL_OBJECT_IS_EXPANDABLE (element)) { + /* Find it in the list */ + priv->expandables = g_list_remove (priv->expandables, element); + } else { + /* remove it from the objects list and resort the lists */ + priv->objects_start = g_list_remove (priv->objects_start, element); + priv->objects_stop = g_list_remove (priv->objects_stop, element); + GST_LOG_OBJECT (element, "Removed from the objects start/stop list"); + } + + g_hash_table_remove (priv->objects_hash, element); + update_required = OBJECT_IN_ACTIVE_SEGMENT (comp, element) || + (GNL_OBJECT_PRIORITY (element) == G_MAXUINT32) || + GNL_OBJECT_IS_EXPANDABLE (element); + + if (G_LIKELY (update_required)) { + /* And update the pipeline at current position if needed */ + update_pipeline_at_current_position (comp); + } else + update_start_stop_duration (comp); + + ret = GST_BIN_CLASS (parent_class)->remove_element (bin, element); + GST_LOG_OBJECT (element, "Done removing from the composition, now updating"); + COMP_OBJECTS_UNLOCK (comp); + + + /* Make it possible to reuse the same object later */ + gnl_object_reset (GNL_OBJECT (element)); + gst_object_unref (element); +out: + return ret; +} diff --git a/gnl/gnlcomposition.h b/gnl/gnlcomposition.h new file mode 100644 index 00000000..8299522b --- /dev/null +++ b/gnl/gnlcomposition.h @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * gnlcomposition.h: Header for base GnlComposition + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_COMPOSITION_H__ +#define __GNL_COMPOSITION_H__ + +#include <gst/gst.h> +#include "gnlobject.h" + +G_BEGIN_DECLS +#define GNL_TYPE_COMPOSITION \ + (gnl_composition_get_type()) +#define GNL_COMPOSITION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_COMPOSITION,GnlComposition)) +#define GNL_COMPOSITION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_COMPOSITION,GnlCompositionClass)) +#define GNL_COMPOSITION_GET_CLASS(obj) \ + (GNL_COMPOSITION_CLASS (G_OBJECT_GET_CLASS (obj))) +#define GNL_IS_COMPOSITION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_COMPOSITION)) +#define GNL_IS_COMPOSITION_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_COMPOSITION)) + +typedef struct _GnlCompositionPrivate GnlCompositionPrivate; + +struct _GnlComposition +{ + GnlObject parent; + + /*< private >*/ + + GnlCompositionPrivate *priv; +}; + +struct _GnlCompositionClass +{ + GnlObjectClass parent_class; +}; + +GType gnl_composition_get_type (void); + +G_END_DECLS +#endif /* __GNL_COMPOSITION_H__ */ diff --git a/gnl/gnlghostpad.c b/gnl/gnlghostpad.c new file mode 100644 index 00000000..5db4e44f --- /dev/null +++ b/gnl/gnlghostpad.c @@ -0,0 +1,770 @@ +/* Gnonlin + * Copyright (C) <2009> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" + +GST_DEBUG_CATEGORY_STATIC (gnlghostpad); +#define GST_CAT_DEFAULT gnlghostpad + +typedef struct _GnlPadPrivate GnlPadPrivate; + +struct _GnlPadPrivate +{ + GnlObject *object; + GnlPadPrivate *ghostpriv; + GstPadDirection dir; + GstPadEventFunction eventfunc; + GstPadQueryFunction queryfunc; +}; + +static GstEvent * +translate_incoming_seek (GnlObject * object, GstEvent * event) +{ + GstEvent *event2; + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType curtype, stoptype; + GstSeekType ncurtype; + gint64 cur; + guint64 ncur; + gint64 stop; + guint64 nstop; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + gst_event_parse_seek (event, &rate, &format, &flags, + &curtype, &cur, &stoptype, &stop); + + GST_DEBUG_OBJECT (object, + "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype, + stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop)); + + if (G_UNLIKELY (format != GST_FORMAT_TIME)) + goto invalid_format; + + /* convert cur */ + ncurtype = GST_SEEK_TYPE_SET; + if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET) + && (gnl_object_to_media_time (object, cur, &ncur)))) { + /* cur is TYPE_SET and value is valid */ + if (ncur > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT, + GST_TIME_ARGS (ncur)); + } else if ((curtype != GST_SEEK_TYPE_NONE)) { + GST_DEBUG_OBJECT (object, "Limiting seek start to inpoint"); + ncur = object->inpoint; + } else { + GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE"); + ncur = cur; + ncurtype = GST_SEEK_TYPE_NONE; + } + + /* convert stop, we also need to limit it to object->stop */ + if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET) + && (gnl_object_to_media_time (object, stop, &nstop)))) { + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } else { + GST_DEBUG_OBJECT (object, "Limiting end of seek to media_stop"); + gnl_object_to_media_time (object, object->stop, &nstop); + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } + + + /* add accurate seekflags */ + if (G_UNLIKELY (!(flags & GST_SEEK_FLAG_ACCURATE))) { + GST_DEBUG_OBJECT (object, "Adding GST_SEEK_FLAG_ACCURATE"); + flags |= GST_SEEK_FLAG_ACCURATE; + } else { + GST_DEBUG_OBJECT (object, + "event already has GST_SEEK_FLAG_ACCURATE : %d", flags); + } + + + + GST_DEBUG_OBJECT (object, + "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype, + GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop)); + + event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop); + GST_EVENT_SEQNUM (event2) = seqnum; + + gst_event_unref (event); + + return event2; + + /* ERRORS */ +invalid_format: + { + GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME"); + return event; + } +} + +static GstEvent * +translate_outgoing_seek (GnlObject * object, GstEvent * event) +{ + GstEvent *event2; + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType curtype, stoptype; + GstSeekType ncurtype; + gint64 cur; + guint64 ncur; + gint64 stop; + guint64 nstop; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + gst_event_parse_seek (event, &rate, &format, &flags, + &curtype, &cur, &stoptype, &stop); + + GST_DEBUG_OBJECT (object, + "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype, + stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop)); + + if (G_UNLIKELY (format != GST_FORMAT_TIME)) + goto invalid_format; + + /* convert cur */ + ncurtype = GST_SEEK_TYPE_SET; + if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET) + && (gnl_media_to_object_time (object, cur, &ncur)))) { + /* cur is TYPE_SET and value is valid */ + if (ncur > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT, + GST_TIME_ARGS (ncur)); + } else if ((curtype != GST_SEEK_TYPE_NONE)) { + GST_DEBUG_OBJECT (object, "Limiting seek start to start"); + ncur = object->start; + } else { + GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE"); + ncur = cur; + ncurtype = GST_SEEK_TYPE_NONE; + } + + /* convert stop, we also need to limit it to object->stop */ + if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET) + && (gnl_media_to_object_time (object, stop, &nstop)))) { + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } else { + GST_DEBUG_OBJECT (object, "Limiting end of seek to stop"); + nstop = object->stop; + if (nstop > G_MAXINT64) + GST_WARNING_OBJECT (object, "return value too big..."); + GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, + GST_TIME_ARGS (nstop)); + } + + GST_DEBUG_OBJECT (object, + "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %" + GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype, + GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop)); + + event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop); + GST_EVENT_SEQNUM (event2) = seqnum; + + gst_event_unref (event); + + return event2; + + /* ERRORS */ +invalid_format: + { + GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME"); + return event; + } +} + +static GstEvent * +translate_outgoing_segment (GnlObject * object, GstEvent * event) +{ + const GstSegment *orig; + GstSegment segment; + GstEvent *event2; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + /* only modify the streamtime */ + gst_event_parse_segment (event, &orig); + + GST_DEBUG_OBJECT (object, + "Got SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop), + GST_TIME_ARGS (orig->time)); + + if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "Can't translate segments with format != GST_FORMAT_TIME"); + return event; + } + + gst_segment_copy_into (orig, &segment); + + gnl_media_to_object_time (object, orig->time, &segment.time); + + if (G_UNLIKELY (segment.time > G_MAXINT64)) + GST_WARNING_OBJECT (object, "Return value too big..."); + + GST_DEBUG_OBJECT (object, + "Sending SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (segment.start), + GST_TIME_ARGS (segment.stop), GST_TIME_ARGS (segment.time)); + + event2 = gst_event_new_segment (&segment); + GST_EVENT_SEQNUM (event2) = seqnum; + gst_event_unref (event); + + return event2; +} + +static GstEvent * +translate_incoming_segment (GnlObject * object, GstEvent * event) +{ + GstEvent *event2; + const GstSegment *orig; + GstSegment segment; + guint32 seqnum = GST_EVENT_SEQNUM (event); + + /* only modify the streamtime */ + gst_event_parse_segment (event, &orig); + + GST_DEBUG_OBJECT (object, + "Got SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop), + GST_TIME_ARGS (orig->time)); + + if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "Can't translate segments with format != GST_FORMAT_TIME"); + return event; + } + + gst_segment_copy_into (orig, &segment); + + if (!gnl_object_to_media_time (object, orig->time, &segment.time)) { + GST_DEBUG ("Can't convert media_time, using 0"); + segment.time = 0; + }; + + if (GNL_IS_OPERATION (object)) { + segment.base = GNL_OPERATION (object)->next_base_time; + GST_INFO_OBJECT (object, "Using operation base time %" GST_TIME_FORMAT, + GST_TIME_ARGS (GNL_OPERATION (object)->next_base_time)); + } + + if (G_UNLIKELY (segment.time > G_MAXINT64)) + GST_WARNING_OBJECT (object, "Return value too big..."); + + GST_DEBUG_OBJECT (object, + "Sending SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %" + GST_TIME_FORMAT, GST_TIME_ARGS (segment.start), + GST_TIME_ARGS (segment.stop), GST_TIME_ARGS (segment.time)); + + event2 = gst_event_new_segment (&segment); + GST_EVENT_SEQNUM (event2) = seqnum; + gst_event_unref (event); + + return event2; +} + +static gboolean +internalpad_event_function (GstPad * internal, GstObject * parent, + GstEvent * event) +{ + GnlPadPrivate *priv = gst_pad_get_element_private (internal); + GnlObject *object = priv->object; + gboolean res; + + GST_DEBUG_OBJECT (internal, "event:%s (seqnum::%d)", + GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event)); + + if (G_UNLIKELY (!(priv->eventfunc))) { + GST_WARNING_OBJECT (internal, + "priv->eventfunc == NULL !! What is going on ?"); + return FALSE; + } + + switch (priv->dir) { + case GST_PAD_SRC:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + event = translate_outgoing_segment (object, event); + break; + default: + break; + } + + break; + } + case GST_PAD_SINK:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + event = translate_outgoing_seek (object, event); + break; + default: + break; + } + break; + } + default: + break; + } + GST_DEBUG_OBJECT (internal, "Calling priv->eventfunc %p", priv->eventfunc); + res = priv->eventfunc (internal, parent, event); + + return res; +} + +/* + translate_outgoing_position_query + + Should only be called: + _ if the query is a GST_QUERY_POSITION + _ after the query was sent upstream + _ if the upstream query returned TRUE +*/ + +static gboolean +translate_incoming_position_query (GnlObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur, cur2; + + gst_query_parse_position (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "position query is in a format different from time, returning without modifying values"); + goto beach; + } + + gnl_media_to_object_time (object, (guint64) cur, (guint64 *) & cur2); + + GST_DEBUG_OBJECT (object, + "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2)); + gst_query_set_position (query, GST_FORMAT_TIME, cur2); + +beach: + return TRUE; +} + +static gboolean +translate_outgoing_position_query (GnlObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur, cur2; + + gst_query_parse_position (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "position query is in a format different from time, returning without modifying values"); + goto beach; + } + + if (G_UNLIKELY (!(gnl_object_to_media_time (object, (guint64) cur, + (guint64 *) & cur2)))) { + GST_WARNING_OBJECT (object, + "Couldn't get media time for %" GST_TIME_FORMAT, GST_TIME_ARGS (cur)); + goto beach; + } + + GST_DEBUG_OBJECT (object, + "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2)); + gst_query_set_position (query, GST_FORMAT_TIME, cur2); + +beach: + return TRUE; +} + +static gboolean +translate_incoming_duration_query (GnlObject * object, GstQuery * query) +{ + GstFormat format; + gint64 cur; + + gst_query_parse_duration (query, &format, &cur); + if (G_UNLIKELY (format != GST_FORMAT_TIME)) { + GST_WARNING_OBJECT (object, + "We can only handle duration queries in GST_FORMAT_TIME"); + return FALSE; + } + + gst_query_set_duration (query, GST_FORMAT_TIME, object->duration); + + return TRUE; +} + +static gboolean +internalpad_query_function (GstPad * internal, GstObject * parent, + GstQuery * query) +{ + GnlPadPrivate *priv = gst_pad_get_element_private (internal); + GnlObject *object = priv->object; + gboolean ret; + + GST_DEBUG_OBJECT (internal, "querytype:%s", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + if (!(priv->queryfunc)) { + GST_WARNING_OBJECT (internal, + "priv->queryfunc == NULL !! What is going on ?"); + return FALSE; + } + + if ((ret = priv->queryfunc (internal, parent, query))) { + + switch (priv->dir) { + case GST_PAD_SRC: + break; + case GST_PAD_SINK: + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + ret = translate_outgoing_position_query (object, query); + break; + default: + break; + } + break; + default: + break; + } + } + return ret; +} + +static gboolean +ghostpad_event_function (GstPad * ghostpad, GstObject * parent, + GstEvent * event) +{ + GnlPadPrivate *priv; + GnlObject *object; + gboolean ret = FALSE; + + priv = gst_pad_get_element_private (ghostpad); + object = priv->object; + + GST_DEBUG_OBJECT (ghostpad, "event:%s", GST_EVENT_TYPE_NAME (event)); + + if (G_UNLIKELY (priv->eventfunc == NULL)) + goto no_function; + + switch (priv->dir) { + case GST_PAD_SRC: + { + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + event = translate_incoming_seek (object, event); + break; + default: + break; + } + } + break; + case GST_PAD_SINK:{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + event = translate_incoming_segment (object, event); + break; + default: + break; + } + } + break; + default: + break; + } + + if (event) { + GST_DEBUG_OBJECT (ghostpad, "Calling priv->eventfunc"); + ret = priv->eventfunc (ghostpad, parent, event); + GST_DEBUG_OBJECT (ghostpad, "Returned from calling priv->eventfunc : %d", + ret); + } + + return ret; + + /* ERRORS */ +no_function: + { + GST_WARNING_OBJECT (ghostpad, + "priv->eventfunc == NULL !! What's going on ?"); + return FALSE; + } +} + +static gboolean +ghostpad_query_function (GstPad * ghostpad, GstObject * parent, + GstQuery * query) +{ + GnlPadPrivate *priv = gst_pad_get_element_private (ghostpad); + GnlObject *object = GNL_OBJECT (parent); + gboolean pret = TRUE; + + GST_DEBUG_OBJECT (ghostpad, "querytype:%s", GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + /* skip duration upstream query, we'll fill it in ourselves */ + break; + default: + pret = priv->queryfunc (ghostpad, parent, query); + } + + if (pret) { + /* translate result */ + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + pret = translate_incoming_position_query (object, query); + break; + case GST_QUERY_DURATION: + pret = translate_incoming_duration_query (object, query); + break; + default: + break; + } + } + + return pret; +} + +/* internal pad going away */ +static void +internal_pad_finalizing (GnlPadPrivate * priv, GObject * pad G_GNUC_UNUSED) +{ + g_slice_free (GnlPadPrivate, priv); +} + +static inline GstPad * +get_proxy_pad (GstPad * ghostpad) +{ + GValue item = { 0, }; + GstIterator *it; + GstPad *ret = NULL; + + it = gst_pad_iterate_internal_links (ghostpad); + g_assert (it); + gst_iterator_next (it, &item); + ret = g_value_dup_object (&item); + g_value_unset (&item); + g_assert (ret); + gst_iterator_free (it); + + return ret; +} + +static void +control_internal_pad (GstPad * ghostpad, GnlObject * object) +{ + GnlPadPrivate *priv; + GnlPadPrivate *privghost; + GstPad *internal; + + if (!ghostpad) { + GST_DEBUG_OBJECT (object, "We don't have a valid ghostpad !"); + return; + } + privghost = gst_pad_get_element_private (ghostpad); + + GST_LOG_OBJECT (ghostpad, "overriding ghostpad's internal pad function"); + + internal = get_proxy_pad (ghostpad); + + if (G_UNLIKELY (!(priv = gst_pad_get_element_private (internal)))) { + GST_DEBUG_OBJECT (internal, + "Creating a GnlPadPrivate to put in element_private"); + priv = g_slice_new0 (GnlPadPrivate); + + /* Remember existing pad functions */ + priv->eventfunc = GST_PAD_EVENTFUNC (internal); + priv->queryfunc = GST_PAD_QUERYFUNC (internal); + gst_pad_set_element_private (internal, priv); + + g_object_weak_ref ((GObject *) internal, + (GWeakNotify) internal_pad_finalizing, priv); + + /* add query/event function overrides on internal pad */ + gst_pad_set_event_function (internal, + GST_DEBUG_FUNCPTR (internalpad_event_function)); + gst_pad_set_query_function (internal, + GST_DEBUG_FUNCPTR (internalpad_query_function)); + } + + priv->object = object; + priv->ghostpriv = privghost; + priv->dir = GST_PAD_DIRECTION (ghostpad); + gst_object_unref (internal); + + GST_DEBUG_OBJECT (ghostpad, "Done with pad %s:%s", + GST_DEBUG_PAD_NAME (ghostpad)); +} + + +/** + * gnl_object_ghost_pad: + * @object: #GnlObject to add the ghostpad to + * @name: Name for the new pad + * @target: Target #GstPad to ghost + * + * Adds a #GstGhostPad overridding the correct pad [query|event]_function so + * that time shifting is done correctly + * The #GstGhostPad is added to the #GnlObject + * + * /!\ This function doesn't check if the existing [src|sink] pad was removed + * first, so you might end up with more pads than wanted + * + * Returns: The #GstPad if everything went correctly, else NULL. + */ +GstPad * +gnl_object_ghost_pad (GnlObject * object, const gchar * name, GstPad * target) +{ + GstPadDirection dir = GST_PAD_DIRECTION (target); + GstPad *ghost; + + GST_DEBUG_OBJECT (object, "name:%s, target:%p", name, target); + + g_return_val_if_fail (target, FALSE); + g_return_val_if_fail ((dir != GST_PAD_UNKNOWN), FALSE); + + ghost = gnl_object_ghost_pad_no_target (object, name, dir); + if (!ghost) { + GST_WARNING_OBJECT (object, "Couldn't create ghostpad"); + return NULL; + } + + if (!(gnl_object_ghost_pad_set_target (object, ghost, target))) { + GST_WARNING_OBJECT (object, + "Couldn't set the target pad... removing ghostpad"); + gst_object_unref (ghost); + return NULL; + } + + GST_DEBUG_OBJECT (object, "activating ghostpad"); + /* activate pad */ + gst_pad_set_active (ghost, TRUE); + /* add it to element */ + if (!(gst_element_add_pad (GST_ELEMENT (object), ghost))) { + GST_WARNING ("couldn't add newly created ghostpad"); + return NULL; + } + + return ghost; +} + +/* + * gnl_object_ghost_pad_no_target: + * /!\ Doesn't add the pad to the GnlObject.... + */ +GstPad * +gnl_object_ghost_pad_no_target (GnlObject * object, const gchar * name, + GstPadDirection dir) +{ + GstPad *ghost; + GnlPadPrivate *priv; + + /* create a no_target ghostpad */ + ghost = gst_ghost_pad_new_no_target (name, dir); + if (!ghost) + return NULL; + + GST_DEBUG ("grabbing existing pad functions"); + + /* remember the existing ghostpad event/query/link/unlink functions */ + priv = g_slice_new0 (GnlPadPrivate); + priv->dir = dir; + priv->object = object; + + /* grab/replace event/query functions */ + GST_DEBUG_OBJECT (ghost, "Setting priv->eventfunc to %p", + GST_PAD_EVENTFUNC (ghost)); + priv->eventfunc = GST_PAD_EVENTFUNC (ghost); + priv->queryfunc = GST_PAD_QUERYFUNC (ghost); + + gst_pad_set_event_function (ghost, + GST_DEBUG_FUNCPTR (ghostpad_event_function)); + gst_pad_set_query_function (ghost, + GST_DEBUG_FUNCPTR (ghostpad_query_function)); + + gst_pad_set_element_private (ghost, priv); + control_internal_pad (ghost, object); + + return ghost; +} + +void +gnl_object_remove_ghost_pad (GnlObject * object, GstPad * ghost) +{ + GnlPadPrivate *priv; + + GST_DEBUG_OBJECT (object, "ghostpad %s:%s", GST_DEBUG_PAD_NAME (ghost)); + + priv = gst_pad_get_element_private (ghost); + gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), NULL); + gst_element_remove_pad (GST_ELEMENT (object), ghost); + if (priv) + g_slice_free (GnlPadPrivate, priv); +} + +gboolean +gnl_object_ghost_pad_set_target (GnlObject * object, GstPad * ghost, + GstPad * target) +{ + GnlPadPrivate *priv = gst_pad_get_element_private (ghost); + + g_return_val_if_fail (priv, FALSE); + + if (target) + GST_DEBUG_OBJECT (object, "setting target %s:%s on ghostpad", + GST_DEBUG_PAD_NAME (target)); + else + GST_DEBUG_OBJECT (object, "removing target from ghostpad"); + + /* set target */ + if (!(gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), target))) + return FALSE; + + return TRUE; +} + +void +gnl_init_ghostpad_category (void) +{ + GST_DEBUG_CATEGORY_INIT (gnlghostpad, "gnlghostpad", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin GhostPad"); + +} diff --git a/gnl/gnlghostpad.h b/gnl/gnlghostpad.h new file mode 100644 index 00000000..f43ab0d5 --- /dev/null +++ b/gnl/gnlghostpad.h @@ -0,0 +1,47 @@ +/* GStreamer + * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com> + * + * gnlghostpad.h: Header for helper ghostpad + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_GHOSTPAD_H__ +#define __GNL_GHOSTPAD_H__ + +#include <gst/gst.h> + +#include "gnltypes.h" + +G_BEGIN_DECLS + +GstPad *gnl_object_ghost_pad (GnlObject * object, + const gchar * name, GstPad * target); + +GstPad *gnl_object_ghost_pad_no_target (GnlObject * object, + const gchar * name, GstPadDirection dir); + +gboolean gnl_object_ghost_pad_set_target (GnlObject * object, + GstPad * ghost, GstPad * target); + +void gnl_object_remove_ghost_pad (GnlObject * object, GstPad * ghost); + +void gnl_init_ghostpad_category (void); + +G_END_DECLS + +#endif /* __GNL_GHOSTPAD_H__ */ diff --git a/gnl/gnlmarshal.list b/gnl/gnlmarshal.list new file mode 100644 index 00000000..67c63946 --- /dev/null +++ b/gnl/gnlmarshal.list @@ -0,0 +1,2 @@ +VOID:OBJECT,UINT + diff --git a/gnl/gnlobject.c b/gnl/gnlobject.c new file mode 100644 index 00000000..3052ea9b --- /dev/null +++ b/gnl/gnlobject.c @@ -0,0 +1,656 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include "gnl.h" + +/** + * SECTION:gnlobject + * @short_description: Base class for GNonLin elements + * + * <refsect2> + * <para> + * GnlObject encapsulates default behaviour and implements standard + * properties provided by all the GNonLin elements. + * </para> + * </refsect2> + * + */ + + +GST_DEBUG_CATEGORY_STATIC (gnlobject_debug); +#define GST_CAT_DEFAULT gnlobject_debug + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gnlobject_debug, "gnlobject", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Object base class"); +#define gnl_object_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GnlObject, gnl_object, GST_TYPE_BIN, _do_init); + +/**************************************************** + * Helper macros * + ****************************************************/ +#define CHECK_AND_SET(PROPERTY, property, prop_str, print_format) \ +{ \ +GstObject *parent = gst_object_get_parent (GST_OBJECT (object)); \ +if (parent == NULL && !GNL_OBJECT_IS_COMPOSITION (object)) { \ + GST_INFO_OBJECT (object, "Not in a composition yet, " \ + "not commiting" prop_str); \ +} else if (object->pending_##property != object->property) { \ + object->property = object->pending_##property; \ + GST_DEBUG_OBJECT(object, "Setting " prop_str " to %" \ + print_format, object->property); \ +} else \ + GST_DEBUG_OBJECT(object, "Nothing to do for " prop_str); \ +if (parent) \ + gst_object_unref (parent); \ +} + +#define SET_PENDING_VALUE(property, property_str, type, print_format) \ +gnlobject->pending_##property = g_value_get_##type (value); \ +if (gnlobject->property != gnlobject->pending_##property) { \ + GST_DEBUG_OBJECT(object, "Setting pending " property_str " to %" \ + print_format, gnlobject->pending_##property); \ + gnl_object_set_commit_needed (gnlobject); \ +} else \ + GST_DEBUG_OBJECT(object, "Pending " property_str " did not change"); + +enum +{ + PROP_0, + PROP_START, + PROP_DURATION, + PROP_STOP, + PROP_INPOINT, + PROP_PRIORITY, + PROP_ACTIVE, + PROP_CAPS, + PROP_EXPANDABLE, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static void gnl_object_dispose (GObject * object); + +static void gnl_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gnl_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn gnl_object_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gnl_object_prepare_func (GnlObject * object); +static gboolean gnl_object_cleanup_func (GnlObject * object); +static gboolean gnl_object_commit_func (GnlObject * object, gboolean recurse); + +static GstStateChangeReturn gnl_object_prepare (GnlObject * object); + +static void +gnl_object_class_init (GnlObjectClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GnlObjectClass *gnlobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gnlobject_class = (GnlObjectClass *) klass; + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_object_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_object_get_property); + gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_object_dispose); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gnl_object_change_state); + + gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_object_prepare_func); + gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_object_cleanup_func); + gnlobject_class->commit_signal_handler = + GST_DEBUG_FUNCPTR (gnl_object_commit); + gnlobject_class->commit = GST_DEBUG_FUNCPTR (gnl_object_commit_func); + + /** + * GnlObject:start + * + * The start position relative to the parent in nanoseconds. + */ + properties[PROP_START] = g_param_spec_uint64 ("start", "Start", + "The start position relative to the parent (in nanoseconds)", + 0, G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_START, + properties[PROP_START]); + + /** + * GnlObject:duration + * + * The outgoing duration in nanoseconds. + */ + properties[PROP_DURATION] = g_param_spec_int64 ("duration", "Duration", + "Outgoing duration (in nanoseconds)", 0, G_MAXINT64, 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_DURATION, + properties[PROP_DURATION]); + + /** + * GnlObject:stop + * + * The stop position relative to the parent in nanoseconds. + * + * This value is computed based on the values of start and duration. + */ + properties[PROP_STOP] = g_param_spec_uint64 ("stop", "Stop", + "The stop position relative to the parent (in nanoseconds)", + 0, G_MAXUINT64, 0, G_PARAM_READABLE); + g_object_class_install_property (gobject_class, PROP_STOP, + properties[PROP_STOP]); + + /** + * GnlObject:inpoint + * + * The media start position in nanoseconds. + * + * Also called 'in-point' in video-editing, this corresponds to + * what position in the 'contained' object we should start outputting from. + */ + properties[PROP_INPOINT] = + g_param_spec_uint64 ("inpoint", "Media start", + "The media start position (in nanoseconds)", 0, G_MAXUINT64, + GST_CLOCK_TIME_NONE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_INPOINT, + properties[PROP_INPOINT]); + + /** + * GnlObject:priority + * + * The priority of the object in the container. + * + * The highest priority is 0, meaning this object will be selected over + * any other between start and stop. + * + * The lowest priority is G_MAXUINT32. + * + * Objects whose priority is (-1) will be considered as 'default' objects + * in GnlComposition and their start/stop values will be modified as to + * fit the whole duration of the composition. + */ + properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", + "The priority of the object (0 = highest priority)", 0, G_MAXUINT, 0, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_PRIORITY, + properties[PROP_PRIORITY]); + + /** + * GnlObject:active + * + * Indicates whether this object should be used by its container. + * + * Set to #TRUE to temporarily disable this object in a #GnlComposition. + */ + properties[PROP_ACTIVE] = g_param_spec_boolean ("active", "Active", + "Use this object in the GnlComposition", TRUE, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_ACTIVE, + properties[PROP_ACTIVE]); + + /** + * GnlObject:caps + * + * Caps used to filter/choose the output stream. + * + * If the controlled object produces several stream, you can set this + * property to choose a specific stream. + * + * If nothing is specified then a source pad will be chosen at random. + */ + properties[PROP_CAPS] = g_param_spec_boxed ("caps", "Caps", + "Caps used to filter/choose the output stream", + GST_TYPE_CAPS, G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_CAPS, + properties[PROP_CAPS]); + + /** + * GnlObject:expandable + * + * Indicates whether this object should expand to the full duration of its + * container #GnlComposition. + */ + properties[PROP_EXPANDABLE] = + g_param_spec_boolean ("expandable", "Expandable", + "Expand to the full duration of the container composition", FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_EXPANDABLE, + properties[PROP_EXPANDABLE]); +} + +static void +gnl_object_init (GnlObject * object) +{ + object->start = object->pending_start = 0; + object->duration = object->pending_duration = 0; + object->stop = 0; + + object->inpoint = object->pending_inpoint = GST_CLOCK_TIME_NONE; + object->priority = object->pending_priority = 0; + object->active = object->pending_active = TRUE; + + object->caps = gst_caps_new_any (); + + object->segment_rate = 1.0; + object->segment_start = -1; + object->segment_stop = -1; +} + +static void +gnl_object_dispose (GObject * object) +{ + GnlObject *gnl = (GnlObject *) object; + + if (gnl->caps) { + gst_caps_unref (gnl->caps); + gnl->caps = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/** + * gnl_object_to_media_time: + * @object: a #GnlObject + * @objecttime: The #GstClockTime we want to convert + * @mediatime: A pointer on a #GstClockTime to fill + * + * Converts a #GstClockTime from the object (container) context to the media context + * + * Returns: TRUE if @objecttime was within the limits of the @object start/stop time, + * FALSE otherwise + */ +gboolean +gnl_object_to_media_time (GnlObject * object, GstClockTime otime, + GstClockTime * mtime) +{ + g_return_val_if_fail (mtime, FALSE); + + GST_DEBUG_OBJECT (object, "ObjectTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (otime)); + + GST_DEBUG_OBJECT (object, + "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] " + "Media start: %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); + + /* limit check */ + if (G_UNLIKELY ((otime < object->start))) { + GST_DEBUG_OBJECT (object, "ObjectTime is before start"); + *mtime = (object->inpoint == GST_CLOCK_TIME_NONE) ? 0 : object->inpoint; + return FALSE; + } + + if (G_UNLIKELY ((otime >= object->stop))) { + GST_DEBUG_OBJECT (object, "ObjectTime is after stop"); + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) + *mtime = object->inpoint + object->duration; + else + *mtime = object->stop - object->start; + return FALSE; + } + + if (G_UNLIKELY (object->inpoint == GST_CLOCK_TIME_NONE)) { + /* no time shifting, for live sources ? */ + *mtime = otime - object->start; + } else { + *mtime = otime - object->start + object->inpoint; + } + + GST_DEBUG_OBJECT (object, "Returning MediaTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (*mtime)); + + return TRUE; +} + +/** + * gnl_media_to_object_time: + * @object: The #GnlObject + * @mediatime: The #GstClockTime we want to convert + * @objecttime: A pointer on a #GstClockTime to fill + * + * Converts a #GstClockTime from the media context to the object (container) context + * + * Returns: TRUE if @objecttime was within the limits of the @object media start/stop time, + * FALSE otherwise + */ + +gboolean +gnl_media_to_object_time (GnlObject * object, GstClockTime mtime, + GstClockTime * otime) +{ + g_return_val_if_fail (otime, FALSE); + + GST_DEBUG_OBJECT (object, "MediaTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (mtime)); + + GST_DEBUG_OBJECT (object, + "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] " + "inpoint %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), + GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); + + + /* limit check */ + if (G_UNLIKELY ((object->inpoint != GST_CLOCK_TIME_NONE) + && (mtime < object->inpoint))) { + GST_DEBUG_OBJECT (object, "media time is before inpoint, forcing to start"); + *otime = object->start; + return FALSE; + } + + if (G_LIKELY (object->inpoint != GST_CLOCK_TIME_NONE)) { + *otime = mtime - object->inpoint + object->start; + } else + *otime = mtime + object->start; + + GST_DEBUG_OBJECT (object, "Returning ObjectTime : %" GST_TIME_FORMAT, + GST_TIME_ARGS (*otime)); + return TRUE; +} + +static gboolean +gnl_object_prepare_func (GnlObject * object) +{ + GST_DEBUG_OBJECT (object, "default prepare function, returning TRUE"); + + return TRUE; +} + +static GstStateChangeReturn +gnl_object_prepare (GnlObject * object) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (object, "preparing"); + + if (!(GNL_OBJECT_GET_CLASS (object)->prepare (object))) + ret = GST_STATE_CHANGE_FAILURE; + + GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret); + + return ret; +} + +static gboolean +gnl_object_cleanup_func (GnlObject * object) +{ + GST_DEBUG_OBJECT (object, "default cleanup function, returning TRUE"); + + return TRUE; +} + +static GstStateChangeReturn +gnl_object_cleanup (GnlObject * object) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (object, "cleaning-up"); + + if (!(GNL_OBJECT_GET_CLASS (object)->cleanup (object))) + ret = GST_STATE_CHANGE_FAILURE; + + GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret); + + return ret; +} + +void +gnl_object_set_caps (GnlObject * object, const GstCaps * caps) +{ + if (object->caps) + gst_caps_unref (object->caps); + + object->caps = gst_caps_copy (caps); +} + +static inline void +_update_stop (GnlObject * gnlobject) +{ + /* check if start/duration has changed */ + + if ((gnlobject->pending_start + gnlobject->pending_duration) != + gnlobject->stop) { + gnlobject->stop = gnlobject->pending_start + gnlobject->pending_duration; + + GST_LOG_OBJECT (gnlobject, + "Updating stop value : %" GST_TIME_FORMAT " [start:%" GST_TIME_FORMAT + ", duration:%" GST_TIME_FORMAT "]", GST_TIME_ARGS (gnlobject->stop), + GST_TIME_ARGS (gnlobject->pending_start), + GST_TIME_ARGS (gnlobject->pending_duration)); + g_object_notify_by_pspec (G_OBJECT (gnlobject), properties[PROP_STOP]); + } +} + +static void +update_values (GnlObject * object) +{ + CHECK_AND_SET (START, start, "start", G_GUINT64_FORMAT); + CHECK_AND_SET (INPOINT, inpoint, "inpoint", G_GUINT64_FORMAT); + CHECK_AND_SET (DURATION, duration, "duration", G_GINT64_FORMAT); + CHECK_AND_SET (PRIORITY, priority, "priority", G_GUINT32_FORMAT); + CHECK_AND_SET (ACTIVE, active, "active", G_GUINT32_FORMAT); + + _update_stop (object); +} + +static gboolean +gnl_object_commit_func (GnlObject * object, gboolean recurse) +{ + GST_INFO_OBJECT (object, "Commiting object changed"); + + if (object->commit_needed == FALSE) { + GST_INFO_OBJECT (object, "No changes to commit"); + + return FALSE; + } + + update_values (object); + + GST_INFO_OBJECT (object, "Done commiting"); + + return TRUE; +} + +static void +gnl_object_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GnlObject *gnlobject = (GnlObject *) object; + + g_return_if_fail (GNL_IS_OBJECT (object)); + + GST_OBJECT_LOCK (object); + switch (prop_id) { + case PROP_START: + SET_PENDING_VALUE (start, "start", uint64, G_GUINT64_FORMAT); + break; + case PROP_DURATION: + SET_PENDING_VALUE (duration, "duration", int64, G_GINT64_FORMAT); + break; + case PROP_INPOINT: + SET_PENDING_VALUE (inpoint, "inpoint", uint64, G_GUINT64_FORMAT); + break; + case PROP_PRIORITY: + SET_PENDING_VALUE (priority, "priority", uint, G_GUINT32_FORMAT); + break; + case PROP_ACTIVE: + SET_PENDING_VALUE (active, "active", boolean, G_GUINT32_FORMAT); + break; + case PROP_CAPS: + gnl_object_set_caps (gnlobject, gst_value_get_caps (value)); + break; + case PROP_EXPANDABLE: + if (g_value_get_boolean (value)) + GST_OBJECT_FLAG_SET (gnlobject, GNL_OBJECT_EXPANDABLE); + else + GST_OBJECT_FLAG_UNSET (gnlobject, GNL_OBJECT_EXPANDABLE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (object); + + _update_stop (gnlobject); +} + +static void +gnl_object_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GnlObject *gnlobject = (GnlObject *) object; + + switch (prop_id) { + case PROP_START: + g_value_set_uint64 (value, gnlobject->pending_start); + break; + case PROP_DURATION: + g_value_set_int64 (value, gnlobject->pending_duration); + break; + case PROP_STOP: + g_value_set_uint64 (value, gnlobject->stop); + break; + case PROP_INPOINT: + g_value_set_uint64 (value, gnlobject->pending_inpoint); + break; + case PROP_PRIORITY: + g_value_set_uint (value, gnlobject->pending_priority); + break; + case PROP_ACTIVE: + g_value_set_boolean (value, gnlobject->pending_active); + break; + case PROP_CAPS: + gst_value_set_caps (value, gnlobject->caps); + break; + case PROP_EXPANDABLE: + g_value_set_boolean (value, GNL_OBJECT_IS_EXPANDABLE (object)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gnl_object_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + { + GstObject *parent = gst_object_get_parent (GST_OBJECT (element)); + + /* Going to READY and if we are not in a composition, we need to make + * sure that the object positioning state is properly commited */ + if (parent) { + if (!GNL_OBJECT_IS_COMPOSITION (parent) && + !GNL_OBJECT_IS_COMPOSITION (GNL_OBJECT (element))) { + GST_DEBUG ("Adding gnlobject to something that is not a composition," + " commiting ourself"); + gnl_object_commit (GNL_OBJECT (element), FALSE); + } + + gst_object_unref (parent); + } + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gnl_object_commit (GNL_OBJECT (element), FALSE); + if (gnl_object_prepare (GNL_OBJECT (element)) == GST_STATE_CHANGE_FAILURE) { + ret = GST_STATE_CHANGE_FAILURE; + goto beach; + } + break; + default: + break; + } + + GST_DEBUG_OBJECT (element, "Calling parent change_state"); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + GST_DEBUG_OBJECT (element, "Return from parent change_state was %d", ret); + + if (ret == GST_STATE_CHANGE_FAILURE) + goto beach; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* cleanup gnlobject */ + if (gnl_object_cleanup (GNL_OBJECT (element)) == GST_STATE_CHANGE_FAILURE) + ret = GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + +beach: + return ret; +} + +void +gnl_object_set_commit_needed (GnlObject * object) +{ + if (G_UNLIKELY (object->commiting)) { + GST_WARNING_OBJECT (object, + "Trying to set 'commit-needed' while commiting"); + + return; + } + + GST_DEBUG_OBJECT (object, "Setting 'commit_needed'"); + object->commit_needed = TRUE; +} + +gboolean +gnl_object_commit (GnlObject * object, gboolean recurse) +{ + gboolean ret; + + GST_DEBUG_OBJECT (object, "Commiting object state"); + + object->commiting = TRUE; + ret = GNL_OBJECT_GET_CLASS (object)->commit (object, recurse); + object->commiting = FALSE; + + return ret; + +} + +void +gnl_object_reset (GnlObject * object) +{ + GST_INFO_OBJECT (object, "Resetting child timing values to default"); + + object->start = 0; + object->duration = 0; + object->stop = 0; + object->inpoint = GST_CLOCK_TIME_NONE; + object->priority = 0; + object->active = TRUE; +} diff --git a/gnl/gnlobject.h b/gnl/gnlobject.h new file mode 100644 index 00000000..4ba7dd82 --- /dev/null +++ b/gnl/gnlobject.h @@ -0,0 +1,160 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * gnlobject.h: Header for base GnlObject + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_OBJECT_H__ +#define __GNL_OBJECT_H__ + +#include <gst/gst.h> + +#include "gnltypes.h" + +G_BEGIN_DECLS +#define GNL_TYPE_OBJECT \ + (gnl_object_get_type()) +#define GNL_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_OBJECT,GnlObject)) +#define GNL_OBJECT_CAST(obj) ((GnlObject*) (obj)) +#define GNL_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_OBJECT,GnlObjectClass)) +#define GNL_OBJECT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GNL_TYPE_OBJECT, GnlObjectClass)) +#define GNL_IS_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_OBJECT)) +#define GNL_IS_OBJECT_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_OBJECT)) + +/** + * GnlObjectFlags: + * @GNL_OBJECT_IS_SOURCE: + * @GNL_OBJECT_IS_OPERATION: + * @GNL_OBJECT_IS_EXPANDABLE: The #GnlObject start/stop will extend accross the full composition. + * @GNL_OBJECT_LAST_FLAG: +*/ + +typedef enum +{ + GNL_OBJECT_SOURCE = (GST_BIN_FLAG_LAST << 0), + GNL_OBJECT_OPERATION = (GST_BIN_FLAG_LAST << 1), + GNL_OBJECT_EXPANDABLE = (GST_BIN_FLAG_LAST << 2), + GNL_OBJECT_COMPOSITION = (GST_BIN_FLAG_LAST << 3), + /* padding */ + GNL_OBJECT_LAST_FLAG = (GST_BIN_FLAG_LAST << 5) +} GnlObjectFlags; + + +#define GNL_OBJECT_IS_SOURCE(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_SOURCE)) +#define GNL_OBJECT_IS_OPERATION(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_OPERATION)) +#define GNL_OBJECT_IS_EXPANDABLE(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_EXPANDABLE)) +#define GNL_OBJECT_IS_COMPOSITION(obj) \ + (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_COMPOSITION)) + +/* For internal usage only */ +#define GNL_OBJECT_START(obj) (GNL_OBJECT_CAST (obj)->start) +#define GNL_OBJECT_STOP(obj) (GNL_OBJECT_CAST (obj)->stop) +#define GNL_OBJECT_DURATION(obj) (GNL_OBJECT_CAST (obj)->duration) +#define GNL_OBJECT_INPOINT(obj) (GNL_OBJECT_CAST (obj)->inpoint) +#define GNL_OBJECT_PRIORITY(obj) (GNL_OBJECT_CAST (obj)->priority) + +#define GNL_OBJECT_IS_COMMITING(obj) (GNL_OBJECT_CAST (obj)->commiting) + +struct _GnlObject +{ + GstBin parent; + + /* Time positionning */ + GstClockTime start; + GstClockTime inpoint; + GstClockTimeDiff duration; + + /* Pending time positionning + * Should be == GST_CLOCK_TIME_NONE when nothing to do + */ + GstClockTime pending_start; + GstClockTime pending_inpoint; + GstClockTimeDiff pending_duration; + guint32 pending_priority; + gboolean pending_active; + + gboolean commit_needed; + gboolean commiting; /* Set to TRUE during the commiting time only */ + + gboolean expandable; + + /* read-only */ + GstClockTime stop; + + /* priority in parent */ + guint32 priority; + + /* active in parent */ + gboolean active; + + /* Filtering caps */ + GstCaps *caps; + + /* current segment seek <RO> */ + gdouble segment_rate; + GstSeekFlags segment_flags; + gint64 segment_start; + gint64 segment_stop; +}; + +struct _GnlObjectClass +{ + GstBinClass parent_class; + + /* Signal method handler */ + gboolean (*commit_signal_handler) (GnlObject * object, gboolean recurse); + + /* virtual methods for subclasses */ + gboolean (*prepare) (GnlObject * object); + gboolean (*cleanup) (GnlObject * object); + gboolean (*commit) (GnlObject * object, gboolean recurse); +}; + +GType gnl_object_get_type (void); + +gboolean +gnl_object_to_media_time (GnlObject * object, GstClockTime otime, + GstClockTime * mtime); + +gboolean +gnl_media_to_object_time (GnlObject * object, GstClockTime mtime, + GstClockTime * otime); + +void +gnl_object_set_caps (GnlObject * object, const GstCaps * caps); + +void +gnl_object_set_commit_needed (GnlObject *object); + +gboolean +gnl_object_commit (GnlObject *object, gboolean recurse); + +void +gnl_object_reset (GnlObject *object); +G_END_DECLS +#endif /* __GNL_OBJECT_H__ */ diff --git a/gnl/gnloperation.c b/gnl/gnloperation.c new file mode 100644 index 00000000..dcc811f5 --- /dev/null +++ b/gnl/gnloperation.c @@ -0,0 +1,792 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" + +/** + * SECTION:element-gnloperation + * + * <refsect2> + * <para> + * A GnlOperation performs a transformation or mixing operation on the + * data from one or more #GnlSources, which is used to implement filters or + * effects. + * </para> + * </refsect2> + */ + +static GstStaticPadTemplate gnl_operation_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gnl_operation_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gnloperation); +#define GST_CAT_DEFAULT gnloperation + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gnloperation, "gnloperation", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Operation element"); +#define gnl_operation_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GnlOperation, gnl_operation, GNL_TYPE_OBJECT, + _do_init); + +enum +{ + ARG_0, + ARG_SINKS, +}; + +enum +{ + INPUT_PRIORITY_CHANGED, + LAST_SIGNAL +}; + +static guint gnl_operation_signals[LAST_SIGNAL] = { 0 }; + +static void gnl_operation_dispose (GObject * object); + +static void gnl_operation_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gnl_operation_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gnl_operation_prepare (GnlObject * object); +static gboolean gnl_operation_cleanup (GnlObject * object); + +static gboolean gnl_operation_add_element (GstBin * bin, GstElement * element); +static gboolean gnl_operation_remove_element (GstBin * bin, + GstElement * element); + +static GstPad *gnl_operation_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps); +static void gnl_operation_release_pad (GstElement * element, GstPad * pad); + +static void synchronize_sinks (GnlOperation * operation); +static gboolean remove_sink_pad (GnlOperation * operation, GstPad * sinkpad); + +static void +gnl_operation_class_init (GnlOperationClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstBinClass *gstbin_class = (GstBinClass *) klass; + + GstElementClass *gstelement_class = (GstElementClass *) klass; + GnlObjectClass *gnlobject_class = (GnlObjectClass *) klass; + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin Operation", + "Filter/Editor", + "Encapsulates filters/effects for use with GNL Objects", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>"); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_operation_dispose); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_operation_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_operation_get_property); + + /** + * GnlOperation:sinks: + * + * Specifies the number of sink pads the operation should provide. + * If the sinks property is -1 (the default) pads are only created as + * demanded via get_request_pad() calls on the element. + */ + g_object_class_install_property (gobject_class, ARG_SINKS, + g_param_spec_int ("sinks", "Sinks", + "Number of input sinks (-1 for automatic handling)", -1, G_MAXINT, -1, + G_PARAM_READWRITE)); + + /** + * GnlOperation:input-priority-changed: + * @pad: The operation's input pad whose priority changed. + * @priority: The new priority + * + * Signals that the @priority of the stream being fed to the given @pad + * might have changed. + */ + gnl_operation_signals[INPUT_PRIORITY_CHANGED] = + g_signal_new ("input-priority-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnlOperationClass, + input_priority_changed), NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 2, GST_TYPE_PAD, G_TYPE_UINT); + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gnl_operation_request_new_pad); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gnl_operation_release_pad); + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_operation_add_element); + gstbin_class->remove_element = + GST_DEBUG_FUNCPTR (gnl_operation_remove_element); + + gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_operation_prepare); + gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_operation_cleanup); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gnl_operation_src_template)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gnl_operation_sink_template)); + +} + +static void +gnl_operation_dispose (GObject * object) +{ + GnlOperation *oper = (GnlOperation *) object; + + GST_DEBUG_OBJECT (object, "Disposing of source pad"); + if (oper->ghostpad) { + gnl_object_remove_ghost_pad (GNL_OBJECT (oper), oper->ghostpad); + oper->ghostpad = NULL; + } + + GST_DEBUG_OBJECT (object, "Disposing of sink pad(s)"); + while (oper->sinks) { + GstPad *ghost = (GstPad *) oper->sinks->data; + remove_sink_pad (oper, ghost); + } + + GST_DEBUG_OBJECT (object, "Done, calling parent class ::dispose()"); + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gnl_operation_reset (GnlOperation * operation) +{ + operation->num_sinks = 1; + operation->realsinks = 0; + operation->next_base_time = 0; +} + +static void +gnl_operation_init (GnlOperation * operation) +{ + gnl_operation_reset (operation); + operation->ghostpad = NULL; + operation->element = NULL; +} + +static gboolean +element_is_valid_filter (GstElement * element, gboolean * isdynamic) +{ + gboolean havesink = FALSE; + gboolean havesrc = FALSE; + gboolean done = FALSE; + GstIterator *pads; + GValue item = { 0, }; + + if (isdynamic) + *isdynamic = FALSE; + + pads = gst_element_iterate_pads (element); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + + if (gst_pad_get_direction (pad) == GST_PAD_SRC) + havesrc = TRUE; + else if (gst_pad_get_direction (pad) == GST_PAD_SINK) + havesink = TRUE; + + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + havesrc = FALSE; + havesink = FALSE; + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + /* just look at the element's class, not the factory, since there might + * not be a factory (in case of python elements) or the factory is the + * wrong one (in case of a GstBin sub-class) and doesn't have complete + * information. */ + { + GList *tmp = + gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS + (element)); + + while (tmp) { + GstPadTemplate *template = (GstPadTemplate *) tmp->data; + + if (template->direction == GST_PAD_SRC) + havesrc = TRUE; + else if (template->direction == GST_PAD_SINK) { + if (!havesink && (template->presence == GST_PAD_REQUEST) && isdynamic) + *isdynamic = TRUE; + havesink = TRUE; + } + tmp = tmp->next; + } + } + return (havesink && havesrc); +} + +/* + * get_src_pad: + * element: a #GstElement + * + * Returns: The src pad for the given element. A reference was added to the + * returned pad, remove it when you don't need that pad anymore. + * Returns NULL if there's no source pad. + */ + +static GstPad * +get_src_pad (GstElement * element) +{ + GstIterator *it; + GstIteratorResult itres; + GValue item = { 0, }; + GstPad *srcpad = NULL; + + it = gst_element_iterate_src_pads (element); + itres = gst_iterator_next (it, &item); + if (itres != GST_ITERATOR_OK) { + GST_DEBUG ("%s doesn't have a src pad !", GST_ELEMENT_NAME (element)); + } else { + srcpad = g_value_get_object (&item); + gst_object_ref (srcpad); + } + g_value_reset (&item); + gst_iterator_free (it); + + return srcpad; +} + +/* get_nb_static_sinks: + * + * Returns : The number of static sink pads of the controlled element. + */ +static guint +get_nb_static_sinks (GnlOperation * oper) +{ + GstIterator *sinkpads; + gboolean done = FALSE; + guint nbsinks = 0; + GValue item = { 0, }; + + sinkpads = gst_element_iterate_sink_pads (oper->element); + + while (!done) { + switch (gst_iterator_next (sinkpads, &item)) { + case GST_ITERATOR_OK:{ + nbsinks++; + g_value_unset (&item); + } + break; + case GST_ITERATOR_RESYNC: + nbsinks = 0; + gst_iterator_resync (sinkpads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_reset (&item); + gst_iterator_free (sinkpads); + + GST_DEBUG ("We found %d static sinks", nbsinks); + + return nbsinks; +} + +static gboolean +gnl_operation_add_element (GstBin * bin, GstElement * element) +{ + GnlOperation *operation = (GnlOperation *) bin; + gboolean res = FALSE; + gboolean isdynamic; + + GST_DEBUG_OBJECT (bin, "element:%s", GST_ELEMENT_NAME (element)); + + if (operation->element) { + GST_WARNING_OBJECT (operation, + "We already control an element : %s , remove it first", + GST_OBJECT_NAME (operation->element)); + } else { + if (!element_is_valid_filter (element, &isdynamic)) { + GST_WARNING_OBJECT (operation, + "Element %s is not a valid filter element", + GST_ELEMENT_NAME (element)); + } else { + if ((res = GST_BIN_CLASS (parent_class)->add_element (bin, element))) { + GstPad *srcpad; + + srcpad = get_src_pad (element); + if (!srcpad) + return FALSE; + + operation->element = element; + operation->dynamicsinks = isdynamic; + + /* Source ghostpad */ + if (operation->ghostpad) + gnl_object_ghost_pad_set_target (GNL_OBJECT (operation), + operation->ghostpad, srcpad); + else + operation->ghostpad = gnl_object_ghost_pad (GNL_OBJECT (operation), + GST_PAD_NAME (srcpad), srcpad); + + /* Remove the reference get_src_pad gave us */ + gst_object_unref (srcpad); + + /* Figure out number of static sink pads */ + operation->num_sinks = get_nb_static_sinks (operation); + + /* Finally sync the ghostpads with the real pads */ + synchronize_sinks (operation); + } + } + } + + return res; +} + +static gboolean +gnl_operation_remove_element (GstBin * bin, GstElement * element) +{ + GnlOperation *operation = (GnlOperation *) bin; + gboolean res = FALSE; + + if (operation->element) { + if ((res = GST_BIN_CLASS (parent_class)->remove_element (bin, element))) + operation->element = NULL; + } else { + GST_WARNING_OBJECT (bin, + "Element %s is not the one controlled by this operation", + GST_ELEMENT_NAME (element)); + } + return res; +} + +static void +gnl_operation_set_sinks (GnlOperation * operation, guint sinks) +{ + /* FIXME : Check if sinkpad of element is on-demand .... */ + + operation->num_sinks = sinks; + synchronize_sinks (operation); +} + +static void +gnl_operation_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GnlOperation *operation = (GnlOperation *) object; + + switch (prop_id) { + case ARG_SINKS: + gnl_operation_set_sinks (operation, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gnl_operation_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GnlOperation *operation = (GnlOperation *) object; + + switch (prop_id) { + case ARG_SINKS: + g_value_set_int (value, operation->num_sinks); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* + * Returns the first unused sink pad of the controlled element. + * Only use with static element. Unref after usage. + * Returns NULL if there's no more unused sink pads. + */ +static GstPad * +get_unused_static_sink_pad (GnlOperation * operation) +{ + GstIterator *pads; + gboolean done = FALSE; + GValue item = { 0, }; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + pads = gst_element_iterate_pads (operation->element); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + + if (gst_pad_get_direction (pad) == GST_PAD_SINK) { + GList *tmp; + gboolean istaken = FALSE; + + /* 1. figure out if one of our sink ghostpads has this pad as target */ + for (tmp = operation->sinks; tmp; tmp = tmp->next) { + GstGhostPad *gpad = (GstGhostPad *) tmp->data; + GstPad *target = gst_ghost_pad_get_target (gpad); + + GST_LOG ("found ghostpad with target %s:%s", + GST_DEBUG_PAD_NAME (target)); + + if (target) { + if (target == pad) + istaken = TRUE; + gst_object_unref (target); + } + } + + /* 2. if not taken, return that pad */ + if (!istaken) { + gst_object_ref (pad); + ret = pad; + done = TRUE; + } + } + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + if (ret) + GST_DEBUG_OBJECT (operation, "found free sink pad %s:%s", + GST_DEBUG_PAD_NAME (ret)); + else + GST_DEBUG_OBJECT (operation, "Couldn't find an unused sink pad"); + + return ret; +} + +GstPad * +get_unlinked_sink_ghost_pad (GnlOperation * operation) +{ + GstIterator *pads; + gboolean done = FALSE; + GValue item = { 0, }; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + pads = gst_element_iterate_sink_pads ((GstElement *) operation); + + while (!done) { + switch (gst_iterator_next (pads, &item)) { + case GST_ITERATOR_OK: + { + GstPad *pad = g_value_get_object (&item); + GstPad *peer = gst_pad_get_peer (pad); + + if (peer == NULL) { + ret = pad; + gst_object_ref (ret); + done = TRUE; + } else { + gst_object_unref (peer); + } + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (pads); + break; + default: + /* ERROR and DONE */ + done = TRUE; + break; + } + } + + g_value_unset (&item); + gst_iterator_free (pads); + + if (ret) + GST_DEBUG_OBJECT (operation, "found unlinked ghost sink pad %s:%s", + GST_DEBUG_PAD_NAME (ret)); + else + GST_DEBUG_OBJECT (operation, "Couldn't find an unlinked ghost sink pad"); + + return ret; + +} + +static GstPad * +get_request_sink_pad (GnlOperation * operation) +{ + GstPad *pad = NULL; + GList *templates; + + if (!operation->element) + return NULL; + + templates = gst_element_class_get_pad_template_list + (GST_ELEMENT_GET_CLASS (operation->element)); + + for (; templates; templates = templates->next) { + GstPadTemplate *templ = (GstPadTemplate *) templates->data; + + GST_LOG_OBJECT (operation->element, "Trying template %s", + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + + if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SINK) && + (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) { + pad = + gst_element_get_request_pad (operation->element, + GST_PAD_TEMPLATE_NAME_TEMPLATE (templ)); + if (pad) + break; + } + } + + return pad; +} + +static GstPad * +add_sink_pad (GnlOperation * operation) +{ + GstPad *gpad = NULL; + GstPad *ret = NULL; + + if (!operation->element) + return NULL; + + /* FIXME : implement */ + GST_LOG_OBJECT (operation, "element:%s , dynamicsinks:%d", + GST_ELEMENT_NAME (operation->element), operation->dynamicsinks); + + + if (!operation->dynamicsinks) { + /* static sink pads */ + ret = get_unused_static_sink_pad (operation); + if (ret) { + gpad = gnl_object_ghost_pad ((GnlObject *) operation, GST_PAD_NAME (ret), + ret); + gst_object_unref (ret); + } + } + + if (!gpad) { + /* request sink pads */ + ret = get_request_sink_pad (operation); + if (ret) { + gpad = gnl_object_ghost_pad ((GnlObject *) operation, GST_PAD_NAME (ret), + ret); + gst_object_unref (ret); + } + } + + if (gpad) { + operation->sinks = g_list_append (operation->sinks, gpad); + operation->realsinks++; + GST_DEBUG ("Created new pad %s:%s ghosting %s:%s", + GST_DEBUG_PAD_NAME (gpad), GST_DEBUG_PAD_NAME (ret)); + } else { + GST_WARNING ("Couldn't find a usable sink pad!"); + } + + return gpad; +} + +static gboolean +remove_sink_pad (GnlOperation * operation, GstPad * sinkpad) +{ + gboolean ret = TRUE; + + GST_DEBUG ("sinkpad %s:%s", GST_DEBUG_PAD_NAME (sinkpad)); + + /* + We can't remove any random pad. + We should remove an unused pad ... which is hard to figure out in a + thread-safe way. + */ + + if ((sinkpad == NULL) && operation->dynamicsinks) { + /* Find an unlinked sinkpad */ + if ((sinkpad = get_unlinked_sink_ghost_pad (operation)) == NULL) { + ret = FALSE; + goto beach; + } + } + + if (sinkpad) { + GstPad *target = gst_ghost_pad_get_target ((GstGhostPad *) sinkpad); + + if (target) { + /* release the target pad */ + gnl_object_ghost_pad_set_target ((GnlObject *) operation, sinkpad, NULL); + if (operation->dynamicsinks) + gst_element_release_request_pad (operation->element, target); + gst_object_unref (target); + } + operation->sinks = g_list_remove (operation->sinks, sinkpad); + gnl_object_remove_ghost_pad ((GnlObject *) operation, sinkpad); + operation->realsinks--; + } + +beach: + return ret; +} + +static void +synchronize_sinks (GnlOperation * operation) +{ + + GST_DEBUG_OBJECT (operation, "num_sinks:%d , realsinks:%d, dynamicsinks:%d", + operation->num_sinks, operation->realsinks, operation->dynamicsinks); + + if (operation->num_sinks == operation->realsinks) + return; + + if (operation->num_sinks > operation->realsinks) { + while (operation->num_sinks > operation->realsinks) /* Add pad */ + if (!(add_sink_pad (operation))) { + break; + } + } else { + /* Remove pad */ + /* FIXME, which one do we remove ? :) */ + while (operation->num_sinks < operation->realsinks) + if (!remove_sink_pad (operation, NULL)) + break; + } +} + +static gboolean +gnl_operation_prepare (GnlObject * object) +{ + /* Prepare the pads */ + synchronize_sinks ((GnlOperation *) object); + + return TRUE; +} + +static gboolean +gnl_operation_cleanup (GnlObject * object) +{ + GnlOperation *oper = (GnlOperation *) object; + + if (oper->dynamicsinks) { + GST_DEBUG ("Resetting dynamic sinks"); + gnl_operation_set_sinks (oper, 0); + } + + return TRUE; +} + + +static GstPad * +gnl_operation_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + GnlOperation *operation = (GnlOperation *) element; + GstPad *ret; + + GST_DEBUG ("template:%s name:%s", templ->name_template, name); + + if (operation->num_sinks == operation->realsinks) { + GST_WARNING_OBJECT (element, + "We already have the maximum number of pads : %d", + operation->num_sinks); + return NULL; + } + + ret = add_sink_pad ((GnlOperation *) element); + + return ret; +} + +static void +gnl_operation_release_pad (GstElement * element, GstPad * pad) +{ + GST_DEBUG ("pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + remove_sink_pad ((GnlOperation *) element, pad); +} + +void +gnl_operation_signal_input_priority_changed (GnlOperation * operation, + GstPad * pad, guint32 priority) +{ + GST_DEBUG_OBJECT (operation, "pad:%s:%s, priority:%d", + GST_DEBUG_PAD_NAME (pad), priority); + g_signal_emit (operation, gnl_operation_signals[INPUT_PRIORITY_CHANGED], + 0, pad, priority); +} + +void +gnl_operation_update_base_time (GnlOperation * operation, + GstClockTime timestamp) +{ + if (!gnl_object_to_media_time (GNL_OBJECT (operation), + timestamp, &operation->next_base_time)) { + GST_WARNING_OBJECT (operation, "Trying to set a basetime outside of " + "ourself"); + + return; + } + + GST_INFO_OBJECT (operation, "Setting next_basetime to %" + GST_TIME_FORMAT, GST_TIME_ARGS (operation->next_base_time)); +} diff --git a/gnl/gnloperation.h b/gnl/gnloperation.h new file mode 100644 index 00000000..3d6454b7 --- /dev/null +++ b/gnl/gnloperation.h @@ -0,0 +1,90 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@chello.be> + * 2004 Edward Hervey <bilboed@bilboed.com> + * + * gnloperation.h: Header for base GnlOperation + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_OPERATION_H__ +#define __GNL_OPERATION_H__ + +#include <gst/gst.h> +#include "gnlobject.h" + +G_BEGIN_DECLS +#define GNL_TYPE_OPERATION \ + (gnl_operation_get_type()) +#define GNL_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_OPERATION,GnlOperation)) +#define GNL_OPERATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_OPERATION,GnlOperationClass)) +#define GNL_IS_OPERATION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_OPERATION)) +#define GNL_IS_OPERATION_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_OPERATION)) + struct _GnlOperation +{ + GnlObject parent; + + /* <private> */ + + /* num_sinks: + * Number of sink inputs of the controlled element. + * -1 if the sink pads are dynamic */ + gint num_sinks; + + /* TRUE if element has request pads */ + gboolean dynamicsinks; + + /* realsinks: + * Number of sink pads currently used on the contolled element. */ + gint realsinks; + + /* FIXME : We might need to use a lock to access this list */ + GList * sinks; /* The sink ghostpads */ + + GstPad *ghostpad; /* src ghostpad */ + + GstElement *element; /* controlled element */ + + GstClockTime next_base_time; +}; + +struct _GnlOperationClass +{ + GnlObjectClass parent_class; + + void (*input_priority_changed) (GnlOperation * operation, GstPad *pad, guint32 priority); +}; + +GstPad * get_unlinked_sink_ghost_pad (GnlOperation * operation); + +void +gnl_operation_signal_input_priority_changed(GnlOperation * operation, GstPad *pad, + guint32 priority); + +void gnl_operation_update_base_time (GnlOperation *operation, + GstClockTime timestamp); + + +/* normal GOperation stuff */ +GType gnl_operation_get_type (void); + +G_END_DECLS +#endif /* __GNL_OPERATION_H__ */ diff --git a/gnl/gnlsource.c b/gnl/gnlsource.c new file mode 100644 index 00000000..af9db9b3 --- /dev/null +++ b/gnl/gnlsource.c @@ -0,0 +1,590 @@ +/* Gnonlin + * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com> + * <2004-2008> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" + +/** + * SECTION:element-gnlsource + * + * The GnlSource encapsulates a pipeline which produces data for processing + * in a #GnlComposition. + */ + +static GstStaticPadTemplate gnl_source_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gnlsource); +#define GST_CAT_DEFAULT gnlsource + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gnlsource, "gnlsource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Source Element"); +#define gnl_source_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GnlSource, gnl_source, GNL_TYPE_OBJECT, _do_init); + +struct _GnlSourcePrivate +{ + gboolean dispose_has_run; + + gboolean dynamicpads; /* TRUE if the controlled element has dynamic pads */ + GstPad *ghostpad; /* The source ghostpad */ + GstEvent *event; /* queued event */ + + gulong padremovedid; /* signal handler for element pad-removed signal */ + gulong padaddedid; /* signal handler for element pad-added signal */ + gulong probeid; /* source pad probe id */ + + gboolean pendingblock; /* We have a pending pad_block */ + gboolean areblocked; /* We already got blocked */ + GstPad *ghostedpad; /* Pad (to be) ghosted */ + GstPad *staticpad; /* The only pad. We keep an extra ref */ +}; + +static gboolean gnl_source_prepare (GnlObject * object); +static gboolean gnl_source_cleanup (GnlObject * object); + +static gboolean gnl_source_add_element (GstBin * bin, GstElement * element); + +static gboolean gnl_source_remove_element (GstBin * bin, GstElement * element); + +static void gnl_source_dispose (GObject * object); + +static gboolean gnl_source_send_event (GstElement * element, GstEvent * event); + +static GstPadProbeReturn +pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, GnlSource * source); + +static gboolean +gnl_source_control_element_func (GnlSource * source, GstElement * element); + +static void +gnl_source_class_init (GnlSourceClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + GnlObjectClass *gnlobject_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbin_class = (GstBinClass *) klass; + gnlobject_class = (GnlObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (GnlSourcePrivate)); + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin Source", + "Filter/Editor", + "Manages source elements", + "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>"); + + parent_class = g_type_class_ref (GNL_TYPE_OBJECT); + + klass->controls_one = TRUE; + klass->control_element = GST_DEBUG_FUNCPTR (gnl_source_control_element_func); + + gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_source_prepare); + gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_source_cleanup); + + gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_source_add_element); + gstbin_class->remove_element = GST_DEBUG_FUNCPTR (gnl_source_remove_element); + + gstelement_class->send_event = GST_DEBUG_FUNCPTR (gnl_source_send_event); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_source_dispose); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gnl_source_src_template)); + +} + + +static void +gnl_source_init (GnlSource * source) +{ + GST_OBJECT_FLAG_SET (source, GNL_OBJECT_SOURCE); + source->element = NULL; + source->priv = + G_TYPE_INSTANCE_GET_PRIVATE (source, GNL_TYPE_SOURCE, GnlSourcePrivate); + + GST_DEBUG_OBJECT (source, "Setting GstBin async-handling to TRUE"); + g_object_set (G_OBJECT (source), "async-handling", TRUE, NULL); +} + +static void +gnl_source_dispose (GObject * object) +{ + GnlSource *source = (GnlSource *) object; + GnlSourcePrivate *priv = source->priv; + + GST_DEBUG_OBJECT (object, "dispose"); + + if (priv->dispose_has_run) + return; + + if (source->element) { + gst_object_unref (source->element); + source->element = NULL; + } + + priv->dispose_has_run = TRUE; + if (priv->event) + gst_event_unref (priv->event); + + if (priv->ghostpad) + gnl_object_remove_ghost_pad ((GnlObject *) object, priv->ghostpad); + priv->ghostpad = NULL; + + if (priv->staticpad) { + gst_object_unref (priv->staticpad); + priv->staticpad = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +element_pad_added_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad, + GnlSource * source) +{ + GstCaps *srccaps; + GnlSourcePrivate *priv = source->priv; + + GST_DEBUG_OBJECT (source, "pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + if (priv->ghostpad || priv->pendingblock) { + GST_WARNING_OBJECT (source, + "We already have (pending) ghost-ed a valid source pad (ghostpad:%s:%s, pendingblock:%d", + GST_DEBUG_PAD_NAME (priv->ghostpad), priv->pendingblock); + return; + } + + /* FIXME: pass filter caps to query_caps directly */ + srccaps = gst_pad_query_caps (pad, NULL); + if (!gst_caps_can_intersect (srccaps, GNL_OBJECT (source)->caps)) { + gst_caps_unref (srccaps); + GST_DEBUG_OBJECT (source, "Pad doesn't have valid caps, ignoring"); + return; + } + gst_caps_unref (srccaps); + + GST_DEBUG_OBJECT (pad, "valid pad, about to add event probe and pad block"); + + priv->probeid = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) pad_blocked_cb, source, NULL); + if (priv->probeid == 0) + GST_WARNING_OBJECT (source, "Couldn't set Async pad blocking"); + else { + priv->ghostedpad = pad; + priv->pendingblock = TRUE; + } + + GST_DEBUG_OBJECT (source, "Done handling pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); +} + +static void +element_pad_removed_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad, + GnlSource * source) +{ + GnlSourcePrivate *priv = source->priv; + + GST_DEBUG_OBJECT (source, "pad %s:%s (controlled pad %s:%s)", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->ghostedpad)); + + if (pad == priv->ghostedpad) { + GST_DEBUG_OBJECT (source, + "The removed pad is the controlled pad, clearing up"); + + if (priv->ghostpad) { + GST_DEBUG_OBJECT (source, "Clearing up ghostpad"); + + priv->areblocked = FALSE; + if (priv->probeid) { + gst_pad_remove_probe (pad, priv->probeid); + priv->probeid = 0; + } + + gnl_object_remove_ghost_pad ((GnlObject *) source, priv->ghostpad); + priv->ghostpad = NULL; + } + + priv->pendingblock = FALSE; + priv->ghostedpad = NULL; + } else { + GST_DEBUG_OBJECT (source, "The removed pad is NOT our controlled pad"); + } +} + +static gint +compare_src_pad (GValue * item, GstCaps * caps) +{ + gint ret = 1; + GstPad *pad = g_value_get_object (item); + GstCaps *padcaps; + + GST_DEBUG_OBJECT (pad, "Trying pad for caps %" GST_PTR_FORMAT, caps); + + /* FIXME: can pass the filter caps right away.. */ + padcaps = gst_pad_query_caps (pad, NULL); + + if (gst_caps_can_intersect (padcaps, caps)) + ret = 0; + + gst_caps_unref (padcaps); + + return ret; +} + +/* + get_valid_src_pad + + Returns True if there's a src pad compatible with the GnlObject caps in the + given element. Fills in pad if so. The returned pad has an incremented refcount +*/ + +static gboolean +get_valid_src_pad (GnlSource * source, GstElement * element, GstPad ** pad) +{ + gboolean res = FALSE; + GstIterator *srcpads; + GValue item = { 0, }; + + g_return_val_if_fail (pad, FALSE); + + srcpads = gst_element_iterate_src_pads (element); + if (gst_iterator_find_custom (srcpads, (GCompareFunc) compare_src_pad, &item, + GNL_OBJECT (source)->caps)) { + *pad = g_value_get_object (&item); + gst_object_ref (*pad); + g_value_reset (&item); + res = TRUE; + } + gst_iterator_free (srcpads); + + return res; +} + +static gpointer +ghost_seek_pad (GnlSource * source) +{ + GnlSourcePrivate *priv = source->priv; + GstPad *pad = priv->ghostedpad; + + if (priv->ghostpad || !pad) + goto beach; + + GST_DEBUG_OBJECT (source, "ghosting %s:%s", GST_DEBUG_PAD_NAME (pad)); + + priv->ghostpad = gnl_object_ghost_pad ((GnlObject *) source, + GST_PAD_NAME (pad), pad); + GST_DEBUG_OBJECT (source, "emitting no more pads"); + gst_pad_set_active (priv->ghostpad, TRUE); + + if (priv->event) { + GST_DEBUG_OBJECT (source, "sending queued seek event"); + if (!(gst_pad_send_event (priv->ghostpad, priv->event))) + GST_ELEMENT_ERROR (source, RESOURCE, SEEK, + (NULL), ("Sending initial seek to upstream element failed")); + else + GST_DEBUG_OBJECT (source, "queued seek sent"); + priv->event = NULL; + } + + GST_DEBUG_OBJECT (source, "about to unblock %s:%s", GST_DEBUG_PAD_NAME (pad)); + priv->areblocked = FALSE; + if (priv->probeid) { + gst_pad_remove_probe (pad, priv->probeid); + priv->probeid = 0; + } + gst_element_no_more_pads (GST_ELEMENT (source)); + + priv->pendingblock = FALSE; + +beach: + return NULL; +} + +static GstPadProbeReturn +pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, GnlSource * source) +{ + GST_DEBUG_OBJECT (pad, "probe callback"); + + if (!source->priv->ghostpad && !source->priv->areblocked) { + GThread *lthread; + + source->priv->areblocked = TRUE; + GST_DEBUG_OBJECT (pad, "starting thread to call ghost_seek_pad"); + lthread = + g_thread_new ("gnlsourceseek", (GThreadFunc) ghost_seek_pad, source); + g_thread_unref (lthread); + } + + return GST_PAD_PROBE_OK; +} + + +/* + * has_dynamic_pads + * Returns TRUE if the element has only dynamic pads. + */ + +static gboolean +has_dynamic_srcpads (GstElement * element) +{ + gboolean ret = TRUE; + GList *templates; + GstPadTemplate *template; + + templates = + gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (element)); + + while (templates) { + template = (GstPadTemplate *) templates->data; + + if ((GST_PAD_TEMPLATE_DIRECTION (template) == GST_PAD_SRC) + && (GST_PAD_TEMPLATE_PRESENCE (template) == GST_PAD_ALWAYS)) { + ret = FALSE; + break; + } + + templates = templates->next; + } + + return ret; +} + +static gboolean +gnl_source_control_element_func (GnlSource * source, GstElement * element) +{ + GnlSourcePrivate *priv = source->priv; + GstPad *pad = NULL; + + g_return_val_if_fail (source->element == NULL, FALSE); + + GST_DEBUG_OBJECT (source, "element:%s, source->element:%p", + GST_ELEMENT_NAME (element), source->element); + + source->element = element; + gst_object_ref (element); + + if (get_valid_src_pad (source, source->element, &pad)) { + priv->staticpad = pad; + GST_DEBUG_OBJECT (source, + "There is a valid source pad, we consider the object as NOT having dynamic pads"); + priv->dynamicpads = FALSE; + } else { + priv->dynamicpads = has_dynamic_srcpads (element); + GST_DEBUG_OBJECT (source, "No valid source pad yet, dynamicpads:%d", + priv->dynamicpads); + if (priv->dynamicpads) { + /* connect to pad-added/removed signals */ + priv->padremovedid = g_signal_connect + (G_OBJECT (element), "pad-removed", + G_CALLBACK (element_pad_removed_cb), source); + priv->padaddedid = + g_signal_connect (G_OBJECT (element), "pad-added", + G_CALLBACK (element_pad_added_cb), source); + } + } + + return TRUE; +} + +static gboolean +gnl_source_add_element (GstBin * bin, GstElement * element) +{ + GnlSource *source = (GnlSource *) bin; + gboolean pret; + + GST_DEBUG_OBJECT (source, "Adding element %s", GST_ELEMENT_NAME (element)); + + if (GNL_SOURCE_GET_CLASS (source)->controls_one && source->element) { + GST_WARNING_OBJECT (bin, "GnlSource can only handle one element at a time"); + return FALSE; + } + + /* call parent add_element */ + pret = GST_BIN_CLASS (parent_class)->add_element (bin, element); + + if (pret && GNL_SOURCE_GET_CLASS (source)->controls_one) { + gnl_source_control_element_func (source, element); + } + return pret; +} + +static gboolean +gnl_source_remove_element (GstBin * bin, GstElement * element) +{ + GnlSource *source = (GnlSource *) bin; + GnlSourcePrivate *priv = source->priv; + gboolean pret; + + GST_DEBUG_OBJECT (source, "Removing element %s", GST_ELEMENT_NAME (element)); + + /* try to remove it */ + pret = GST_BIN_CLASS (parent_class)->remove_element (bin, element); + + if ((!source->element) || (source->element != element)) { + return TRUE; + } + + if (pret) { + /* remove ghostpad */ + if (priv->ghostpad) { + gnl_object_remove_ghost_pad ((GnlObject *) bin, priv->ghostpad); + priv->ghostpad = NULL; + } + + /* discard events */ + if (priv->event) { + gst_event_unref (priv->event); + priv->event = NULL; + } + + /* remove signal handlers */ + if (priv->padremovedid) { + g_signal_handler_disconnect (source->element, priv->padremovedid); + priv->padremovedid = 0; + } + if (priv->padaddedid) { + g_signal_handler_disconnect (source->element, priv->padaddedid); + priv->padaddedid = 0; + } + + priv->dynamicpads = FALSE; + gst_object_unref (element); + source->element = NULL; + } + return pret; +} + +static gboolean +gnl_source_send_event (GstElement * element, GstEvent * event) +{ + GnlSource *source = (GnlSource *) element; + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + if (source->priv->ghostpad) + res = gst_pad_send_event (source->priv->ghostpad, event); + else { + if (source->priv->event) + gst_event_unref (source->priv->event); + source->priv->event = event; + } + break; + default: + res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + break; + } + + return res; +} + +static gboolean +gnl_source_prepare (GnlObject * object) +{ + GnlSource *source = GNL_SOURCE (object); + GnlSourcePrivate *priv = source->priv; + GstElement *parent = + (GstElement *) gst_element_get_parent ((GstElement *) object); + + if (!source->element) { + GST_WARNING_OBJECT (source, + "GnlSource doesn't have an element to control !"); + return FALSE; + } + + GST_LOG_OBJECT (source, "ghostpad:%p, dynamicpads:%d", + priv->ghostpad, priv->dynamicpads); + + if (!(priv->ghostpad) && !priv->pendingblock) { + GstPad *pad; + + GST_LOG_OBJECT (source, "no ghostpad and no dynamic pads"); + + /* Do an async block on valid source pad */ + + if (!priv->staticpad + && !(get_valid_src_pad (source, source->element, &pad))) { + GST_DEBUG_OBJECT (source, "Couldn't find a valid source pad"); + } else { + if (priv->staticpad) + pad = gst_object_ref (priv->staticpad); + GST_LOG_OBJECT (source, "Trying to async block source pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + priv->ghostedpad = pad; + priv->probeid = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) pad_blocked_cb, source, NULL); + gst_object_unref (pad); + } + } + + if (!GNL_IS_COMPOSITION (parent)) { + /* Figure out if we're in a composition */ + if (source->priv->event) + gst_event_unref (source->priv->event); + + GST_DEBUG_OBJECT (object, "Creating initial seek"); + + source->priv->event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, object->start, GST_SEEK_TYPE_SET, object->stop); + } + + gst_object_unref (parent); + + return TRUE; +} + +static gboolean +gnl_source_cleanup (GnlObject * object) +{ + GnlSource *source = GNL_SOURCE (object); + GnlSourcePrivate *priv = source->priv; + + if (priv->ghostpad) { + GstPad *target = gst_ghost_pad_get_target ((GstGhostPad *) priv->ghostpad); + + if (target) { + if (priv->probeid) { + gst_pad_remove_probe (target, priv->probeid); + priv->probeid = 0; + } + gst_object_unref (target); + } + gnl_object_remove_ghost_pad ((GnlObject *) source, priv->ghostpad); + priv->ghostpad = NULL; + priv->ghostedpad = NULL; + priv->areblocked = FALSE; + priv->pendingblock = FALSE; + } + + return TRUE; +} diff --git a/gnl/gnlsource.h b/gnl/gnlsource.h new file mode 100644 index 00000000..120270a7 --- /dev/null +++ b/gnl/gnlsource.h @@ -0,0 +1,68 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * gnlsource.h: Header for base GnlSource + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_SOURCE_H__ +#define __GNL_SOURCE_H__ + +#include <gst/gst.h> +#include "gnlobject.h" + +G_BEGIN_DECLS +#define GNL_TYPE_SOURCE \ + (gnl_source_get_type()) +#define GNL_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_SOURCE,GnlSource)) +#define GNL_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_SOURCE,GnlSourceClass)) +#define GNL_SOURCE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GNL_TYPE_SOURCE, GnlSourceClass)) +#define GNL_IS_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_SOURCE)) +#define GNL_IS_SOURCE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_SOURCE)) +typedef struct _GnlSourcePrivate GnlSourcePrivate; + +struct _GnlSource +{ + GnlObject parent; + + /* controlled source element, acces with gst_bin_[add|remove]_element */ + GstElement *element; + + GnlSourcePrivate *priv; +}; + +struct _GnlSourceClass +{ + GnlObjectClass parent_class; + + /* controls_one is TRUE if the class only controls one element */ + gboolean controls_one; + /* control_element() takes care of controlling the given element */ + gboolean (*control_element) (GnlSource * source, GstElement * element); +}; + +GType gnl_source_get_type (void); + +G_END_DECLS +#endif /* __GNL_SOURCE_H__ */ diff --git a/gnl/gnltypes.h b/gnl/gnltypes.h new file mode 100644 index 00000000..559c7d66 --- /dev/null +++ b/gnl/gnltypes.h @@ -0,0 +1,42 @@ +/* GStreamer + * Copyright (C) 2004 Edward Hervey <bilboed@bilboed.com> + * + * gnltypes.h: Header for class definition + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GNL_TYPES_H__ +#define __GNL_TYPES_H__ + +#include <glib.h> + +typedef struct _GnlObject GnlObject; +typedef struct _GnlObjectClass GnlObjectClass; + +typedef struct _GnlComposition GnlComposition; +typedef struct _GnlCompositionClass GnlCompositionClass; + +typedef struct _GnlOperation GnlOperation; +typedef struct _GnlOperationClass GnlOperationClass; + +typedef struct _GnlSource GnlSource; +typedef struct _GnlSourceClass GnlSourceClass; + +typedef struct _GnlURISource GnlURISource; +typedef struct _GnlURISourceClass GnlURISourceClass; + +#endif diff --git a/gnl/gnlurisource.c b/gnl/gnlurisource.c new file mode 100644 index 00000000..b02a8797 --- /dev/null +++ b/gnl/gnlurisource.c @@ -0,0 +1,166 @@ +/* Gnonlin + * Copyright (C) <2005-2008> Edward Hervey <bilboed@bilboed.com> + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gnl.h" +#include "gnlurisource.h" + +/** + * SECTION:element-gnlurisource + * + * GnlURISource is a #GnlSource which reads and decodes the contents + * of a given file. The data in the file is decoded using any available + * GStreamer plugins. + */ + +static GstStaticPadTemplate gnl_urisource_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gnlurisource); +#define GST_CAT_DEFAULT gnlurisource + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gnlurisource, "gnlurisource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin URI Source Element"); +#define gnl_urisource_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GnlURISource, gnl_urisource, GNL_TYPE_SOURCE, + _do_init); + +enum +{ + ARG_0, + ARG_URI, +}; + +static gboolean gnl_urisource_prepare (GnlObject * object); + +static void +gnl_urisource_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static void +gnl_urisource_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +gnl_urisource_class_init (GnlURISourceClass * klass) +{ + GObjectClass *gobject_class; + GnlObjectClass *gnlobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gnlobject_class = (GnlObjectClass *) klass; + parent_class = g_type_class_ref (GNL_TYPE_SOURCE); + + gst_element_class_set_static_metadata (gstelement_class, "GNonLin URI Source", + "Filter/Editor", + "High-level URI Source element", "Edward Hervey <bilboed@bilboed.com>"); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_urisource_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_urisource_get_property); + + g_object_class_install_property (gobject_class, ARG_URI, + g_param_spec_string ("uri", "Uri", + "Uri of the file to use", NULL, G_PARAM_READWRITE)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gnl_urisource_src_template)); + + gnlobject_class->prepare = gnl_urisource_prepare; +} + +static void +gnl_urisource_init (GnlURISource * urisource) +{ + GstElement *decodebin = NULL; + + GST_OBJECT_FLAG_SET (urisource, GNL_OBJECT_SOURCE); + + /* We create a bin with source and decodebin within */ + decodebin = + gst_element_factory_make ("uridecodebin", "internal-uridecodebin"); + g_object_set (decodebin, "expose-all-streams", FALSE, NULL); + + gst_bin_add (GST_BIN (urisource), decodebin); +} + +static inline void +gnl_urisource_set_uri (GnlURISource * fs, const gchar * uri) +{ + g_object_set (GNL_SOURCE (fs)->element, "uri", uri, NULL); +} + +static void +gnl_urisource_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GnlURISource *fs = (GnlURISource *) object; + + switch (prop_id) { + case ARG_URI: + gnl_urisource_set_uri (fs, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gnl_urisource_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GnlURISource *fs = (GnlURISource *) object; + + switch (prop_id) { + case ARG_URI: + g_object_get_property ((GObject *) GNL_SOURCE (fs)->element, "uri", + value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +} + +static gboolean +gnl_urisource_prepare (GnlObject * object) +{ + GnlSource *fs = (GnlSource *) object; + + GST_DEBUG ("prepare"); + + /* Set the caps on uridecodebin */ + if (!gst_caps_is_any (object->caps)) { + GST_DEBUG_OBJECT (object, "Setting uridecodebin caps to %" GST_PTR_FORMAT, + object->caps); + g_object_set (fs->element, "caps", object->caps, NULL); + } + + return GNL_OBJECT_CLASS (parent_class)->prepare (object); +} diff --git a/gnl/gnlurisource.h b/gnl/gnlurisource.h new file mode 100644 index 00000000..455b20e3 --- /dev/null +++ b/gnl/gnlurisource.h @@ -0,0 +1,57 @@ +/* GStreamer + * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com> + * 2004-2008 Edward Hervey <bilboed@bilboed.com> + * + * gnlurisource.h: Header for GnlURISource + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GNL_URI_SOURCE_H__ +#define __GNL_URI_SOURCE_H__ + +#include <gst/gst.h> +#include "gnlsource.h" + +G_BEGIN_DECLS +#define GNL_TYPE_URI_SOURCE \ + (gnl_urisource_get_type()) +#define GNL_URI_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_URI_SOURCE,GnlURIsource)) +#define GNL_URI_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_URI_SOURCE,GnlURIsourceClass)) +#define GNL_IS_URI_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_URI_SOURCE)) +#define GNL_IS_URI_SOURCE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_URI_SOURCE)) + +struct _GnlURISource +{ + GnlSource parent; + + gchar *uri; +}; + +struct _GnlURISourceClass +{ + GnlSourceClass parent_class; +}; + +GType gnl_urisource_get_type (void); + +G_END_DECLS +#endif /* __GNL_URI_SOURCE_H__ */ diff --git a/tests/check/gnl/common.c b/tests/check/gnl/common.c new file mode 100644 index 00000000..68c849b5 --- /dev/null +++ b/tests/check/gnl/common.c @@ -0,0 +1,348 @@ +#include "common.h" + +void +poll_the_bus (GstBus * bus) +{ + GstMessage *message; + gboolean carry_on = TRUE; + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_DEBUG ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } +} + +GstElement * +gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name) +{ + GstElement *element; + + element = gst_element_factory_make (factoryname, name); + fail_unless (element != NULL, "Failed to make element %s", factoryname); + return element; +} + +void +composition_pad_added_cb (GstElement * composition, GstPad * pad, + CollectStructure * collect) +{ + fail_if (!(gst_element_link_pads_full (composition, GST_OBJECT_NAME (pad), + collect->sink, "sink", GST_PAD_LINK_CHECK_NOTHING))); +} + +/* return TRUE to discard the Segment */ +static gboolean +compare_segments (CollectStructure * collect, Segment * segment, + GstEvent * event) +{ + const GstSegment *orig; + guint64 running_stop, running_start, running_duration; + + gst_event_parse_segment (event, &orig); + + GST_DEBUG ("Got Segment rate:%f, format:%s, start:%" GST_TIME_FORMAT + ", stop:%" GST_TIME_FORMAT ", time:%" GST_TIME_FORMAT + ", base:%" GST_TIME_FORMAT ", offset:%" GST_TIME_FORMAT, + orig->rate, gst_format_get_name (orig->format), + GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop), + GST_TIME_ARGS (orig->time), GST_TIME_ARGS (orig->base), + GST_TIME_ARGS (orig->offset)); + GST_DEBUG ("[RUNNING] start:%" GST_TIME_FORMAT " [STREAM] start:%" + GST_TIME_FORMAT, GST_TIME_ARGS (gst_segment_to_running_time (orig, + GST_FORMAT_TIME, orig->start)), + GST_TIME_ARGS (gst_segment_to_stream_time (orig, GST_FORMAT_TIME, + orig->start))); + + GST_DEBUG ("Expecting rate:%f, format:%s, start:%" GST_TIME_FORMAT + ", stop:%" GST_TIME_FORMAT ", position:%" GST_TIME_FORMAT ", base:%" + GST_TIME_FORMAT, segment->rate, gst_format_get_name (segment->format), + GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop), + GST_TIME_ARGS (segment->position), + GST_TIME_ARGS (collect->expected_base)); + + running_start = + gst_segment_to_running_time (orig, GST_FORMAT_TIME, orig->start); + running_stop = + gst_segment_to_running_time (orig, GST_FORMAT_TIME, orig->stop); + running_duration = running_stop - running_start; + fail_if (orig->rate != segment->rate); + fail_if (orig->format != segment->format); + fail_unless_equals_int64 (orig->time, segment->position); + fail_unless_equals_int64 (orig->base, collect->expected_base); + fail_unless_equals_uint64 (orig->stop - orig->start, + segment->stop - segment->start); + + collect->expected_base += running_duration; + + GST_DEBUG ("Segment was valid, discarding expected Segment"); + + return TRUE; +} + +static GstPadProbeReturn +sinkpad_event_probe (GstPad * sinkpad, GstEvent * event, + CollectStructure * collect) +{ + Segment *segment; + + GST_DEBUG_OBJECT (sinkpad, "event:%p (%s seqnum:%d) , collect:%p", event, + GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event), collect); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + fail_if (collect->expected_segments == NULL, + "Received unexpected segment on pad: %s:%s", + GST_DEBUG_PAD_NAME (sinkpad)); + + if (!collect->gotsegment) + collect->seen_segments = + g_list_append (NULL, GINT_TO_POINTER (GST_EVENT_SEQNUM (event))); + else { + fail_if (g_list_find (collect->seen_segments, + GINT_TO_POINTER (GST_EVENT_SEQNUM (event))), + "Got a segment event we already saw before !"); + collect->seen_segments = + g_list_append (collect->seen_segments, + GINT_TO_POINTER (GST_EVENT_SEQNUM (event))); + } + + segment = (Segment *) collect->expected_segments->data; + + if (compare_segments (collect, segment, event) && + collect->keep_expected_segments == FALSE) { + collect->expected_segments = + g_list_remove (collect->expected_segments, segment); + g_free (segment); + } + + collect->gotsegment = TRUE; + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn +sinkpad_buffer_probe (GstPad * sinkpad, GstBuffer * buffer, + CollectStructure * collect) +{ + GST_DEBUG_OBJECT (sinkpad, "buffer:%p (%" GST_TIME_FORMAT ") , collect:%p", + buffer, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), collect); + fail_if (!collect->gotsegment, + "Received a buffer without a preceding segment"); + return GST_PAD_PROBE_OK; +} + +GstPadProbeReturn +sinkpad_probe (GstPad * sinkpad, GstPadProbeInfo * info, + CollectStructure * collect) +{ + if (info->type & GST_PAD_PROBE_TYPE_BUFFER) + return sinkpad_buffer_probe (sinkpad, (GstBuffer *) info->data, collect); + if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) + return sinkpad_event_probe (sinkpad, (GstEvent *) info->data, collect); + return GST_PAD_PROBE_OK; +} + +static GstElement * +new_gnl_src (const gchar * name, guint64 start, gint64 duration, gint priority) +{ + GstElement *gnlsource = NULL; + + gnlsource = gst_element_factory_make_or_warn ("gnlsource", name); + fail_if (gnlsource == NULL); + + g_object_set (G_OBJECT (gnlsource), + "start", start, + "duration", duration, "inpoint", start, "priority", priority, NULL); + + return gnlsource; +} + +GstElement * +videotest_gnl_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority) +{ + GstElement *gnlsource = NULL; + GstElement *videotestsrc = NULL; + GstCaps *caps = + gst_caps_from_string + ("video/x-raw,format=(string)I420,framerate=(fraction)3/2"); + + fail_if (caps == NULL); + + videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL); + g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL); + + gnlsource = new_gnl_src (name, start, duration, priority); + g_object_set (G_OBJECT (gnlsource), "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (gnlsource), videotestsrc); + + return gnlsource; +} + +GstElement * +videotest_gnl_src_full (const gchar * name, guint64 start, gint64 duration, + guint64 inpoint, gint pattern, guint priority) +{ + GstElement *gnls; + + gnls = videotest_gnl_src (name, start, duration, pattern, priority); + if (gnls) { + g_object_set (G_OBJECT (gnls), "inpoint", inpoint, NULL); + } + + + return gnls; +} + +GstElement * +videotest_in_bin_gnl_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority) +{ + GstElement *gnlsource = NULL; + GstElement *videotestsrc = NULL; + GstElement *bin = NULL; + GstElement *alpha = NULL; + GstPad *srcpad = NULL; + + alpha = gst_element_factory_make ("alpha", NULL); + if (alpha == NULL) + return NULL; + + videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL); + g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL); + bin = gst_bin_new (NULL); + + gnlsource = new_gnl_src (name, start, duration, priority); + + gst_bin_add (GST_BIN (bin), videotestsrc); + gst_bin_add (GST_BIN (bin), alpha); + + gst_element_link_pads_full (videotestsrc, "src", alpha, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + gst_bin_add (GST_BIN (gnlsource), bin); + + srcpad = gst_element_get_static_pad (alpha, "src"); + + gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad)); + + gst_object_unref (srcpad); + + return gnlsource; +} + +GstElement * +audiotest_bin_src (const gchar * name, guint64 start, + gint64 duration, guint priority, gboolean intaudio) +{ + GstElement *source = NULL; + GstElement *identity = NULL; + GstElement *audiotestsrc = NULL; + GstElement *audioconvert = NULL; + GstElement *bin = NULL; + GstCaps *caps; + GstPad *srcpad = NULL; + + audiotestsrc = gst_element_factory_make_or_warn ("audiotestsrc", NULL); + identity = gst_element_factory_make_or_warn ("identity", NULL); + bin = gst_bin_new (NULL); + source = new_gnl_src (name, start, duration, priority); + audioconvert = gst_element_factory_make_or_warn ("audioconvert", NULL); + + if (intaudio) + caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE"); + else + caps = gst_caps_from_string ("audio/x-raw,format=(string)F32LE"); + + gst_bin_add_many (GST_BIN (bin), audiotestsrc, audioconvert, identity, NULL); + gst_element_link_pads_full (audiotestsrc, "src", audioconvert, "sink", + GST_PAD_LINK_CHECK_NOTHING); + fail_if ((gst_element_link_filtered (audioconvert, identity, caps)) != TRUE); + + gst_caps_unref (caps); + + gst_bin_add (GST_BIN (source), bin); + + srcpad = gst_element_get_static_pad (identity, "src"); + + gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad)); + + gst_object_unref (srcpad); + + return source; +} + +GstElement * +new_operation (const gchar * name, const gchar * factory, guint64 start, + gint64 duration, guint priority) +{ + GstElement *gnloperation = NULL; + GstElement *operation = NULL; + + operation = gst_element_factory_make_or_warn (factory, NULL); + gnloperation = gst_element_factory_make_or_warn ("gnloperation", name); + + g_object_set (G_OBJECT (gnloperation), + "start", start, "duration", duration, "priority", priority, NULL); + + gst_bin_add (GST_BIN (gnloperation), operation); + + return gnloperation; +} + + +Segment * +segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, + gint64 position) +{ + Segment *segment; + + segment = g_new0 (Segment, 1); + + segment->rate = rate; + segment->format = format; + segment->start = start; + segment->stop = stop; + segment->position = position; + + return segment; +} + +GList * +copy_segment_list (GList * list) +{ + GList *res = NULL; + + while (list) { + Segment *pdata = (Segment *) list->data; + + res = + g_list_append (res, segment_new (pdata->rate, pdata->format, + pdata->start, pdata->stop, pdata->position)); + + list = list->next; + } + + return res; +} diff --git a/tests/check/gnl/common.h b/tests/check/gnl/common.h new file mode 100644 index 00000000..b1d6d9e3 --- /dev/null +++ b/tests/check/gnl/common.h @@ -0,0 +1,73 @@ + +#include <gst/check/gstcheck.h> + +#define fail_error_message(msg) \ + G_STMT_START { \ + GError *error; \ + gst_message_parse_error(msg, &error, NULL); \ + fail_unless(FALSE, "Error Message from %s : %s", \ + GST_OBJECT_NAME (GST_MESSAGE_SRC(msg)), error->message); \ + g_error_free (error); \ + } G_STMT_END; + +#define check_start_stop_duration(object, startval, stopval, durval) \ + G_STMT_START { guint64 start, stop; \ + gint64 duration; \ + GST_DEBUG_OBJECT (object, "Checking for valid start/stop/duration values"); \ + g_object_get (object, "start", &start, "stop", &stop, \ + "duration", &duration, NULL); \ + fail_unless_equals_uint64(start, startval); \ + fail_unless_equals_uint64(stop, stopval); \ + fail_unless_equals_int64(duration, durval); \ + GST_DEBUG_OBJECT (object, "start/stop/duration values valid"); \ + } G_STMT_END; + +#define check_state_simple(object, expected_state) \ + G_STMT_START { \ + GstStateChangeReturn ret; \ + GstState state, pending; \ + ret = gst_element_get_state(GST_ELEMENT_CAST(object), &state, &pending, 5 * GST_SECOND); \ + fail_if (ret == GST_STATE_CHANGE_FAILURE); \ + fail_unless (state == expected_state, "Element state (%s) is not the expected one (%s)", \ + gst_element_state_get_name(state), gst_element_state_get_name(expected_state)); \ + } G_STMT_END; + +typedef struct _Segment { + gdouble rate; + GstFormat format; + guint64 start, stop, position; +} Segment; + +typedef struct _CollectStructure { + GstElement *comp; + GstElement *sink; + guint64 last_time; + gboolean gotsegment; + GList *seen_segments; + GList *expected_segments; + guint64 expected_base; + + gboolean keep_expected_segments; +} CollectStructure; + +void poll_the_bus(GstBus *bus); +void composition_pad_added_cb (GstElement *composition, GstPad *pad, CollectStructure * collect); +GstPadProbeReturn sinkpad_probe (GstPad *sinkpad, GstPadProbeInfo * info, CollectStructure * collect); +GstElement *videotest_gnl_src (const gchar * name, guint64 start, gint64 duration, + gint pattern, guint priority); +GstElement * videotest_gnl_src_full (const gchar * name, guint64 start, gint64 duration, + guint64 inpoint, + gint pattern, guint priority); +GstElement * +videotest_in_bin_gnl_src (const gchar * name, guint64 start, gint64 duration, gint pattern, guint priority); +GstElement * +audiotest_bin_src (const gchar * name, guint64 start, + gint64 duration, guint priority, gboolean intaudio); +GstElement * +new_operation (const gchar * name, const gchar * factory, guint64 start, gint64 duration, guint priority); +GList * +copy_segment_list (GList *list); +GstElement * +gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name); +Segment * +segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, gint64 position); diff --git a/tests/check/gnl/complex.c b/tests/check/gnl/complex.c new file mode 100644 index 00000000..f4b7eb84 --- /dev/null +++ b/tests/check/gnl/complex.c @@ -0,0 +1,834 @@ +#include "common.h" + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments, + gint expected_error_domain) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GList *listcopy = copy_segment_list (segments); + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + { + GError *error; + + gst_message_parse_error (message, &error, NULL); + if (comp == GST_ELEMENT (GST_MESSAGE_SRC (message)) && + expected_error_domain == error->domain) { + GST_DEBUG ("Expected Error Message from %s : %s", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), error->message); + + carry_on = FALSE; + } else + fail_error_message (message); + } + break; + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + collect->expected_segments = listcopy; + collect->gotsegment = FALSE; + collect->expected_base = 0; + + if (expected_error_domain) + goto done; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + +done: + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +GST_START_TEST (test_one_space_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-source1--] [-source2--] | 1 + * */ + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 2, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 1s + Priority : 1 + */ + source2 = videotest_gnl_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); +} + +GST_END_TEST; + +GST_START_TEST (test_one_default_another) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2, *source3, *defaultsrc; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-source1--] [-source2--][-source3-] | 1 + * [--------------------------defaultsource------------------] | MAXUINT32 + * */ + + + /* + defaultsrc source + Start : 0s + Duration : 5s + Priority : 2 + */ + + defaultsrc = + videotest_gnl_src ("defaultsrc", 0, 5 * GST_SECOND, 2, G_MAXUINT32); + g_object_set (defaultsrc, "expandable", TRUE, NULL); + fail_if (defaultsrc == NULL); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 3s + Duration : 1s + Priority : 1 + */ + source2 = videotest_gnl_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 4s + Duration : 1s + Priority : 1 + */ + source3 = videotest_gnl_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source3 == NULL); + check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND, + 1 * GST_SECOND); + + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* defaultsrc source */ + gst_bin_add (GST_BIN (comp), defaultsrc); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + /* Third source */ + gst_bin_add (GST_BIN (comp), source3); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret); + check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); +} + +GST_END_TEST; + +GST_START_TEST (test_one_expandable_another) +{ + GstElement *comp, *source1, *source2, *source3, *defaultsrc; + GList *segments = NULL; + gboolean ret = FALSE; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [ source1 ] [ source2 ][ source3 ] | 1 + * [--------------------- defaultsrc ------------------------] | 1000 EXPANDABLE + * */ + + /* + defaultsrc source + Start : 0s + Duration : 5s + Priority : 1000 + */ + + defaultsrc = videotest_gnl_src ("defaultsrc", 0, 5 * GST_SECOND, 2, 1000); + g_object_set (defaultsrc, "expandable", TRUE, NULL); + fail_if (defaultsrc == NULL); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 3s + Duration : 1s + Priority : 1 + */ + source2 = videotest_gnl_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 4s + Duration : 1s + Priority : 1 + */ + source3 = videotest_gnl_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source3 == NULL); + check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND, + 1 * GST_SECOND); + + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* defaultsrc source */ + + gst_bin_add (GST_BIN (comp), defaultsrc); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Third source */ + + gst_bin_add (GST_BIN (comp), source3); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND); + check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, 0); +} + +GST_END_TEST; + + + +GST_START_TEST (test_renegotiation) +{ + gboolean ret; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2, *source3; + GstElement *audioconvert; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GstCaps *caps; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + source1 = + audiotest_bin_src ("source1", 0 * GST_SECOND, 1 * GST_SECOND, 1, FALSE); + check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + audiotest_bin_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 1, TRUE); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* + Source 3 + Start : 2s + Duration : 1s + Priority : 1 + */ + source3 = + audiotest_bin_src ("source3", 2 * GST_SECOND, 1 * GST_SECOND, 1, FALSE); + check_start_stop_duration (source3, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Third source */ + + gst_bin_add (GST_BIN (comp), source3); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source3, "source3", 1); + + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + audioconvert = gst_element_factory_make_or_warn ("audioconvert", "aconv"); + + gst_bin_add_many (GST_BIN (pipeline), comp, audioconvert, sink, NULL); + caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE"); + gst_element_link_filtered (audioconvert, sink, caps); + gst_caps_unref (caps); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = audioconvert; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_bin_space_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND, + 1 * GST_SECOND); + + /* Re-add second source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR); +} + +GST_END_TEST; + +GST_START_TEST (test_one_above_another) +{ + GstElement *comp, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 2 + */ + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 2s + Duration : 2s + Priority : 1 + */ + source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Re-add second source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments, 0); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-complex"); + TCase *tc_chain = tcase_create ("complex"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_one_space_another); + tcase_add_test (tc_chain, test_one_default_another); + tcase_add_test (tc_chain, test_one_expandable_another); + tcase_add_test (tc_chain, test_renegotiation); + tcase_add_test (tc_chain, test_one_bin_space_another); + tcase_add_test (tc_chain, test_one_above_another); + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/gnl/gnlcomposition.c b/tests/check/gnl/gnlcomposition.c new file mode 100644 index 00000000..1710e2f1 --- /dev/null +++ b/tests/check/gnl/gnlcomposition.c @@ -0,0 +1,560 @@ +/* Gnonlin + * Copyright (C) <2009> Alessandro Decina <alessandro.decina@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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "common.h" + +typedef struct +{ + GstElement *composition; + GstElement *source3; +} TestClosure; + +static int composition_pad_added; +static int composition_pad_removed; +static int seek_events; +static gulong blockprobeid = 0; +static GMutex pad_added_lock; +static GCond pad_added_cond; + +static GstPadProbeReturn +on_source1_pad_event_cb (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEEK) + ++seek_events; + + return GST_PAD_PROBE_OK; +} + +static void +on_source1_pad_added_cb (GstElement * source, GstPad * pad, gpointer user_data) +{ + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL); +} + +static void +on_composition_pad_added_cb (GstElement * composition, GstPad * pad, + GstElement * sink) +{ + GstPad *s = gst_element_get_static_pad (sink, "sink"); + gst_pad_link (pad, s); + ++composition_pad_added; + g_mutex_lock (&pad_added_lock); + g_cond_broadcast (&pad_added_cond); + g_mutex_unlock (&pad_added_lock); + gst_object_unref (s); +} + +static void +on_composition_pad_removed_cb (GstElement * composition, GstPad * pad, + GstElement * sink) +{ + ++composition_pad_removed; +} + +GST_START_TEST (test_change_object_start_stop_in_current_stack) +{ + GstElement *pipeline; + GstElement *comp, *source1, *def, *sink; + GstBus *bus; + GstMessage *message; + gboolean carry_on, ret = FALSE; + int seek_events_before; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* connect to pad-added */ + g_object_connect (comp, "signal::pad-added", + on_composition_pad_added_cb, sink, NULL); + g_object_connect (comp, "signal::pad-removed", + on_composition_pad_removed_cb, NULL, NULL); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2); + g_object_connect (source1, "signal::pad-added", + on_source1_pad_added_cb, NULL, NULL); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + + /* keep an extra ref to source1 as we remove it from the bin */ + gst_object_ref (source1); + gst_bin_add (GST_BIN (comp), source1); + + /* Add default */ + gst_bin_add (GST_BIN (comp), def); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 2); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ASYNC_DONE: + { + carry_on = FALSE; + GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); + break; + } + case GST_MESSAGE_EOS: + { + GST_WARNING ("Saw EOS"); + + fail_if (TRUE); + } + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + fail_unless_equals_int (composition_pad_added, 1); + fail_unless_equals_int (composition_pad_removed, 0); + + seek_events_before = seek_events; + + /* pipeline is paused at this point */ + + /* move source1 out of the active segment */ + g_object_set (source1, "start", (guint64) 4 * GST_SECOND, NULL); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (seek_events > seek_events_before); + + /* remove source1 from the composition, which will become empty and remove the + * ghostpad */ + gst_bin_remove (GST_BIN (comp), source1); + + fail_unless_equals_int (composition_pad_added, 1); + fail_unless_equals_int (composition_pad_removed, 1); + + g_object_set (source1, "start", (guint64) 0 * GST_SECOND, NULL); + /* add the source again and check that the ghostpad is added again */ + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + + g_mutex_lock (&pad_added_lock); + g_cond_wait (&pad_added_cond, &pad_added_lock); + fail_unless_equals_int (composition_pad_added, 2); + fail_unless_equals_int (composition_pad_removed, 1); + g_mutex_unlock (&pad_added_lock); + + seek_events_before = seek_events; + + g_object_set (source1, "duration", (guint64) 1 * GST_SECOND, NULL); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (seek_events > seek_events_before); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + gst_element_set_state (source1, GST_STATE_NULL); + gst_object_unref (source1); + + GST_DEBUG ("Resetted pipeline to READY"); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); +} + +GST_END_TEST; + +GST_START_TEST (test_remove_invalid_object) +{ + GstBin *composition; + GstElement *source1, *source2; + + composition = GST_BIN (gst_element_factory_make ("gnlcomposition", + "composition")); + source1 = gst_element_factory_make ("gnlsource", "source1"); + source2 = gst_element_factory_make ("gnlsource", "source2"); + + gst_bin_add (composition, source1); + fail_if (gst_bin_remove (composition, source2)); + fail_unless (gst_bin_remove (composition, source1)); + + gst_object_unref (composition); + gst_object_unref (source2); +} + +GST_END_TEST; + +static GstPadProbeReturn +pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstPad *ghost; + GstBin *bin; + + bin = GST_BIN (user_data); + + GST_DEBUG_OBJECT (pad, "probe type:0x%x", GST_PAD_PROBE_INFO_TYPE (info)); + + ghost = gst_ghost_pad_new ("src", pad); + gst_pad_set_active (ghost, TRUE); + + gst_element_add_pad (GST_ELEMENT (bin), ghost); + + return GST_PAD_PROBE_REMOVE; +} + +static void +no_more_pads_test_cb (GObject * object, TestClosure * c) +{ + gboolean ret; + + GST_WARNING ("NO MORE PADS"); + gst_bin_add (GST_BIN (c->composition), c->source3); + g_signal_emit_by_name (c->composition, "commit", TRUE, &ret); +} + +GST_START_TEST (test_no_more_pads_race) +{ + gboolean ret; + GstElement *source1, *source2, *source3; + GstBin *bin; + GstElement *videotestsrc1, *videotestsrc2; + GstElement *operation; + GstElement *composition; + GstElement *videomixer, *fakesink; + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + GstPad *pad; + TestClosure closure; + + /* We create a composition with an operation and three sources. The operation + * contains a videomixer instance and the three sources are videotestsrc's. + * + * One of the sources, source2, contains videotestsrc inside a bin. Initially + * the bin doesn't have a source pad. We do this to exercise the dynamic src + * pad code path in gnlcomposition. We block on the videotestsrc srcpad and in + * the pad block callback we ghost the pad and add the ghost to the parent + * bin. This makes gnlsource emit no-more-pads, which is used by + * gnlcomposition to link the source2:src pad to videomixer. + * + * We start with the composition containing operation and source1. We preroll + * and then add source2. Source2 will do what described above and emit + * no-more-pads. We connect to that no-more-pads and from there we add source3 to + * the composition. Adding a new source will make gnlcomposition deactivate + * the old stack and activate a new one. The new one contains operation, + * source1, source2 and source3. Source2 was active in the old stack as well and + * gnlcomposition is *still waiting* for no-more-pads to be emitted on it + * (since the no-more-pads emission is now blocked in our test's no-more-pads + * callback, calling gst_bin_add). In short, here, we're simulating a race between + * no-more-pads and someone modifying the composition. + * + * Activating the new stack, gnlcomposition calls compare_relink_single_node, + * which finds an existing source pad for source2 this time since we have + * already blocked and ghosted. It takes another code path that assumes that + * source2 doesn't have dynamic pads and *BOOM*. + */ + + pipeline = GST_ELEMENT (gst_pipeline_new (NULL)); + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + composition = gst_element_factory_make ("gnlcomposition", "composition"); + fakesink = gst_element_factory_make ("fakesink", NULL); + fail_unless (fakesink != NULL); + g_object_set (fakesink, "sync", TRUE, NULL); + + /* operation */ + operation = gst_element_factory_make ("gnloperation", "operation"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + fail_unless (videomixer != NULL); + gst_bin_add (GST_BIN (operation), videomixer); + g_object_set (operation, "start", (guint64) 0 * GST_SECOND, + "duration", (guint64) 10 * GST_SECOND, + "inpoint", (guint64) 0 * GST_SECOND, "priority", 10, NULL); + gst_bin_add (GST_BIN (composition), operation); + + /* source 1 */ + source1 = gst_element_factory_make ("gnlsource", "source1"); + videotestsrc1 = gst_element_factory_make ("videotestsrc", "videotestsrc1"); + gst_bin_add (GST_BIN (source1), videotestsrc1); + g_object_set (source1, "start", (guint64) 0 * GST_SECOND, "duration", + (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority", + 20, NULL); + + /* source2 */ + source2 = gst_element_factory_make ("gnlsource", "source2"); + bin = GST_BIN (gst_bin_new (NULL)); + videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc2"); + pad = gst_element_get_static_pad (videotestsrc2, "src"); + blockprobeid = + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) pad_block, bin, NULL); + gst_bin_add (bin, videotestsrc2); + gst_bin_add (GST_BIN (source2), GST_ELEMENT (bin)); + g_object_set (source2, "start", (guint64) 0 * GST_SECOND, "duration", + (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority", + 20, NULL); + + /* source3 */ + source3 = gst_element_factory_make ("gnlsource", "source3"); + videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc3"); + gst_bin_add (GST_BIN (source3), videotestsrc2); + g_object_set (source3, "start", (guint64) 0 * GST_SECOND, "duration", + (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority", + 20, NULL); + + closure.composition = composition; + closure.source3 = source3; + g_object_connect (source2, "signal::no-more-pads", + no_more_pads_test_cb, &closure, NULL); + + gst_bin_add (GST_BIN (composition), source1); + g_signal_emit_by_name (composition, "commit", TRUE, &ret); + g_object_connect (composition, "signal::pad-added", + on_composition_pad_added_cb, fakesink, NULL); + g_object_connect (composition, "signal::pad-removed", + on_composition_pad_removed_cb, NULL, NULL); + + GST_DEBUG ("Adding composition to pipeline"); + + gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL); + + GST_DEBUG ("Setting pipeline to PAUSED"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED) + == GST_STATE_CHANGE_FAILURE); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) { + fail_error_message (message); + } + gst_message_unref (message); + + GST_DEBUG ("Adding second source"); + + /* FIXME: maybe slow down the videotestsrc steaming thread */ + gst_bin_add (GST_BIN (composition), source2); + g_signal_emit_by_name (composition, "commit", TRUE, &ret); + + message = + gst_bus_timed_pop_filtered (bus, GST_SECOND / 10, GST_MESSAGE_ERROR); + if (message) { + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) { + fail_error_message (message); + } else { + fail_if (TRUE); + } + + gst_message_unref (message); + } + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + gst_object_unref (bus); +} + +GST_END_TEST; + +GST_START_TEST (test_simple_adder) +{ + GstBus *bus; + GstMessage *message; + GstElement *pipeline; + GstElement *gnl_adder; + GstElement *composition; + GstElement *adder, *fakesink; + GstClockTime start_playing_time; + GstElement *gnlsource1, *gnlsource2; + GstElement *audiotestsrc1, *audiotestsrc2; + + gboolean carry_on = TRUE, ret; + GstClockTime total_time = 10 * GST_SECOND; + + pipeline = GST_ELEMENT (gst_pipeline_new (NULL)); + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + composition = gst_element_factory_make ("gnlcomposition", "composition"); + fakesink = gst_element_factory_make ("fakesink", NULL); + g_object_set (fakesink, "sync", TRUE, NULL); + + /* gnl_adder */ + gnl_adder = gst_element_factory_make ("gnloperation", "gnl_adder"); + adder = gst_element_factory_make ("adder", "adder"); + fail_unless (adder != NULL); + gst_bin_add (GST_BIN (gnl_adder), adder); + g_object_set (gnl_adder, "start", (guint64) 0 * GST_SECOND, + "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, + "priority", 0, NULL); + gst_bin_add (GST_BIN (composition), gnl_adder); + + /* source 1 */ + gnlsource1 = gst_element_factory_make ("gnlsource", "gnlsource1"); + audiotestsrc1 = gst_element_factory_make ("audiotestsrc", "audiotestsrc1"); + gst_bin_add (GST_BIN (gnlsource1), audiotestsrc1); + g_object_set (gnlsource1, "start", (guint64) 0 * GST_SECOND, + "duration", total_time / 2, "inpoint", (guint64) 0, "priority", 1, NULL); + fail_unless (gst_bin_add (GST_BIN (composition), gnlsource1)); + + /* gnlsource2 */ + gnlsource2 = gst_element_factory_make ("gnlsource", "gnlsource2"); + audiotestsrc2 = gst_element_factory_make ("audiotestsrc", "audiotestsrc2"); + gst_bin_add (GST_BIN (gnlsource2), GST_ELEMENT (audiotestsrc2)); + g_object_set (gnlsource2, "start", (guint64) 0 * GST_SECOND, + "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, "priority", + 2, NULL); + fail_unless (gst_bin_add (GST_BIN (composition), gnlsource2)); + + /* Connecting signals */ + g_object_connect (composition, "signal::pad-added", + on_composition_pad_added_cb, fakesink, NULL); + g_object_connect (composition, "signal::pad-removed", + on_composition_pad_removed_cb, NULL, NULL); + + + GST_DEBUG ("Adding composition to pipeline"); + + gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL); + + GST_DEBUG ("Setting pipeline to PAUSED"); + + g_signal_emit_by_name (composition, "commit", TRUE, &ret); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) + == GST_STATE_CHANGE_FAILURE); + + message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR); + + if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) + fail_error_message (message); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-play"); + + /* Now play the 10 second composition */ + start_playing_time = gst_util_get_timestamp (); + while (carry_on) { + + if (GST_CLOCK_DIFF (start_playing_time, gst_util_get_timestamp ()) > + total_time + GST_SECOND) { + GST_ERROR ("No EOS found after %" GST_TIME_FORMAT " sec", + GST_TIME_ARGS ((total_time / GST_SECOND) + 1)); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-fail"); + + fail_unless ("No EOS received" == NULL); + + break; + } + + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll: %" GST_PTR_FORMAT, message); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL); + gst_object_unref (pipeline); + gst_object_unref (bus); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnlcomposition"); + TCase *tc_chain = tcase_create ("gnlcomposition"); + + suite_add_tcase (s, tc_chain); + + g_cond_init (&pad_added_cond); + g_mutex_init (&pad_added_lock); + tcase_add_test (tc_chain, test_change_object_start_stop_in_current_stack); + tcase_add_test (tc_chain, test_remove_invalid_object); + if (gst_registry_check_feature_version (gst_registry_get (), "videomixer", 0, + 11, 0)) { + tcase_add_test (tc_chain, test_no_more_pads_race); + } else { + GST_WARNING ("videomixer element not available, skipping 1 test"); + } + + if (gst_registry_check_feature_version (gst_registry_get (), "adder", 1, + 0, 0)) { + tcase_add_test (tc_chain, test_simple_adder); + } else { + GST_WARNING ("adder element not available, skipping 1 test"); + } + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/gnl/gnloperation.c b/tests/check/gnl/gnloperation.c new file mode 100644 index 00000000..d6f7a78f --- /dev/null +++ b/tests/check/gnl/gnloperation.c @@ -0,0 +1,698 @@ +#include "common.h" + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + GList *listcopy = copy_segment_list (segments); + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + collect->expected_base = 0; + collect->expected_segments = listcopy; + collect->gotsegment = FALSE; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +GST_START_TEST (test_simple_operation) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [-- oper --] | 0 + * [------------- source -------------] | 1 + * */ + + /* + source + Start : 0s + Duration : 3s + Priority : 1 + */ + + source = videotest_gnl_src ("source", 0, 3 * GST_SECOND, 2, 1); + fail_if (source == NULL); + + /* + operation + Start : 1s + Duration : 1s + Priority : 0 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 1 * GST_SECOND, 0); + fail_if (oper == NULL); + + /* Add source */ + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + gst_bin_add (GST_BIN (comp), source); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Add operaton */ + + gst_bin_add (GST_BIN (comp), oper); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* remove source */ + + gst_object_ref (source); + gst_bin_remove (GST_BIN (comp), source); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* re-add source */ + gst_bin_add (GST_BIN (comp), source); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations) +{ + GstElement *comp, *oper1, *oper2, *source; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* + source + Start : 0s + Duration : 10s + Priority : 2 + */ + + source = videotest_gnl_src ("source", 0, 10 * GST_SECOND, 2, 2); + + /* + operation1 + Start : 4s + Duration : 2s + Priority : 1 + */ + + oper1 = + new_operation ("oper1", "identity", 4 * GST_SECOND, 2 * GST_SECOND, 1); + + /* + operation2 + Start : 2s + Duration : 6s + Priority : 0 + */ + + oper2 = + new_operation ("oper2", "identity", 2 * GST_SECOND, 6 * GST_SECOND, 0); + + /* Add source */ + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1); + ASSERT_OBJECT_REFCOUNT (oper2, "oper2", 1); + + gst_bin_add (GST_BIN (comp), source); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source, 0, 10 * GST_SECOND, 10 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source, "source", 1); + + /* Add operation 1 */ + + gst_bin_add (GST_BIN (comp), oper1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (oper1, 4 * GST_SECOND, 6 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1); + + /* Add operation 2 */ + + gst_bin_add (GST_BIN (comp), oper2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (oper2, 2 * GST_SECOND, 8 * GST_SECOND, + 6 * GST_SECOND); + check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper1, "oper2", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 6 * GST_SECOND, 8 * GST_SECOND, 6 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 8 * GST_SECOND, 10 * GST_SECOND, 8 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations2) +{ + gboolean ret; + GstElement *comp, *oper, *source1, *source2, *def; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2); + + /* + operation + Start : 1s + Duration : 4s + Priority : 1 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1); + + /* + source2 + Start : 4s + Duration : 2s + Priority : 2 + */ + + source2 = videotest_gnl_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* Add source 2 */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Add operation */ + + gst_bin_add (GST_BIN (comp), oper); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Add default */ + + gst_bin_add (GST_BIN (comp), def); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 5 * GST_SECOND, 6 * GST_SECOND, 5 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + +GST_START_TEST (test_pyramid_operations_expandable) +{ + GstElement *comp, *oper, *source1, *source2, *def; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* + source1 + Start : 0s + Duration : 2s + Priority : 2 + */ + + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2); + + /* + operation (expandable) + Start : XX + Duration : XX + Priority : 1 + */ + + oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1); + g_object_set (oper, "expandable", TRUE, NULL); + + /* + source2 + Start : 4s + Duration : 2s + Priority : 2 + */ + + source2 = videotest_gnl_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2); + + /* + def (default source) + Priority = G_MAXUINT32 + */ + def = + videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2, + G_MAXUINT32); + g_object_set (def, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + ASSERT_OBJECT_REFCOUNT (def, "default", 1); + + /* Add source 1 */ + gst_bin_add (GST_BIN (comp), source1); + /* Add source 2 */ + gst_bin_add (GST_BIN (comp), source2); + /* Add operation */ + gst_bin_add (GST_BIN (comp), oper); + /* Add default */ + gst_bin_add (GST_BIN (comp), def); + + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + check_start_stop_duration (source2, 4 * GST_SECOND, 6 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations) +{ + GstElement *comp, *oper, *source1, *source2; + gboolean ret = FALSE; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 6 | Priority + * ---------------------------------------------------------------------------- + * [ -oper- ] | 1 + * [ -source2- -] | 2 + * [ -source1- -] | 3 + * */ + + /* + source1 + Start : 0s + Duration : 4s + Priority : 3 + */ + + source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 2, 3); + fail_if (source1 == NULL); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 2 + */ + + source2 = + videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 2); + fail_if (source2 == NULL); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + */ + + oper = + new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + gst_bin_add (GST_BIN (comp), source1); + check_start_stop_duration (comp, 0, 0, 0); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + gst_bin_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 0, 0); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + gst_bin_add (GST_BIN (comp), oper); + check_start_stop_duration (comp, 0, 0, 0); + + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations_bis) +{ + GstElement *comp, *oper, *source1, *source2; + gboolean ret; + GList *segments = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [ ......................[------ oper ----------]..........] | 1 EXPANDABLE + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------] | 3 + * */ + + + /* + source1 + Start : 0s + Duration : 4s + Priority : 2 + */ + + source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 3 + */ + + source2 = + videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 3); + fail_if (source2 == NULL); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + EXPANDABLE + */ + + oper = + new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + g_object_set (oper, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + gst_bin_add (GST_BIN (comp), oper); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + /* Since it's expandable, it should have changed to full length */ + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND)); + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, + 0 * GST_SECOND, 2 * GST_SECOND, 4 * GST_SECOND)); + + fill_pipeline_and_check (comp, segments); +} + +GST_END_TEST; + + + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnloperation"); + TCase *tc_chain = tcase_create ("gnloperation"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_simple_operation); + tcase_add_test (tc_chain, test_pyramid_operations); + tcase_add_test (tc_chain, test_pyramid_operations2); + tcase_add_test (tc_chain, test_pyramid_operations_expandable); + if (gst_registry_check_feature_version (gst_registry_get (), "videomixer", 0, + 11, 0)) { + tcase_add_test (tc_chain, test_complex_operations); + tcase_add_test (tc_chain, test_complex_operations_bis); + } else + GST_WARNING ("videomixer element not available, skipping 1 test"); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/gnl/gnlsource.c b/tests/check/gnl/gnlsource.c new file mode 100644 index 00000000..e2d568a2 --- /dev/null +++ b/tests/check/gnl/gnlsource.c @@ -0,0 +1,220 @@ +#include "common.h" + +GST_START_TEST (test_simple_videotestsrc) +{ + GstElement *pipeline; + GstElement *gnlsource, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + gnlsource = + videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (gnlsource == NULL); + check_start_stop_duration (gnlsource, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), gnlsource, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = gnlsource; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + + g_signal_connect (G_OBJECT (gnlsource), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + fail_if (sinkpad == NULL); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (pipeline); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (gnlsource, "gnlsource", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll"); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + GST_DEBUG ("Resetted pipeline to NULL"); + + gst_object_unref (pipeline); + gst_object_unref (bus); + + g_free (collect); +} + +GST_END_TEST; + +GST_START_TEST (test_videotestsrc_in_bin) +{ + GstElement *pipeline; + GstElement *gnlsource, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + + /* + Source 1 + Start : 1s + Duration : 1s + Priority : 1 + */ + gnlsource = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 2, 1); + /* Handle systems which don't have alpha available */ + if (gnlsource == NULL) + return; + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), gnlsource, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = gnlsource; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + g_signal_connect (G_OBJECT (gnlsource), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + fail_if (sinkpad == NULL); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (pipeline); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (gnlsource, "gnlsource", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + GST_LOG ("poll"); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to NULL"); + + gst_object_unref (pipeline); + gst_object_unref (bus); + + g_free (collect); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnlsource"); + TCase *tc_chain = tcase_create ("gnlsource"); + + suite_add_tcase (s, tc_chain); + + if (0) + tcase_add_test (tc_chain, test_simple_videotestsrc); + tcase_add_test (tc_chain, test_videotestsrc_in_bin); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/gnl/seek.c b/tests/check/gnl/seek.c new file mode 100644 index 00000000..a3471d86 --- /dev/null +++ b/tests/check/gnl/seek.c @@ -0,0 +1,764 @@ +#include "common.h" + +typedef struct _SeekInfo +{ + GstClockTime position; /* Seek value and segment position */ + GstClockTime start; /* Segment start */ + GstClockTime stop; /* Segment stop */ + gboolean expect_failure; /* Whether we expect the seek to fail or not */ +} SeekInfo; + +static SeekInfo * +new_seek_info (GstClockTime position, GstClockTime start, GstClockTime stop, + gboolean expect_failure) +{ + SeekInfo *info = g_new0 (SeekInfo, 1); + + info->position = position; + info->start = start; + info->stop = stop; + info->expect_failure = expect_failure; + + return info; +} + +static void +fill_pipeline_and_check (GstElement * comp, GList * segments, GList * seeks) +{ + GstElement *pipeline, *sink; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE, expected_failure; + GstPad *sinkpad; + GList *ltofree = seeks; + + pipeline = gst_pipeline_new ("test_pipeline"); + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = segments; + collect->keep_expected_segments = TRUE; + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + break; + case GST_MESSAGE_ASYNC_DONE: + GST_DEBUG ("prerolling done"); + + if (seeks == NULL) { + carry_on = FALSE; + g_list_free_full (collect->expected_segments, g_free); + collect->expected_segments = NULL; + GST_DEBUG ("Done seeking"); + break; + } + + g_list_free_full (collect->expected_segments, g_free); + collect->expected_segments = NULL; + expected_failure = TRUE; + while (expected_failure && carry_on) { + SeekInfo *sinfo = (SeekInfo *) seeks->data; + + seeks = seeks->next; + + if (!sinfo->expect_failure) { + collect->gotsegment = FALSE; + collect->expected_base = 0; + collect->expected_segments = + g_list_append (collect->expected_segments, segment_new (1.0, + GST_FORMAT_TIME, sinfo->start, sinfo->stop, + sinfo->position)); + + expected_failure = FALSE; + } + + GST_DEBUG ("Seeking to %" GST_TIME_FORMAT ", Expecting (%" + GST_TIME_FORMAT " %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (sinfo->position), GST_TIME_ARGS (sinfo->start), + GST_TIME_ARGS (sinfo->stop)); + + fail_unless_equals_int (gst_element_seek_simple (pipeline, + GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, sinfo->position), + !sinfo->expect_failure); + + if (!sinfo->expect_failure) { + g_free (sinfo); + break; + } + + if (seeks == NULL) + carry_on = FALSE; + g_free (sinfo); + } + break; + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to READY"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_list_free (ltofree); + g_free (collect); +} + +static void +test_simplest_full (void) +{ + gboolean ret; + GstElement *comp, *source1; + GList *segments = NULL; + GList *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND, TRUE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND, TRUE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_after_other_full (void) +{ + gboolean ret; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [5 source1 ][2 source2 ] | 1 + * + * */ + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Media start : 2s + Priority : 1 + */ + source2 = videotest_gnl_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add sources */ + gst_bin_add (GST_BIN (comp), source1); + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1, + 6 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, 2 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1, + 3 * GST_SECOND - 1, 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, 3 * GST_SECOND, + 3 * GST_SECOND, TRUE)); + + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_under_another_full (void) +{ + gboolean ret; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [ source1 ] | 1 + * [ source2 ] | 2 + * + * */ + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 2s + Priority : 2 + */ + source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add two sources */ + + gst_bin_add (GST_BIN (comp), source1); + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0)); + + + /* Hit source1 */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 1 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND, + 1 * GST_SECOND, FALSE)); + /* Hit source1 over source2 */ + seeks = + g_list_append (seeks, new_seek_info (1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + /* Hit source2 */ + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, 2 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 2.5 * GST_SECOND, + 3 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +static void +test_one_bin_after_other_full (void) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + + /* Hit source1 */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND, + GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND - 1, GST_SECOND - 1, + GST_SECOND, FALSE)); + /* Hit source2 */ + seeks = + g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (GST_SECOND, GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1, + 2 * GST_SECOND - 1, 2 * GST_SECOND, FALSE)); + /* Should fail */ + seeks = + g_list_append (seeks, new_seek_info (2 * GST_SECOND, GST_SECOND, + GST_SECOND, TRUE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + + +GST_START_TEST (test_complex_operations) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [------ oper ----------] | 1 + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------] | 3 + * */ + + /* + source1 + Start : 0s + Duration : 4s + Priority : 3 + */ + + source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 2, 3); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 2 + */ + + source2 = + videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + */ + + oper = + new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND, + 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + gst_bin_add (GST_BIN (comp), oper); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + + /* Seeks */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + /* and backwards */ + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND, + 2 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND, + 6 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +GST_END_TEST; + + +GST_START_TEST (test_complex_operations_bis) +{ + gboolean ret = FALSE; + GstElement *comp, *oper, *source1, *source2; + GList *segments = NULL, *seeks = NULL; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* TOPOLOGY + * + * 0 1 2 3 4 .. 6 | Priority + * ---------------------------------------------------------------------------- + * [ ......................[------ oper ----------]..........] | 1 EXPANDABLE + * [--------------------- source1 ----------------] | 2 + * [------------ source2 ------] | 3 + * */ + + + /* + source1 + Start : 0s + Duration : 4s + Priority : 2 + */ + + source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 3, 2); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + /* + source2 + Start : 2s + Duration : 4s + Priority : 3 + */ + + source2 = + videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2, + 3); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + + /* + operation + Start : 2s + Duration : 2s + Priority : 1 + EXPANDABLE + */ + + oper = + new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1); + fail_if (oper == NULL); + check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND, + 2 * GST_SECOND); + g_object_set (oper, "expandable", TRUE, NULL); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Add source1 */ + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Add source2 */ + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Add operaton */ + + gst_bin_add (GST_BIN (comp), oper); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND); + check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND); + check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND, + 6 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + + /* Expected segments */ + segments = g_list_append (segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0)); + + /* Seeks */ + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + /* and backwards */ + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + + seeks = + g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + seeks = + g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND, + 1.5 * GST_SECOND, FALSE)); + + fill_pipeline_and_check (comp, segments, seeks); +} + +GST_END_TEST; + + +GST_START_TEST (test_simplest) +{ + test_simplest_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_after_other) +{ + test_one_after_other_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_under_another) +{ + test_one_under_another_full (); +} + +GST_END_TEST; + + +GST_START_TEST (test_one_bin_after_other) +{ + test_one_bin_after_other_full (); +} + +GST_END_TEST; + + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-seek"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_simplest); + tcase_add_test (tc_chain, test_one_after_other); + tcase_add_test (tc_chain, test_one_under_another); + tcase_add_test (tc_chain, test_one_bin_after_other); + tcase_add_test (tc_chain, test_complex_operations); + tcase_add_test (tc_chain, test_complex_operations_bis); + + return s; +} + +GST_CHECK_MAIN (gnonlin) diff --git a/tests/check/gnl/simple.c b/tests/check/gnl/simple.c new file mode 100644 index 00000000..c305a6c5 --- /dev/null +++ b/tests/check/gnl/simple.c @@ -0,0 +1,791 @@ +#include "common.h" + +static void +test_simplest_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1; + CollectStructure *collect; + GstBus *bus; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Media Duartion : 1s + Priority : 1 + */ + source1 = + videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + poll_the_bus (bus); + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_base = 0; + collect->gotsegment = FALSE; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus AGAIN"); + + poll_the_bus (bus); + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +static void +test_time_duration_full (void) +{ + gboolean ret = FALSE; + GstElement *comp, *source1, *source2; + + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret == TRUE); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + ret = FALSE; + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret == TRUE); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + gst_object_unref (source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + gst_object_unref (comp); +} + +static void +test_one_after_other_full (void) +{ + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + gboolean ret = FALSE; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Media start : 5s + Priority : 1 + */ + source1 = + videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3, + 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Media start : 2s + Priority : 1 + */ + source2 = videotest_gnl_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND, + 2 * GST_SECOND, 2, 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + gst_bin_add (GST_BIN (comp), source2); + + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret); + check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + gst_object_unref (source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + + GST_DEBUG ("Let's poll the bus AGAIN"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (TRUE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } else { + GST_DEBUG ("bus_poll responded, but there wasn't any message..."); + } + } + + fail_if (collect->expected_segments != NULL); + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +static void +test_one_under_another_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* TOPOLOGY + * + * 0 1 2 3 4 5 | Priority + * ---------------------------------------------------------------------------- + * [- source1 -] | 1 + * [- source2 -] | 2 + * */ + + /* + Source 1 + Start : 0s + Duration : 2s + Priority : 1 + */ + source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 1); + fail_if (source1 == NULL); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 2s + Priority : 2 + */ + source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 2); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Add two sources */ + + gst_bin_add (GST_BIN (comp), source1); + gst_bin_add (GST_BIN (comp), source2); + check_start_stop_duration (comp, 0, 0 * GST_SECOND, 0 * GST_SECOND); + /* Now commiting changes */ + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Remove second source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND, + 2 * GST_SECOND); + + /* Re-add second source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND); + gst_object_unref (source1); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0)); + + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, GST_SECOND, 2 * GST_SECOND, + GST_SECOND)); + + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND)); + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* check if the segment is the correct one (0s-4s) */ + carry_on = FALSE; + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_message_unref (message); + } + } + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + gst_object_unref (GST_OBJECT (sinkpad)); + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +static void +test_one_bin_after_other_full (void) +{ + gboolean ret = FALSE; + GstElement *pipeline; + GstElement *comp, *sink, *source1, *source2; + CollectStructure *collect; + GstBus *bus; + GstMessage *message; + gboolean carry_on = TRUE; + GstPad *sinkpad; + + pipeline = gst_pipeline_new ("test_pipeline"); + comp = + gst_element_factory_make_or_warn ("gnlcomposition", "test_composition"); + fail_if (comp == NULL); + + /* + Source 1 + Start : 0s + Duration : 1s + Priority : 1 + */ + source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1); + if (source1 == NULL) { + gst_object_unref (pipeline); + gst_object_unref (comp); + return; + } + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + /* + Source 2 + Start : 1s + Duration : 1s + Priority : 1 + */ + source2 = + videotest_in_bin_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, + 1); + fail_if (source2 == NULL); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + /* Add one source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + fail_unless (ret); + check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Second source */ + + gst_bin_add (GST_BIN (comp), source2); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND); + check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source2, "source2", 1); + + /* Remove first source */ + + gst_object_ref (source1); + gst_bin_remove (GST_BIN (comp), source1); + check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND, + 1 * GST_SECOND); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + /* Re-add first source */ + + gst_bin_add (GST_BIN (comp), source1); + g_signal_emit_by_name (comp, "commit", TRUE, &ret); + check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); + gst_object_unref (source1); + + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + sink = gst_element_factory_make_or_warn ("fakesink", "sink"); + fail_if (sink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); + + /* Shared data */ + collect = g_new0 (CollectStructure, 1); + collect->comp = comp; + collect->sink = sink; + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + + g_signal_connect (G_OBJECT (comp), "pad-added", + G_CALLBACK (composition_pad_added_cb), collect); + + sinkpad = gst_element_get_static_pad (sink, "sink"); + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, + (GstPadProbeCallback) sinkpad_probe, collect, NULL); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + GST_WARNING ("Got an EOS"); + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + GST_DEBUG ("Setting pipeline to NULL"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + + fail_if (collect->expected_segments != NULL); + + GST_DEBUG ("Resetted pipeline to READY"); + + /* Expected segments */ + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0)); + collect->expected_segments = g_list_append (collect->expected_segments, + segment_new (1.0, GST_FORMAT_TIME, + 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND)); + collect->gotsegment = FALSE; + collect->expected_base = 0; + + GST_DEBUG ("Setting pipeline to PLAYING again"); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + /* we should check if we really finished here */ + carry_on = FALSE; + break; + case GST_MESSAGE_SEGMENT_START: + case GST_MESSAGE_SEGMENT_DONE: + /* We shouldn't see any segement messages, since we didn't do a segment seek */ + GST_WARNING ("Saw a Segment start/stop"); + fail_if (FALSE); + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + default: + break; + } + gst_mini_object_unref (GST_MINI_OBJECT (message)); + } + } + + gst_object_unref (GST_OBJECT (sinkpad)); + + fail_if (collect->expected_segments != NULL); + + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + + g_free (collect); +} + +GST_START_TEST (test_simplest) +{ + test_simplest_full (); +} + +GST_END_TEST; + +GST_START_TEST (test_time_duration) +{ + test_time_duration_full (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_after_other) +{ + test_one_after_other_full (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_under_another) +{ + test_one_under_another_full (); +} + +GST_END_TEST; + +GST_START_TEST (test_one_bin_after_other) +{ + test_one_bin_after_other_full (); +} + +GST_END_TEST; + +static Suite * +gnonlin_suite (void) +{ + Suite *s = suite_create ("gnonlin-simple"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_time_duration); + tcase_add_test (tc_chain, test_simplest); + tcase_add_test (tc_chain, test_one_after_other); + tcase_add_test (tc_chain, test_one_under_another); + tcase_add_test (tc_chain, test_one_bin_after_other); + return s; +} + +GST_CHECK_MAIN (gnonlin) |