summaryrefslogtreecommitdiff
path: root/plugins/nle
diff options
context:
space:
mode:
authorThibault Saunier <tsaunier@gnome.org>2015-08-19 11:24:11 +0200
committerThibault Saunier <tsaunier@gnome.org>2015-08-20 13:49:20 +0200
commitedc5c27ffbcc527d6bc8121463228eaa015e9cdd (patch)
tree30b92f073e0f4ea6f732879d9205b78dd2152ba2 /plugins/nle
parentfdd1456062f64714cd57108380e696d28b14aad6 (diff)
Move NLE to a dedicated GstPlugin
Summary: Allowing external user to directly use it Reviewers: Mathieu_Du Differential Revision: https://phabricator.freedesktop.org/D231
Diffstat (limited to 'plugins/nle')
-rw-r--r--plugins/nle/.gitignore8
-rw-r--r--plugins/nle/Makefile.am30
-rw-r--r--plugins/nle/gnlmarshal.list2
-rw-r--r--plugins/nle/gstnle.c66
-rw-r--r--plugins/nle/nle.h36
-rw-r--r--plugins/nle/nlecomposition.c3069
-rw-r--r--plugins/nle/nlecomposition.h66
-rw-r--r--plugins/nle/nleghostpad.c803
-rw-r--r--plugins/nle/nleghostpad.h48
-rw-r--r--plugins/nle/nleobject.c738
-rw-r--r--plugins/nle/nleobject.h173
-rw-r--r--plugins/nle/nleoperation.c843
-rw-r--r--plugins/nle/nleoperation.h90
-rw-r--r--plugins/nle/nlesource.c505
-rw-r--r--plugins/nle/nlesource.h66
-rw-r--r--plugins/nle/nletypes.h42
-rw-r--r--plugins/nle/nleurisource.c166
-rw-r--r--plugins/nle/nleurisource.h57
18 files changed, 6808 insertions, 0 deletions
diff --git a/plugins/nle/.gitignore b/plugins/nle/.gitignore
new file mode 100644
index 00000000..3ac440bf
--- /dev/null
+++ b/plugins/nle/.gitignore
@@ -0,0 +1,8 @@
+Makefile
+Makefile.in
+*.o
+*.lo
+*.la
+.deps
+.libs
+nleversion.h
diff --git a/plugins/nle/Makefile.am b/plugins/nle/Makefile.am
new file mode 100644
index 00000000..2e8fa57d
--- /dev/null
+++ b/plugins/nle/Makefile.am
@@ -0,0 +1,30 @@
+plugin_LTLIBRARIES = libgstnle.la
+
+libgstnle_la_SOURCES = gstnle.c \
+ nleobject.c \
+ nlecomposition.c \
+ nleghostpad.c \
+ nleoperation.c \
+ nlesource.c \
+ nleurisource.c
+
+libgstnle_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_BASE_CFLAGS) $(GST_CFLAGS) \
+ -I$(top_srcdir)
+
+libgstnle_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \
+ $(GST_BASE_LIBS) $(GST_LIBS)
+
+libgstnle_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstnle_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = \
+ nle.h \
+ nleobject.h \
+ nlecomposition.h \
+ nletypes.h \
+ nleghostpad.h \
+ nleoperation.h \
+ nlesource.h \
+ nletypes.h \
+ nleurisource.h
diff --git a/plugins/nle/gnlmarshal.list b/plugins/nle/gnlmarshal.list
new file mode 100644
index 00000000..67c63946
--- /dev/null
+++ b/plugins/nle/gnlmarshal.list
@@ -0,0 +1,2 @@
+VOID:OBJECT,UINT
+
diff --git a/plugins/nle/gstnle.c b/plugins/nle/gstnle.c
new file mode 100644
index 00000000..df32330a
--- /dev/null
+++ b/plugins/nle/gstnle.c
@@ -0,0 +1,66 @@
+/* Non Linear Engine plugin
+ *
+ * Copyright (C) 2015 Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * gstnle.c
+ *
+ * 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 <gst/gst.h>
+#include "nle.h"
+
+struct _elements_entry
+{
+ const gchar *name;
+ GType (*type) (void);
+};
+
+static struct _elements_entry _elements[] = {
+ {"nlesource", nle_source_get_type},
+ {"nlecomposition", nle_composition_get_type},
+ {"nleoperation", nle_operation_get_type},
+ {"nleurisource", nle_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;
+
+ nle_init_ghostpad_category ();
+
+ return TRUE;
+}
+
+/* plugin export resolution */
+GST_PLUGIN_DEFINE
+ (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ nle,
+ "GStreamer Non Linear Engine",
+ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/plugins/nle/nle.h b/plugins/nle/nle.h
new file mode 100644
index 00000000..6e00eae4
--- /dev/null
+++ b/plugins/nle/nle.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 __NLE_H__
+#define __NLE_H__
+
+#include <gst/gst.h>
+
+#include "nletypes.h"
+
+#include "nleobject.h"
+#include "nleghostpad.h"
+#include "nlesource.h"
+#include "nlecomposition.h"
+#include "nleoperation.h"
+
+#include "nleurisource.h"
+
+#endif /* __GST_H__ */
diff --git a/plugins/nle/nlecomposition.c b/plugins/nle/nlecomposition.c
new file mode 100644
index 00000000..7b69589b
--- /dev/null
+++ b/plugins/nle/nlecomposition.c
@@ -0,0 +1,3069 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ * 2004-2008 Edward Hervey <bilboed@bilboed.com>
+ * 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
+ * 2014 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * 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 "nle.h"
+
+/**
+ * SECTION:element-nlecomposition
+ *
+ * A NleComposition contains NleObjects such as NleSources and NleOperations,
+ * and connects them dynamically to create a composition timeline.
+ */
+
+static GstStaticPadTemplate nle_composition_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (nlecomposition_debug);
+#define GST_CAT_DEFAULT nlecomposition_debug
+
+#define _do_init \
+ GST_DEBUG_CATEGORY_INIT (nlecomposition_debug,"nlecomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Composition");
+#define nle_composition_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (NleComposition, nle_composition, NLE_TYPE_OBJECT,
+ _do_init);
+
+enum
+{
+ PROP_0,
+ PROP_DEACTIVATED_ELEMENTS_STATE,
+ PROP_LAST,
+};
+
+/* Properties from NleObject */
+enum
+{
+ NLEOBJECT_PROP_START,
+ NLEOBJECT_PROP_STOP,
+ NLEOBJECT_PROP_DURATION,
+ NLEOBJECT_PROP_LAST
+};
+
+enum
+{
+ COMMIT_SIGNAL,
+ COMMITED_SIGNAL,
+ QUERY_POSITION_SIGNAL,
+ LAST_SIGNAL
+};
+
+typedef enum
+{
+ COMP_UPDATE_STACK_INITIALIZE,
+ COMP_UPDATE_STACK_ON_COMMIT,
+ COMP_UPDATE_STACK_ON_EOS,
+ COMP_UPDATE_STACK_ON_SEEK,
+ COMP_UPDATE_STACK_NONE
+} NleUpdateStackReason;
+
+static const char *UPDATE_PIPELINE_REASONS[] = {
+ "Initialize", "Commit", "EOS", "Seek"
+};
+
+typedef struct
+{
+ NleComposition *comp;
+ GstEvent *event;
+} SeekData;
+
+typedef struct
+{
+ NleComposition *comp;
+ NleObject *object;
+} ChildIOData;
+
+typedef struct
+{
+ NleComposition *comp;
+ gint32 seqnum;
+
+ NleUpdateStackReason reason;
+} UpdateCompositionData;
+
+struct _NleCompositionPrivate
+{
+ gboolean dispose_has_run;
+
+ /*
+ Sorted List of NleObjects , ThreadSafe
+ objects_start : sorted by start-time then priority
+ objects_stop : sorted by stop-time then priority
+ objects_hash : contains all controlled objects
+
+ Those list should be manipulated exclusively in the main context
+ or while the task is totally stopped.
+ */
+ GList *objects_start;
+ GList *objects_stop;
+ GHashTable *objects_hash;
+
+ /* List of NleObject to be inserted or removed from the composition on the
+ * next commit */
+ GHashTable *pending_io;
+
+ gulong ghosteventprobe;
+
+ /* current stack, list of NleObject* */
+ GNode *current;
+
+ /* List of NleObject whose start/duration will be the same as the composition */
+ GList *expandables;
+
+ /*
+ 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;
+
+ /* Seek segment handler */
+ GstSegment *segment;
+ GstSegment *outside_segment;
+
+ /* Next running base_time to set on outgoing segment */
+ guint64 next_base_time;
+
+ /*
+ OUR sync_handler on the child_bus
+ We are called before nle_object_sync_handler
+ */
+ GstPadEventFunction nle_event_pad_func;
+ gboolean send_stream_start;
+
+ /* Protect the actions list */
+ GMutex actions_lock;
+ GCond actions_cond;
+ GList *actions;
+
+ gboolean running;
+ gboolean initialized;
+
+ GstElement *current_bin;
+
+ gboolean seeking_itself;
+ gint real_eos_seqnum;
+ gint next_eos_seqnum;
+ gint flush_seqnum;
+
+ /* 0 means that we already received the right caps or segment */
+ gint seqnum_to_restart_task;
+ gboolean waiting_for_buffer;
+
+ gboolean tearing_down_stack;
+
+ NleUpdateStackReason updating_reason;
+};
+
+typedef struct _Action
+{
+ GCClosure closure;
+ gint priority;
+} Action;
+
+#define ACTION_CALLBACK(__action) (((GCClosure*) (__action))->callback)
+
+static guint _signals[LAST_SIGNAL] = { 0 };
+
+static GParamSpec *nleobject_properties[NLEOBJECT_PROP_LAST];
+
+#define OBJECT_IN_ACTIVE_SEGMENT(comp,element) \
+ ((NLE_OBJECT_START(element) < comp->priv->segment_stop) && \
+ (NLE_OBJECT_STOP(element) >= comp->priv->segment_start))
+
+static void nle_composition_dispose (GObject * object);
+static void nle_composition_finalize (GObject * object);
+static void nle_composition_reset (NleComposition * comp);
+
+static gboolean nle_composition_add_object (GstBin * bin, GstElement * element);
+
+static gboolean
+nle_composition_remove_object (GstBin * bin, GstElement * element);
+
+static GstStateChangeReturn
+nle_composition_change_state (GstElement * element, GstStateChange transition);
+
+static inline void nle_composition_reset_target_pad (NleComposition * comp);
+
+static gboolean
+seek_handling (NleComposition * comp, gint32 seqnum,
+ NleUpdateStackReason update_stack_reason);
+static gint objects_start_compare (NleObject * a, NleObject * b);
+static gint objects_stop_compare (NleObject * a, NleObject * b);
+static GstClockTime get_current_position (NleComposition * comp);
+
+static gboolean update_pipeline (NleComposition * comp,
+ GstClockTime currenttime, gint32 seqnum,
+ NleUpdateStackReason update_stack_reason);
+static gboolean nle_composition_commit_func (NleObject * object,
+ gboolean recurse);
+static void update_start_stop_duration (NleComposition * comp);
+
+static gboolean
+nle_composition_event_handler (GstPad * ghostpad, GstObject * parent,
+ GstEvent * event);
+static void _relink_single_node (NleComposition * comp, GNode * node,
+ GstEvent * toplevel_seek);
+static void _update_pipeline_func (NleComposition * comp,
+ UpdateCompositionData * ucompo);
+static void _commit_func (NleComposition * comp,
+ UpdateCompositionData * ucompo);
+static GstEvent *get_new_seek_event (NleComposition * comp, gboolean initial,
+ gboolean updatestoponly);
+static gboolean _nle_composition_add_object (NleComposition * comp,
+ NleObject * object);
+static gboolean _nle_composition_remove_object (NleComposition * comp,
+ NleObject * object);
+static void _deactivate_stack (NleComposition * comp,
+ gboolean flush_downstream);
+static gboolean _set_real_eos_seqnum_from_seek (NleComposition * comp,
+ GstEvent * event);
+static void _emit_commited_signal_func (NleComposition * comp, gpointer udata);
+static void _restart_task (NleComposition * comp);
+static void
+_add_action (NleComposition * comp, GCallback func, gpointer data,
+ gint priority);
+static gboolean
+_is_ready_to_restart_task (NleComposition * comp, GstEvent * event);
+
+
+/* COMP_REAL_START: actual position to start current playback at. */
+#define COMP_REAL_START(comp) \
+ (MAX (comp->priv->segment->start, NLE_OBJECT_START (comp)))
+
+#define COMP_REAL_STOP(comp) \
+ (GST_CLOCK_TIME_IS_VALID (comp->priv->segment->stop) ? \
+ (MIN (comp->priv->segment->stop, NLE_OBJECT_STOP (comp))) : \
+ NLE_OBJECT_STOP (comp))
+
+#define ACTIONS_LOCK(comp) G_STMT_START { \
+ GST_LOG_OBJECT (comp, "Getting ACTIONS_LOCK in thread %p", \
+ g_thread_self()); \
+ g_mutex_lock(&((NleComposition*)comp)->priv->actions_lock); \
+ GST_LOG_OBJECT (comp, "Got ACTIONS_LOCK in thread %p", \
+ g_thread_self()); \
+} G_STMT_END
+
+#define ACTIONS_UNLOCK(comp) G_STMT_START { \
+ g_mutex_unlock(&((NleComposition*)comp)->priv->actions_lock); \
+ GST_LOG_OBJECT (comp, "Unlocked ACTIONS_LOCK in thread %p", \
+ g_thread_self()); \
+} G_STMT_END
+
+#define WAIT_FOR_AN_ACTION(comp) G_STMT_START { \
+ GST_LOG_OBJECT (comp, "Waiting for an action in thread %p", \
+ g_thread_self()); \
+ g_cond_wait(&((NleComposition*)comp)->priv->actions_cond, \
+ &((NleComposition*)comp)->priv->actions_lock); \
+ GST_LOG_OBJECT (comp, "Done WAITING for an action in thread %p", \
+ g_thread_self()); \
+} G_STMT_END
+
+#define SIGNAL_NEW_ACTION(comp) G_STMT_START { \
+ GST_LOG_OBJECT (comp, "Signalling new action from thread %p", \
+ g_thread_self()); \
+ g_cond_signal(&((NleComposition*)comp)->priv->actions_cond); \
+} G_STMT_END
+
+#define GET_TASK_LOCK(comp) (&(NLE_COMPOSITION(comp)->task_rec_lock))
+
+static inline gboolean
+_have_to_flush_downstream (NleUpdateStackReason update_reason)
+{
+ if (update_reason == COMP_UPDATE_STACK_ON_COMMIT ||
+ update_reason == COMP_UPDATE_STACK_ON_SEEK ||
+ update_reason == COMP_UPDATE_STACK_INITIALIZE)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+_assert_proper_thread (NleComposition * comp)
+{
+ if (comp->task && gst_task_get_state (comp->task) != GST_TASK_STOPPED &&
+ g_thread_self () != comp->task->thread) {
+ g_warning ("Trying to touch children in a thread different from"
+ " its dedicated thread!");
+ }
+}
+
+static void
+_remove_actions_for_type (NleComposition * comp, GCallback callback)
+{
+ GList *tmp;
+
+ ACTIONS_LOCK (comp);
+ for (tmp = comp->priv->actions; tmp; tmp = tmp->next) {
+ Action *act = tmp->data;
+
+ if (ACTION_CALLBACK (act) == callback) {
+ g_closure_unref ((GClosure *) act);
+ comp->priv->actions = g_list_delete_link (comp->priv->actions, tmp);
+ }
+ }
+ ACTIONS_UNLOCK (comp);
+
+}
+
+static void
+_execute_actions (NleComposition * comp)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ ACTIONS_LOCK (comp);
+ if (priv->running == FALSE) {
+ GST_DEBUG_OBJECT (comp, "Not running anymore");
+
+ ACTIONS_UNLOCK (comp);
+ return;
+ }
+
+ if (priv->actions == NULL)
+ WAIT_FOR_AN_ACTION (comp);
+
+ if (comp->priv->running == FALSE) {
+ GST_INFO_OBJECT (comp, "Done waiting but not running anymore");
+
+ ACTIONS_UNLOCK (comp);
+ return;
+ }
+
+ if (priv->actions) {
+ GValue params[1] = { G_VALUE_INIT };
+ GList *lact;
+
+ g_value_init (&params[0], G_TYPE_OBJECT);
+ g_value_set_object (&params[0], comp);
+
+ lact = priv->actions;
+ priv->actions = priv->actions->next;
+ ACTIONS_UNLOCK (comp);
+
+ GST_INFO_OBJECT (comp, "Invoking %p:%s",
+ lact->data, GST_DEBUG_FUNCPTR_NAME ((ACTION_CALLBACK (lact->data))));
+ g_closure_invoke (lact->data, NULL, 1, params, NULL);
+ g_value_unset (&params[0]);
+ } else {
+ ACTIONS_UNLOCK (comp);
+ }
+}
+
+static void
+_start_task (NleComposition * comp)
+{
+ GstTask *task;
+
+ ACTIONS_LOCK (comp);
+ comp->priv->running = TRUE;
+ ACTIONS_UNLOCK (comp);
+
+ GST_OBJECT_LOCK (comp);
+
+ task = comp->task;
+ if (task == NULL) {
+ gchar *taskname =
+ g_strdup_printf ("%s_update_management", GST_OBJECT_NAME (comp));
+
+ task = gst_task_new ((GstTaskFunction) _execute_actions, comp, NULL);
+ gst_object_set_name (GST_OBJECT_CAST (task), taskname);
+ gst_task_set_lock (task, GET_TASK_LOCK (comp));
+ GST_DEBUG_OBJECT (comp, "created task %p", task);
+ comp->task = task;
+ }
+
+ gst_task_set_state (task, GST_TASK_STARTED);
+ GST_OBJECT_UNLOCK (comp);
+}
+
+static gboolean
+_stop_task (NleComposition * comp)
+{
+ gboolean res = TRUE;
+ GstTask *task;
+
+ GST_INFO_OBJECT (comp, "Stoping children management task");
+
+ ACTIONS_LOCK (comp);
+ comp->priv->running = FALSE;
+
+ /* Make sure we do not stay blocked trying to execute an action */
+ SIGNAL_NEW_ACTION (comp);
+ ACTIONS_UNLOCK (comp);
+
+ GST_DEBUG_OBJECT (comp, "stop task");
+
+ GST_OBJECT_LOCK (comp);
+ task = comp->task;
+ if (task == NULL)
+ goto no_task;
+ comp->task = NULL;
+ res = gst_task_set_state (task, GST_TASK_STOPPED);
+ GST_OBJECT_UNLOCK (comp);
+
+ if (!gst_task_join (task))
+ goto join_failed;
+
+ gst_object_unref (task);
+
+ return res;
+
+no_task:
+ {
+ GST_OBJECT_UNLOCK (comp);
+
+ /* this is not an error */
+ return TRUE;
+ }
+join_failed:
+ {
+ /* this is bad, possibly the application tried to join the task from
+ * the task's thread. We install the task again so that it will be stopped
+ * again from the right thread next time hopefully. */
+ GST_OBJECT_LOCK (comp);
+ GST_DEBUG_OBJECT (comp, "join failed");
+ /* we can only install this task if there was no other task */
+ if (comp->task == NULL)
+ comp->task = task;
+ GST_OBJECT_UNLOCK (comp);
+
+ return FALSE;
+ }
+
+ return res;
+}
+
+static void
+_post_start_composition_update (NleComposition * comp,
+ gint32 seqnum, NleUpdateStackReason reason)
+{
+ GstMessage *msg;
+
+ msg = gst_message_new_element (GST_OBJECT (comp),
+ gst_structure_new ("NleCompositionStartUpdate",
+ "reason", G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason], NULL));
+
+ gst_message_set_seqnum (msg, seqnum);
+ gst_element_post_message (GST_ELEMENT (comp), msg);
+}
+
+static void
+_post_start_composition_update_done (NleComposition * comp,
+ gint32 seqnum, NleUpdateStackReason reason)
+{
+ GstMessage *msg = gst_message_new_element (GST_OBJECT (comp),
+ gst_structure_new ("NleCompositionUpdateDone",
+ "reason", G_TYPE_STRING, UPDATE_PIPELINE_REASONS[reason],
+ NULL));
+
+ gst_message_set_seqnum (msg, seqnum);
+ gst_element_post_message (GST_ELEMENT (comp), msg);
+}
+
+static void
+_seek_pipeline_func (NleComposition * comp, SeekData * seekd)
+{
+ gdouble rate;
+ GstFormat format;
+ GstSeekFlags flags;
+ GstSeekType cur_type, stop_type;
+ gint64 cur, stop;
+ NleCompositionPrivate *priv = comp->priv;
+
+ gst_event_parse_seek (seekd->event, &rate, &format, &flags,
+ &cur_type, &cur, &stop_type, &stop);
+
+ GST_DEBUG_OBJECT (seekd->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 (seekd->comp, "Segment now has flags:%d",
+ priv->segment->flags);
+
+ if (priv->segment->start >= NLE_OBJECT_STOP (seekd->comp)) {
+ GST_INFO_OBJECT (seekd->comp,
+ "Start %" GST_TIME_FORMAT " > comp->stop: %" GST_TIME_FORMAT
+ " Not seeking", GST_TIME_ARGS (priv->segment->start),
+ GST_TIME_ARGS (NLE_OBJECT_STOP (seekd->comp)));
+ GST_FIXME_OBJECT (seekd->comp, "HANDLE error async!");
+ return;
+ }
+
+ _post_start_composition_update (seekd->comp,
+ gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
+
+ /* 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, NLE_OBJECT_START (seekd->comp));
+ priv->segment->stop =
+ MIN (priv->segment->stop, NLE_OBJECT_STOP (seekd->comp));
+
+ priv->next_base_time = 0;
+
+ seek_handling (seekd->comp, gst_event_get_seqnum (seekd->event),
+ COMP_UPDATE_STACK_ON_SEEK);
+
+ _post_start_composition_update_done (seekd->comp,
+ gst_event_get_seqnum (seekd->event), COMP_UPDATE_STACK_ON_SEEK);
+}
+
+/* Must be called with OBJECTS_LOCK taken */
+static void
+_process_pending_entries (NleComposition * comp)
+{
+ NleObject *object;
+ GHashTableIter iter;
+ gboolean deactivated_stack = FALSE;
+
+ NleCompositionPrivate *priv = comp->priv;
+
+ g_hash_table_iter_init (&iter, priv->pending_io);
+ while (g_hash_table_iter_next (&iter, (gpointer *) & object, NULL)) {
+ if (g_hash_table_contains (priv->objects_hash, object)) {
+
+ if (GST_OBJECT_PARENT (object) == GST_OBJECT_CAST (priv->current_bin) &&
+ deactivated_stack == FALSE) {
+ deactivated_stack = TRUE;
+
+ _deactivate_stack (comp, TRUE);
+ }
+
+ _nle_composition_remove_object (comp, object);
+ } else {
+ _nle_composition_add_object (comp, object);
+ }
+ }
+
+ g_hash_table_remove_all (priv->pending_io);
+}
+
+
+static inline gboolean
+_commit_values (NleComposition * comp)
+{
+ GList *tmp;
+ gboolean commited = FALSE;
+ NleCompositionPrivate *priv = comp->priv;
+
+ for (tmp = priv->objects_start; tmp; tmp = tmp->next) {
+ if (nle_object_commit (tmp->data, TRUE))
+ commited = TRUE;
+ }
+
+ GST_DEBUG_OBJECT (comp, "Linking up commit vmethod");
+ commited |= NLE_OBJECT_CLASS (parent_class)->commit (NLE_OBJECT (comp), TRUE);
+
+ return commited;
+}
+
+static gboolean
+_commit_all_values (NleComposition * comp)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ priv->next_base_time = 0;
+
+ _process_pending_entries (comp);
+
+ if (_commit_values (comp) == FALSE) {
+
+ 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);
+
+ return TRUE;
+}
+
+static gboolean
+_initialize_stack_func (NleComposition * comp, UpdateCompositionData * ucompo)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+
+ _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason);
+
+ _commit_all_values (comp);
+ update_start_stop_duration (comp);
+ comp->priv->next_base_time = 0;
+ /* set ghostpad target */
+ if (!(update_pipeline (comp, COMP_REAL_START (comp),
+ ucompo->seqnum, COMP_UPDATE_STACK_INITIALIZE))) {
+ GST_FIXME_OBJECT (comp, "PLEASE signal state change failure ASYNC");
+ }
+
+ _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason);
+ priv->initialized = TRUE;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+_remove_object_func (NleComposition * comp, ChildIOData * childio)
+{
+ NleObject *object = childio->object;
+
+ NleCompositionPrivate *priv = comp->priv;
+ NleObject *in_pending_io;
+
+ in_pending_io = g_hash_table_lookup (priv->pending_io, object);
+
+ if (!g_hash_table_contains (priv->objects_hash, object)) {
+ if (in_pending_io) {
+ GST_INFO_OBJECT (comp, "Object %" GST_PTR_FORMAT " was marked"
+ " for addition, removing it from the addition list", object);
+
+ g_hash_table_remove (priv->pending_io, object);
+ return;
+ }
+
+ GST_ERROR_OBJECT (comp, "Object %" GST_PTR_FORMAT " is "
+ " not in the composition", object);
+
+ return;
+ }
+
+ if (in_pending_io) {
+ GST_WARNING_OBJECT (comp, "Object %" GST_PTR_FORMAT " is already marked"
+ " for removal", object);
+
+ return;
+ }
+
+ g_hash_table_add (priv->pending_io, object);
+
+ return;
+}
+
+static void
+_add_remove_object_action (NleComposition * comp, NleObject * object)
+{
+ ChildIOData *childio = g_slice_new0 (ChildIOData);
+
+ GST_DEBUG_OBJECT (comp, "Adding Action");
+
+ childio->comp = comp;
+ childio->object = object;
+
+ _add_action (comp, G_CALLBACK (_remove_object_func),
+ childio, G_PRIORITY_DEFAULT);
+}
+
+static void
+_add_object_func (NleComposition * comp, ChildIOData * childio)
+{
+ NleObject *object = childio->object;
+ NleCompositionPrivate *priv = comp->priv;
+ NleObject *in_pending_io;
+
+ in_pending_io = g_hash_table_lookup (priv->pending_io, object);
+
+ if (g_hash_table_contains (priv->objects_hash, object)) {
+
+ if (in_pending_io) {
+ GST_INFO_OBJECT (comp, "Object already in but marked in pendings"
+ " removing from pendings");
+ g_hash_table_remove (priv->pending_io, object);
+
+ return;
+ }
+ GST_ERROR_OBJECT (comp, "Object %" GST_PTR_FORMAT " is "
+ " already in the composition", object);
+
+ return;
+ }
+
+ if (in_pending_io) {
+ GST_WARNING_OBJECT (comp, "Object %" GST_PTR_FORMAT " is already marked"
+ " for addition", object);
+
+ return;
+ }
+
+
+ g_hash_table_add (priv->pending_io, object);
+}
+
+static void
+_add_add_object_action (NleComposition * comp, NleObject * object)
+{
+ ChildIOData *childio = g_slice_new0 (ChildIOData);
+
+ GST_DEBUG_OBJECT (comp, "Adding Action");
+
+ childio->comp = comp;
+ childio->object = object;
+
+ _add_action (comp, G_CALLBACK (_add_object_func), childio,
+ G_PRIORITY_DEFAULT);
+}
+
+static void
+_free_action (Action * action, gpointer udata)
+{
+ if (ACTION_CALLBACK (action) == _seek_pipeline_func) {
+ SeekData *seekd = (SeekData *) udata;
+
+ gst_event_unref (seekd->event);
+ g_slice_free (SeekData, seekd);
+ } else if (ACTION_CALLBACK (action) == _remove_object_func ||
+ ACTION_CALLBACK (action) == _add_object_func) {
+ g_slice_free (ChildIOData, udata);
+ } else if (ACTION_CALLBACK (action) == _update_pipeline_func ||
+ ACTION_CALLBACK (action) == _commit_func ||
+ ACTION_CALLBACK (action) == _initialize_stack_func) {
+ g_slice_free (UpdateCompositionData, udata);
+ }
+}
+
+static void
+_add_action (NleComposition * comp, GCallback func,
+ gpointer data, gint priority)
+{
+ Action *action;
+ NleCompositionPrivate *priv = comp->priv;
+
+
+ action = (Action *) g_closure_new_simple (sizeof (Action), data);
+ g_closure_add_finalize_notifier ((GClosure *) action, data,
+ (GClosureNotify) _free_action);
+ ACTION_CALLBACK (action) = func;
+ action->priority = priority;
+ g_closure_set_marshal ((GClosure *) action, g_cclosure_marshal_VOID__VOID);
+
+ ACTIONS_LOCK (comp);
+ GST_INFO_OBJECT (comp, "Adding Action for function: %p:%s",
+ action, GST_DEBUG_FUNCPTR_NAME (func));
+
+ if (func == G_CALLBACK (_emit_commited_signal_func))
+ priv->actions = g_list_prepend (priv->actions, action);
+ else
+ priv->actions = g_list_append (priv->actions, action);
+
+ SIGNAL_NEW_ACTION (comp);
+ ACTIONS_UNLOCK (comp);
+}
+
+static void
+_add_seek_action (NleComposition * comp, GstEvent * event)
+{
+ SeekData *seekd = g_slice_new0 (SeekData);
+
+ GST_DEBUG_OBJECT (comp, "Adding Action");
+
+ seekd->comp = comp;
+ seekd->event = event;
+
+ comp->priv->next_eos_seqnum = 0;
+ comp->priv->real_eos_seqnum = 0;
+ _add_action (comp, G_CALLBACK (_seek_pipeline_func), seekd,
+ G_PRIORITY_DEFAULT);
+}
+
+static void
+_remove_update_actions (NleComposition * comp)
+{
+ _remove_actions_for_type (comp, G_CALLBACK (_update_pipeline_func));
+}
+
+static void
+_remove_seek_actions (NleComposition * comp)
+{
+ _remove_actions_for_type (comp, G_CALLBACK (_seek_pipeline_func));
+}
+
+static void
+_add_update_compo_action (NleComposition * comp,
+ GCallback callback, NleUpdateStackReason reason)
+{
+ UpdateCompositionData *ucompo = g_slice_new0 (UpdateCompositionData);
+
+ ucompo->comp = comp;
+ ucompo->reason = reason;
+ ucompo->seqnum = gst_util_seqnum_next ();
+
+ GST_INFO_OBJECT (comp, "Updating because: %s -- Setting seqnum: %i",
+ UPDATE_PIPELINE_REASONS[reason], ucompo->seqnum);
+
+ _add_action (comp, callback, ucompo, G_PRIORITY_DEFAULT);
+}
+
+static void
+nle_composition_handle_message (GstBin * bin, GstMessage * message)
+{
+ NleComposition *comp = (NleComposition *) bin;
+
+ if (comp->priv->tearing_down_stack) {
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+ GST_FIXME_OBJECT (comp, "Dropping %" GST_PTR_FORMAT " message from "
+ " %" GST_PTR_FORMAT " being teared down to READY",
+ message, GST_MESSAGE_SRC (message));
+ }
+
+ GST_DEBUG_OBJECT (comp, "Dropping message %" GST_PTR_FORMAT " from "
+ "object being teared down to READY!", message);
+ gst_message_unref (message);
+
+ return;
+ }
+
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static void
+nle_composition_class_init (NleCompositionClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBinClass *gstbin_class;
+ NleObjectClass *nleobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbin_class = (GstBinClass *) klass;
+ nleobject_class = (NleObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (NleCompositionPrivate));
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "GNonLin Composition", "Filter/Editor", "Combines NLE objects",
+ "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>,"
+ " Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>,"
+ " Thibault Saunier <tsaunier@gnome.org>");
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_composition_dispose);
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (nle_composition_finalize);
+
+ gstelement_class->change_state = nle_composition_change_state;
+
+ gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_composition_add_object);
+ gstbin_class->remove_element =
+ GST_DEBUG_FUNCPTR (nle_composition_remove_object);
+ gstbin_class->handle_message =
+ GST_DEBUG_FUNCPTR (nle_composition_handle_message);
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&nle_composition_src_template));
+
+ /* Get the paramspec of the NleObject klass so we can do
+ * fast notifies */
+ nleobject_properties[NLEOBJECT_PROP_START] =
+ g_object_class_find_property (gobject_class, "start");
+ nleobject_properties[NLEOBJECT_PROP_STOP] =
+ g_object_class_find_property (gobject_class, "stop");
+ nleobject_properties[NLEOBJECT_PROP_DURATION] =
+ g_object_class_find_property (gobject_class, "duration");
+
+ _signals[COMMITED_SIGNAL] =
+ g_signal_new ("commited", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+
+ /**
+ * NleComposition::query-position
+ * @comp: a #NleComposition
+ *
+ * A signal that *has* to be connected and which should return the current
+ * position of the pipeline.
+ *
+ * This signal is used in order to know the current position of the whole
+ * pipeline so it is user's responsability to give that answer as there
+ * is no other way to precisely know the position in the whole pipeline.
+ */
+ _signals[QUERY_POSITION_SIGNAL] =
+ g_signal_new ("query-position", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
+ 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_UINT64, 0, NULL);
+
+ GST_DEBUG_REGISTER_FUNCPTR (_seek_pipeline_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_remove_object_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_add_object_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_update_pipeline_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_commit_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_emit_commited_signal_func);
+ GST_DEBUG_REGISTER_FUNCPTR (_initialize_stack_func);
+
+ /* Just be useless, so the compiler does not warn us
+ * about our uselessness */
+ nleobject_class->commit = nle_composition_commit_func;
+
+}
+
+static void
+nle_composition_init (NleComposition * comp)
+{
+ NleCompositionPrivate *priv;
+
+ GST_OBJECT_FLAG_SET (comp, NLE_OBJECT_SOURCE);
+ GST_OBJECT_FLAG_SET (comp, NLE_OBJECT_COMPOSITION);
+
+ priv = G_TYPE_INSTANCE_GET_PRIVATE (comp, NLE_TYPE_COMPOSITION,
+ NleCompositionPrivate);
+ priv->objects_start = NULL;
+ priv->objects_stop = NULL;
+
+ priv->segment = gst_segment_new ();
+ priv->outside_segment = gst_segment_new ();
+
+ g_rec_mutex_init (&comp->task_rec_lock);
+
+ priv->objects_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ g_mutex_init (&priv->actions_lock);
+ g_cond_init (&priv->actions_cond);
+
+ priv->pending_io = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ comp->priv = priv;
+
+ priv->current_bin = gst_bin_new ("current-bin");
+ gst_bin_add (GST_BIN (comp), priv->current_bin);
+
+ nle_composition_reset (comp);
+
+ priv->nle_event_pad_func = GST_PAD_EVENTFUNC (NLE_OBJECT_SRC (comp));
+ gst_pad_set_event_function (NLE_OBJECT_SRC (comp),
+ GST_DEBUG_FUNCPTR (nle_composition_event_handler));
+}
+
+static void
+nle_composition_dispose (GObject * object)
+{
+ GList *iter;
+ NleComposition *comp = NLE_COMPOSITION (object);
+ NleCompositionPrivate *priv = comp->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ iter = priv->objects_start;
+ while (iter) {
+ GList *next = iter->next;
+
+ _nle_composition_remove_object (comp, iter->data);
+ iter = next;
+ }
+
+ g_list_free (priv->objects_stop);
+
+ if (priv->expandables) {
+ GList *iter;
+
+ iter = priv->expandables;
+
+ while (iter) {
+ GList *next = iter->next;
+
+ _nle_composition_remove_object (comp, iter->data);
+ iter = next;
+ }
+
+ priv->expandables = NULL;
+ }
+
+ nle_composition_reset_target_pad (comp);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+nle_composition_finalize (GObject * object)
+{
+ NleComposition *comp = NLE_COMPOSITION (object);
+ NleCompositionPrivate *priv = comp->priv;
+
+ _assert_proper_thread (comp);
+
+ if (priv->current) {
+ g_node_destroy (priv->current);
+ priv->current = NULL;
+ }
+
+ g_hash_table_destroy (priv->objects_hash);
+
+ gst_segment_free (priv->segment);
+ gst_segment_free (priv->outside_segment);
+
+ g_rec_mutex_clear (&comp->task_rec_lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+
+ g_mutex_clear (&priv->actions_lock);
+ g_cond_clear (&priv->actions_cond);
+}
+
+/* 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 (NleComposition * comp)
+{
+ gst_element_post_message (GST_ELEMENT_CAST (comp),
+ gst_message_new_duration_changed (GST_OBJECT_CAST (comp)));
+}
+
+static gboolean
+_remove_child (GValue * item, GValue * ret G_GNUC_UNUSED, GstBin * bin)
+{
+ GstElement *child = g_value_get_object (item);
+
+ if (NLE_IS_OPERATION (child))
+ nle_operation_hard_cleanup (NLE_OPERATION (child));
+
+
+ gst_bin_remove (bin, child);
+
+ return TRUE;
+}
+
+static void
+_empty_bin (GstBin * bin)
+{
+ GstIterator *children;
+
+ children = gst_bin_iterate_elements (bin);
+
+ while (G_UNLIKELY (gst_iterator_fold (children,
+ (GstIteratorFoldFunction) _remove_child, NULL,
+ bin) == GST_ITERATOR_RESYNC)) {
+ gst_iterator_resync (children);
+ }
+
+ gst_iterator_free (children);
+}
+
+static void
+nle_composition_reset (NleComposition * comp)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ GST_DEBUG_OBJECT (comp, "resetting");
+
+ _assert_proper_thread (comp);
+
+ 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;
+
+ nle_composition_reset_target_pad (comp);
+
+ priv->initialized = FALSE;
+ priv->send_stream_start = TRUE;
+ priv->real_eos_seqnum = 0;
+ priv->next_eos_seqnum = 0;
+ priv->flush_seqnum = 0;
+
+ _empty_bin (GST_BIN_CAST (priv->current_bin));
+
+ GST_DEBUG_OBJECT (comp, "Composition now resetted");
+}
+
+static GstPadProbeReturn
+ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
+ GstPadProbeInfo * info, NleComposition * comp)
+{
+ GstPadProbeReturn retval = GST_PAD_PROBE_OK;
+ NleCompositionPrivate *priv = comp->priv;
+ GstEvent *event;
+
+ if (GST_IS_BUFFER (info->data)) {
+ if (priv->waiting_for_buffer) {
+ GST_INFO_OBJECT (comp, "update_pipeline DONE");
+ _restart_task (comp);
+ }
+
+ return GST_PAD_PROBE_OK;
+ }
+
+ event = GST_PAD_PROBE_INFO_EVENT (info);
+
+ GST_DEBUG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ if (_is_ready_to_restart_task (comp, event))
+ _restart_task (comp);
+
+ if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) {
+ GST_INFO_OBJECT (comp, "Dropping flush stop %d -- %d",
+ gst_event_get_seqnum (event), priv->seqnum_to_restart_task);
+ retval = GST_PAD_PROBE_DROP;
+ } else {
+ GST_INFO_OBJECT (comp, "Forwarding our flush stop with seqnum %i",
+ comp->priv->flush_seqnum);
+ gst_event_unref (event);
+ event = gst_event_new_flush_stop (TRUE);
+ GST_PAD_PROBE_INFO_DATA (info) = event;
+ gst_event_set_seqnum (event, comp->priv->flush_seqnum);
+ comp->priv->flush_seqnum = 0;
+ }
+ break;
+ case GST_EVENT_FLUSH_START:
+ if (gst_event_get_seqnum (event) != comp->priv->flush_seqnum) {
+ GST_INFO_OBJECT (comp, "Dropping flush start");
+ retval = GST_PAD_PROBE_DROP;
+ } else {
+ GST_INFO_OBJECT (comp, "Forwarding our flush start with seqnum %i",
+ comp->priv->flush_seqnum);
+ }
+ 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 */
+
+ if (_is_ready_to_restart_task (comp, event))
+ _restart_task (comp);
+
+ gst_event_parse_segment (event, &segment);
+ gst_segment_copy_into (segment, &copy);
+
+ 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 (&copy);
+ GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event);
+
+ GST_PAD_PROBE_INFO_DATA (info) = event2;
+ gst_event_unref (event);
+ }
+ break;
+ case GST_EVENT_EOS:
+ {
+ gint seqnum = gst_event_get_seqnum (event);
+
+ GST_INFO_OBJECT (comp, "Got EOS, last EOS seqnum id : %i current "
+ "seq num is: %i", comp->priv->real_eos_seqnum, seqnum);
+
+ if (_is_ready_to_restart_task (comp, event)) {
+ GST_INFO_OBJECT (comp, "We got an EOS right after seeing the right"
+ " segment, restarting task");
+
+ _restart_task (comp);
+ }
+
+ if (g_atomic_int_compare_and_exchange (&comp->priv->real_eos_seqnum,
+ seqnum, 1)) {
+
+ GST_INFO_OBJECT (comp, "Got EOS for real, seq ID is %i, fowarding it",
+ seqnum);
+
+ return GST_PAD_PROBE_OK;
+ }
+
+ if (priv->next_eos_seqnum == seqnum)
+ _add_update_compo_action (comp, G_CALLBACK (_update_pipeline_func),
+ COMP_UPDATE_STACK_ON_EOS);
+ else
+ GST_INFO_OBJECT (comp,
+ "Got an EOS but it seqnum %i != next eos seqnum %i", seqnum,
+ priv->next_eos_seqnum);
+
+ retval = GST_PAD_PROBE_DROP;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static gint
+priority_comp (NleObject * a, NleObject * b)
+{
+ if (a->priority < b->priority)
+ return -1;
+
+ if (a->priority > b->priority)
+ return 1;
+
+ return 0;
+}
+
+static inline gboolean
+have_to_update_pipeline (NleComposition * comp,
+ NleUpdateStackReason update_stack_reason)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ if (update_stack_reason == COMP_UPDATE_STACK_ON_EOS)
+ return TRUE;
+
+ 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;
+}
+
+static gboolean
+nle_composition_commit_func (NleObject * object, gboolean recurse)
+{
+ _add_update_compo_action (NLE_COMPOSITION (object),
+ G_CALLBACK (_commit_func), COMP_UPDATE_STACK_ON_COMMIT);
+
+ 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 (NleComposition * comp, gboolean initial,
+ gboolean updatestoponly)
+{
+ GstSeekFlags flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH;
+ gint64 start, stop;
+ GstSeekType starttype = GST_SEEK_TYPE_SET;
+ NleCompositionPrivate *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 = GST_CLOCK_TIME_IS_VALID (priv->segment->start)
+ ? MAX (priv->segment->start, 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 (NleComposition * comp)
+{
+ GstPad *pad;
+ NleObject *obj;
+ NleCompositionPrivate *priv = comp->priv;
+ gboolean res;
+ gint64 value = GST_CLOCK_TIME_NONE;
+
+ GstPad *peer;
+
+ g_signal_emit (comp, _signals[QUERY_POSITION_SIGNAL], 0, &value);
+
+ if (value >= 0) {
+ GST_DEBUG_OBJECT (comp, "Got position %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (value));
+
+ return value;
+ }
+
+ /* Try querying position downstream */
+ peer = gst_pad_get_peer (NLE_OBJECT (comp)->srcpad);
+ if (peer) {
+ res = gst_pad_query_position (peer, GST_FORMAT_TIME, &value);
+ gst_object_unref (peer);
+
+ if (res) {
+ GST_DEBUG_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;
+
+ /* 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 = (NleObject *) priv->current->data;
+
+ pad = NLE_OBJECT_SRC (obj);
+ res = gst_pad_query_position (pad, GST_FORMAT_TIME, &value);
+
+ if (G_UNLIKELY (res == FALSE)) {
+ GST_WARNING_OBJECT (comp, "query position failed");
+ value = GST_CLOCK_TIME_NONE;
+ } else {
+ GST_LOG_OBJECT (comp, "Query returned %" GST_TIME_FORMAT,
+ GST_TIME_ARGS ((guint64) value));
+ }
+
+beach:
+
+ if (!GST_CLOCK_TIME_IS_VALID (value)) {
+ if (GST_CLOCK_TIME_IS_VALID (comp->priv->segment_start)) {
+ value = comp->priv->segment_start;
+ } else {
+ GST_INFO_OBJECT (comp, "Current position is unknown, " "setting it to 0");
+
+ value = 0;
+ }
+ }
+
+ return (guint64) value;
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static gboolean
+update_base_time (GNode * node, GstClockTime * timestamp)
+{
+ if (NLE_IS_OPERATION (node->data))
+ nle_operation_update_base_time (NLE_OPERATION (node->data), *timestamp);
+
+ return FALSE;
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static void
+update_operations_base_time (NleComposition * 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, &timestamp);
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static gboolean
+_seek_current_stack (NleComposition * comp, GstEvent * event,
+ gboolean flush_downstream)
+{
+ gboolean res;
+ NleCompositionPrivate *priv = comp->priv;
+ GstPad *peer = gst_pad_get_peer (NLE_OBJECT_SRC (comp));
+
+ GST_INFO_OBJECT (comp, "Seeking itself %" GST_PTR_FORMAT, event);
+
+ if (flush_downstream) {
+ priv->flush_seqnum = gst_event_get_seqnum (event);
+ GST_INFO_OBJECT (comp, "sending flushes downstream with seqnum %d",
+ priv->flush_seqnum);
+ }
+
+ priv->seeking_itself = TRUE;
+ res = gst_pad_push_event (peer, event);
+ priv->seeking_itself = FALSE;
+ gst_object_unref (peer);
+
+ GST_DEBUG_OBJECT (comp, "Done seeking");
+
+ return res;
+}
+
+/*
+ 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
+
+ update_stack_reason: The reason for which we need to handle 'seek'
+*/
+
+static gboolean
+seek_handling (NleComposition * comp, gint32 seqnum,
+ NleUpdateStackReason update_stack_reason)
+{
+ GST_DEBUG_OBJECT (comp, "Seek hanlding update pipeline reason: %s",
+ UPDATE_PIPELINE_REASONS[update_stack_reason]);
+
+ if (have_to_update_pipeline (comp, update_stack_reason)) {
+ if (comp->priv->segment->rate >= 0.0)
+ update_pipeline (comp, comp->priv->segment->start, seqnum,
+ update_stack_reason);
+ else
+ update_pipeline (comp, comp->priv->segment->stop, seqnum,
+ update_stack_reason);
+ } else {
+ GstEvent *toplevel_seek = get_new_seek_event (comp, FALSE, FALSE);
+
+ gst_event_set_seqnum (toplevel_seek, seqnum);
+ _set_real_eos_seqnum_from_seek (comp, toplevel_seek);
+
+ _remove_update_actions (comp);
+ update_operations_base_time (comp, !(comp->priv->segment->rate >= 0.0));
+ _seek_current_stack (comp, toplevel_seek,
+ _have_to_flush_downstream (update_stack_reason));
+ }
+
+ return TRUE;
+}
+
+static gboolean
+nle_composition_event_handler (GstPad * ghostpad, GstObject * parent,
+ GstEvent * event)
+{
+ NleComposition *comp = (NleComposition *) parent;
+ NleCompositionPrivate *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:
+ {
+ if (!priv->seeking_itself) {
+ _add_seek_action (comp, event);
+ event = NULL;
+ GST_FIXME_OBJECT (comp, "HANDLE seeking errors!");
+
+ return TRUE;
+ }
+ break;
+ }
+ case GST_EVENT_QOS:
+ {
+ gdouble prop;
+ GstQOSType qostype;
+ GstClockTimeDiff diff;
+ GstClockTime timestamp;
+
+ gst_event_parse_qos (event, &qostype, &prop, &diff, &timestamp);
+
+ GST_DEBUG_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_DEBUG_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) {
+ GST_DEBUG_OBJECT (comp, "About to call nle_event_pad_func: %p",
+ priv->nle_event_pad_func);
+ res = priv->nle_event_pad_func (NLE_OBJECT (comp)->srcpad, parent, event);
+ GST_DEBUG_OBJECT (comp, "Done calling nle_event_pad_func() %d", res);
+ }
+
+beach:
+ return res;
+}
+
+static inline void
+nle_composition_reset_target_pad (NleComposition * comp)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ GST_DEBUG_OBJECT (comp, "Removing ghostpad");
+
+ if (priv->ghosteventprobe) {
+ GstPad *target;
+
+ target = gst_ghost_pad_get_target ((GstGhostPad *) NLE_OBJECT_SRC (comp));
+ if (target)
+ gst_pad_remove_probe (target, priv->ghosteventprobe);
+ priv->ghosteventprobe = 0;
+ }
+
+ nle_object_ghost_pad_set_target (NLE_OBJECT (comp),
+ NLE_OBJECT_SRC (comp), NULL);
+ priv->send_stream_start = TRUE;
+}
+
+/* nle_composition_ghost_pad_set_target:
+ * target: The target #GstPad. The refcount will be decremented (given to the ghostpad).
+ */
+static void
+nle_composition_ghost_pad_set_target (NleComposition * comp, GstPad * target)
+{
+ GstPad *ptarget;
+ NleCompositionPrivate *priv = comp->priv;
+
+ if (target)
+ GST_DEBUG_OBJECT (comp, "target:%s:%s", GST_DEBUG_PAD_NAME (target));
+ else
+ GST_DEBUG_OBJECT (comp, "Removing target");
+
+
+ ptarget =
+ gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT (comp)->srcpad));
+ if (ptarget) {
+ gst_object_unref (ptarget);
+
+ if (ptarget == target) {
+ GST_DEBUG_OBJECT (comp,
+ "Target of srcpad is the same as existing one, not changing");
+ return;
+ }
+ }
+
+ /* Actually set the target */
+ nle_object_ghost_pad_set_target ((NleObject *) comp,
+ NLE_OBJECT (comp)->srcpad, target);
+
+ if (target && (priv->ghosteventprobe == 0)) {
+ priv->ghosteventprobe =
+ gst_pad_add_probe (target,
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH |
+ GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+ (GstPadProbeCallback) ghost_event_probe_handler, comp, NULL);
+ GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe);
+ }
+}
+
+static void
+refine_start_stop_in_region_above_priority (NleComposition * composition,
+ GstClockTime timestamp, GstClockTime start,
+ GstClockTime stop,
+ GstClockTime * rstart, GstClockTime * rstop, guint32 priority)
+{
+ GList *tmp;
+ NleObject *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 = (NleObject *) 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) || (!NLE_OBJECT_ACTIVE (object)))
+ 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 = (NleObject *) 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) || (!NLE_OBJECT_ACTIVE (object)))
+ 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;
+ NleObject *object;
+
+ if (!stack || !*stack)
+ return NULL;
+
+ object = (NleObject *) (*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 (NLE_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 {
+ /* NleOperation */
+ NleOperation *oper = (NleOperation *) 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 #NleComposition
+ * @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 (NleComposition * 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)) {
+ NleObject *object = (NleObject *) 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) || (NLE_OBJECT_ACTIVE (object)))) {
+ 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 (NLE_IS_OPERATION (object))
+ nle_operation_update_base_time (NLE_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)) {
+ NleObject *object = (NleObject *) 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) || (NLE_OBJECT_ACTIVE (object)))) {
+ 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 (NLE_IS_OPERATION (object))
+ nle_operation_update_base_time (NLE_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 < NLE_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 (NLE_IS_OPERATION (tmp->data))
+ nle_operation_update_base_time (NLE_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 #NleComposition
+ * @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 #NleComposition and @timestamp.
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+static GNode *
+get_clean_toplevel_stack (NleComposition * 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 = NLE_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;
+}
+
+static GstPadProbeReturn
+_drop_all_cb (GstPad * pad G_GNUC_UNUSED,
+ GstPadProbeInfo * info, NleComposition * comp)
+{
+ return GST_PAD_PROBE_DROP;
+}
+
+/* Must be called with OBJECTS_LOCK taken */
+static void
+_set_current_bin_to_ready (NleComposition * comp, gboolean flush_downstream)
+{
+ gint probe_id = -1;
+ GstPad *ptarget = NULL;
+ NleCompositionPrivate *priv = comp->priv;
+ GstEvent *flush_event;
+
+ comp->priv->tearing_down_stack = TRUE;
+ if (flush_downstream) {
+ ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp)));
+ if (ptarget) {
+
+ /* Make sure that between the flush_start/flush_stop
+ * and the time we set the current_bin to READY, no
+ * buffer can ever get prerolled which would lead to
+ * a deadlock */
+ probe_id = gst_pad_add_probe (ptarget,
+ GST_PAD_PROBE_TYPE_DATA_BOTH | GST_PAD_PROBE_TYPE_EVENT_BOTH,
+ (GstPadProbeCallback) _drop_all_cb, comp, NULL);
+
+ GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe);
+
+ flush_event = gst_event_new_flush_start ();
+ priv->flush_seqnum = gst_event_get_seqnum (flush_event);
+ GST_INFO_OBJECT (comp, "sending flushes downstream with seqnum %d",
+ priv->flush_seqnum);
+ gst_pad_push_event (ptarget, flush_event);
+
+ }
+
+ }
+
+ gst_element_set_locked_state (priv->current_bin, TRUE);
+ gst_element_set_state (priv->current_bin, GST_STATE_READY);
+
+ if (ptarget) {
+ if (flush_downstream) {
+ flush_event = gst_event_new_flush_stop (TRUE);
+ gst_event_set_seqnum (flush_event, priv->flush_seqnum);
+
+ /* Force ad activation so that the event can actually travel.
+ * Not doing that would lead to the event being discarded.
+ */
+ gst_pad_set_active (ptarget, TRUE);
+ gst_pad_push_event (ptarget, flush_event);
+ gst_pad_set_active (ptarget, FALSE);
+ }
+
+ gst_pad_remove_probe (ptarget, probe_id);
+ gst_object_unref (ptarget);
+ }
+
+ comp->priv->tearing_down_stack = FALSE;
+}
+
+static void
+_emit_commited_signal_func (NleComposition * comp, gpointer udata)
+{
+ GST_INFO_OBJECT (comp, "Emiting COMMITED now that the stack " "is ready");
+
+ g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE);
+}
+
+static void
+_restart_task (NleComposition * comp)
+{
+ GST_INFO_OBJECT (comp, "Restarting task! after %s DONE",
+ UPDATE_PIPELINE_REASONS[comp->priv->updating_reason]);
+
+ if (comp->priv->updating_reason == COMP_UPDATE_STACK_ON_COMMIT)
+ _add_action (comp, G_CALLBACK (_emit_commited_signal_func), comp,
+ G_PRIORITY_HIGH);
+
+ comp->priv->seqnum_to_restart_task = 0;
+ comp->priv->waiting_for_buffer = FALSE;
+
+ comp->priv->updating_reason = COMP_UPDATE_STACK_NONE;
+ GST_OBJECT_LOCK (comp);
+ if (comp->task)
+ gst_task_start (comp->task);
+ GST_OBJECT_UNLOCK (comp);
+}
+
+static gboolean
+_is_ready_to_restart_task (NleComposition * comp, GstEvent * event)
+{
+ NleCompositionPrivate *priv = comp->priv;
+ gint seqnum = gst_event_get_seqnum (event);
+
+
+ if (comp->priv->seqnum_to_restart_task == seqnum) {
+ gchar *name = g_strdup_printf ("%s-new-stack__%" GST_TIME_FORMAT "--%"
+ GST_TIME_FORMAT "", GST_OBJECT_NAME (comp),
+ GST_TIME_ARGS (comp->priv->segment_start),
+ GST_TIME_ARGS (comp->priv->segment_stop));
+
+ GST_INFO_OBJECT (comp, "Got %s with proper seqnum"
+ " done with stack reconfiguration %" GST_PTR_FORMAT,
+ GST_EVENT_TYPE_NAME (event), event);
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (comp),
+ GST_DEBUG_GRAPH_SHOW_ALL, name);
+ g_free (name);
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+ GST_INFO_OBJECT (comp, "update_pipeline DONE");
+ return TRUE;
+ }
+
+ priv->waiting_for_buffer = TRUE;
+ return FALSE;
+
+ } else if (comp->priv->seqnum_to_restart_task) {
+ GST_INFO_OBJECT (comp, "WARNING: %s seqnum %i != wanted %i",
+ GST_EVENT_TYPE_NAME (event), seqnum,
+ comp->priv->seqnum_to_restart_task);
+ }
+
+ return FALSE;
+}
+
+static void
+_commit_func (NleComposition * comp, UpdateCompositionData * ucompo)
+{
+ GstClockTime curpos;
+ NleCompositionPrivate *priv = comp->priv;
+
+ _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason);
+
+ /* Get current so that it represent the duration it was
+ * before commiting children */
+ curpos = get_current_position (comp);
+
+ if (!_commit_all_values (comp)) {
+ GST_DEBUG_OBJECT (comp, "Nothing to commit, leaving");
+
+ g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, FALSE);
+ _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason);
+
+ return;
+ }
+
+ if (priv->initialized == FALSE) {
+ GST_DEBUG_OBJECT (comp, "Not initialized yet, just updating values");
+
+ update_start_stop_duration (comp);
+
+ g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE);
+
+ } else {
+ /* And update the pipeline at current position if needed */
+
+ update_start_stop_duration (comp);
+ update_pipeline (comp, curpos, ucompo->seqnum, COMP_UPDATE_STACK_ON_COMMIT);
+
+ if (!priv->current) {
+ GST_INFO_OBJECT (comp, "No new stack set, we can go and keep acting on"
+ " our children");
+
+ g_signal_emit (comp, _signals[COMMITED_SIGNAL], 0, TRUE);
+ }
+ }
+
+ _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason);
+}
+
+static void
+_update_pipeline_func (NleComposition * comp, UpdateCompositionData * ucompo)
+{
+ gboolean reverse;
+ NleCompositionPrivate *priv = comp->priv;
+
+ _post_start_composition_update (comp, ucompo->seqnum, ucompo->reason);
+
+ /* Set up a non-initial seek on segment_stop */
+ 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, ucompo->seqnum, COMP_UPDATE_STACK_ON_EOS);
+
+ /* Post segment done if last seek was a segment seek */
+ if (!priv->current && (priv->segment->flags & GST_SEEK_FLAG_SEGMENT)) {
+ gint64 epos;
+
+ if (GST_CLOCK_TIME_IS_VALID (priv->segment->stop))
+ epos = (MIN (priv->segment->stop, NLE_OBJECT_STOP (comp)));
+ else
+ epos = NLE_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 (NLE_OBJECT (comp)->srcpad,
+ gst_event_new_segment_done (priv->segment->format, epos));
+ }
+
+ _post_start_composition_update_done (comp, ucompo->seqnum, ucompo->reason);
+}
+
+static void
+_set_all_children_state (NleComposition * comp, GstState state)
+{
+ GList *tmp;
+
+ GST_DEBUG_OBJECT (comp, "Setting all children state to %s",
+ gst_element_state_get_name (state));
+
+ comp->priv->tearing_down_stack = TRUE;
+ gst_element_set_state (comp->priv->current_bin, state);
+ for (tmp = comp->priv->objects_start; tmp; tmp = tmp->next)
+ gst_element_set_state (tmp->data, state);
+
+ for (tmp = comp->priv->expandables; tmp; tmp = tmp->next)
+ gst_element_set_state (tmp->data, state);
+ comp->priv->tearing_down_stack = FALSE;
+}
+
+static GstStateChangeReturn
+nle_composition_change_state (GstElement * element, GstStateChange transition)
+{
+ NleComposition *comp = (NleComposition *) element;
+
+ 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:
+ _start_task (comp);
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* state-lock all elements */
+ GST_DEBUG_OBJECT (comp,
+ "Setting all children to READY and locking their state");
+
+ _add_update_compo_action (comp, G_CALLBACK (_initialize_stack_func),
+ COMP_UPDATE_STACK_INITIALIZE);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ _stop_task (comp);
+
+ _remove_update_actions (comp);
+ _remove_seek_actions (comp);
+ _set_all_children_state (comp, GST_STATE_READY);
+ nle_composition_reset (comp);
+
+ _start_task (comp);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ _stop_task (comp);
+
+ _remove_update_actions (comp);
+ _remove_seek_actions (comp);
+ _set_all_children_state (comp, GST_STATE_NULL);
+ break;
+ default:
+ break;
+ }
+
+ return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+}
+
+static gint
+objects_start_compare (NleObject * a, NleObject * 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 (NleObject * a, NleObject * 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 (NleComposition * comp)
+{
+ NleObject *obj;
+ NleObject *cobj = (NleObject *) comp;
+ NleCompositionPrivate *priv = comp->priv;
+
+ _assert_proper_thread (comp);
+
+ if (!priv->objects_start) {
+ GST_INFO_OBJECT (comp, "no objects, resetting everything to 0");
+
+ if (cobj->start) {
+ cobj->start = cobj->pending_start = 0;
+ g_object_notify_by_pspec (G_OBJECT (cobj),
+ nleobject_properties[NLEOBJECT_PROP_START]);
+ }
+
+ if (cobj->duration) {
+ cobj->pending_duration = cobj->duration = 0;
+ g_object_notify_by_pspec (G_OBJECT (cobj),
+ nleobject_properties[NLEOBJECT_PROP_DURATION]);
+ signal_duration_change (comp);
+ }
+
+ if (cobj->stop) {
+ cobj->stop = 0;
+ g_object_notify_by_pspec (G_OBJECT (cobj),
+ nleobject_properties[NLEOBJECT_PROP_STOP]);
+ }
+
+ return;
+ }
+
+ /* If we have a default object, the start position is 0 */
+ if (priv->expandables) {
+ GST_INFO_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),
+ nleobject_properties[NLEOBJECT_PROP_START]);
+ }
+
+ } else {
+
+ /* Else it's the first object's start value */
+ obj = (NleObject *) priv->objects_start->data;
+
+ if (obj->start != cobj->start) {
+ GST_INFO_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),
+ nleobject_properties[NLEOBJECT_PROP_START]);
+ }
+
+ }
+
+ obj = (NleObject *) priv->objects_stop->data;
+
+ if (obj->stop != cobj->stop) {
+ GST_INFO_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);
+ nle_object_commit (NLE_OBJECT (tmp->data), FALSE);
+ }
+ }
+
+ priv->segment->stop = obj->stop;
+ cobj->stop = obj->stop;
+ g_object_notify_by_pspec (G_OBJECT (cobj),
+ nleobject_properties[NLEOBJECT_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),
+ nleobject_properties[NLEOBJECT_PROP_DURATION]);
+ signal_duration_change (comp);
+ }
+
+ GST_INFO_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
+_link_to_parent (NleComposition * comp, NleObject * newobj,
+ NleObject * newparent)
+{
+ GstPad *sinkpad;
+
+ /* relink to new parent in required order */
+ 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 ((NleOperation *) 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 (NLE_OBJECT_SRC (newobj), 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 (NLE_OBJECT_SRC (newobj)),
+ GST_DEBUG_PAD_NAME (sinkpad));
+ }
+ gst_object_unref (sinkpad);
+ }
+}
+
+static void
+_relink_children_recursively (NleComposition * comp,
+ NleObject * newobj, GNode * node, GstEvent * toplevel_seek)
+{
+ GNode *child;
+ guint nbchildren = g_node_n_children (node);
+ NleOperation *oper = (NleOperation *) newobj;
+
+ GST_INFO_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)
+ _relink_single_node (comp, child, toplevel_seek);
+
+ if (G_UNLIKELY (nbchildren < oper->num_sinks))
+ GST_ERROR_OBJECT (comp,
+ "%" GST_PTR_FORMAT
+ " Not enough sinkpads to link all objects to the operation ! "
+ "%d / %d", oper, oper->num_sinks, nbchildren);
+
+ if (G_UNLIKELY (nbchildren == 0)) {
+ GST_ERROR_OBJECT (comp,
+ "Operation %" GST_PTR_FORMAT
+ " has no child objects to be connected to !!!", oper);
+ }
+ /* Make sure we have enough sinkpads */
+}
+
+/*
+ * 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
+_relink_single_node (NleComposition * comp, GNode * node,
+ GstEvent * toplevel_seek)
+{
+ NleObject *newobj;
+ NleObject *newparent;
+ GstPad *srcpad = NULL, *sinkpad = NULL;
+ GstEvent *translated_seek;
+
+ if (G_UNLIKELY (!node))
+ return;
+
+ newparent = G_NODE_IS_ROOT (node) ? NULL : (NleObject *) node->parent->data;
+ newobj = (NleObject *) node->data;
+
+ GST_DEBUG_OBJECT (comp, "newobj:%s",
+ GST_ELEMENT_NAME ((GstElement *) newobj));
+
+ srcpad = NLE_OBJECT_SRC (newobj);
+
+ gst_bin_add (GST_BIN (comp->priv->current_bin), GST_ELEMENT (newobj));
+ gst_element_sync_state_with_parent (GST_ELEMENT_CAST (newobj));
+
+ translated_seek = nle_object_translate_incoming_seek (newobj, toplevel_seek);
+
+ gst_element_send_event (GST_ELEMENT (newobj), translated_seek);
+
+ /* link to parent if needed. */
+ if (newparent) {
+ _link_to_parent (comp, newobj, newparent);
+
+ /* If there's an operation, inform it about priority changes */
+ sinkpad = gst_pad_get_peer (srcpad);
+ nle_operation_signal_input_priority_changed ((NleOperation *)
+ newparent, sinkpad, newobj->priority);
+ gst_object_unref (sinkpad);
+ }
+
+ /* Handle children */
+ if (NLE_IS_OPERATION (newobj))
+ _relink_children_recursively (comp, newobj, node, toplevel_seek);
+
+ GST_LOG_OBJECT (comp, "done with object %s",
+ GST_ELEMENT_NAME (GST_ELEMENT (newobj)));
+}
+
+
+
+/*
+ * compare_relink_stack:
+ * @comp: The #NleComposition
+ * @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 #NleObject no longer used
+ */
+
+static void
+_deactivate_stack (NleComposition * comp, gboolean flush_downstream)
+{
+ GstPad *ptarget;
+
+ GST_INFO_OBJECT (comp, "Deactivating current stack (flushing downstream: %d",
+ flush_downstream);
+ _set_current_bin_to_ready (comp, flush_downstream);
+
+ ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (NLE_OBJECT_SRC (comp)));
+ _empty_bin (GST_BIN_CAST (comp->priv->current_bin));
+
+ if (comp->priv->ghosteventprobe) {
+ GST_INFO_OBJECT (comp, "Removing old ghost pad probe");
+
+ gst_pad_remove_probe (ptarget, comp->priv->ghosteventprobe);
+ comp->priv->ghosteventprobe = 0;
+ }
+
+ if (ptarget)
+ gst_object_unref (ptarget);
+
+ GST_INFO_OBJECT (comp, "Stack desctivated");
+
+/* priv->current = NULL;
+ */
+}
+
+static void
+_relink_new_stack (NleComposition * comp, GNode * stack,
+ GstEvent * toplevel_seek)
+{
+ _relink_single_node (comp, stack, toplevel_seek);
+
+ gst_event_unref (toplevel_seek);
+}
+
+/* static void
+ * unlock_activate_stack (NleComposition * 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;
+}
+
+static inline gboolean
+_activate_new_stack (NleComposition * comp)
+{
+ GstPad *pad;
+ GstElement *topelement;
+
+ NleCompositionPrivate *priv = comp->priv;
+
+ if (!priv->current) {
+ if ((!priv->objects_start)) {
+ nle_composition_reset_target_pad (comp);
+ priv->segment_start = 0;
+ priv->segment_stop = GST_CLOCK_TIME_NONE;
+ }
+
+ GST_DEBUG_OBJECT (comp, "Nothing else in the composition"
+ ", update 'worked'");
+ goto resync_state;
+ }
+
+ /* The stack is entirely ready, send seek out synchronously */
+ topelement = GST_ELEMENT (priv->current->data);
+ /* Get toplevel object source pad */
+ pad = NLE_OBJECT_SRC (topelement);
+
+ GST_INFO_OBJECT (comp,
+ "We have a valid toplevel element pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ nle_composition_ghost_pad_set_target (comp, pad);
+
+ GST_DEBUG_OBJECT (comp, "New stack activated!");
+
+resync_state:
+ gst_element_set_locked_state (priv->current_bin, FALSE);
+ GST_DEBUG ("going back to parent state");
+ gst_element_sync_state_with_parent (priv->current_bin);
+ GST_DEBUG ("gone back to parent state");
+
+ return TRUE;
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static gboolean
+_set_real_eos_seqnum_from_seek (NleComposition * comp, GstEvent * event)
+{
+ GList *tmp;
+
+ gboolean should_check_objects = FALSE;
+ NleCompositionPrivate *priv = comp->priv;
+ gboolean reverse = (priv->segment->rate < 0);
+ gint stack_seqnum = gst_event_get_seqnum (event);
+
+ if (reverse && GST_CLOCK_TIME_IS_VALID (priv->segment_start))
+ should_check_objects = TRUE;
+ else if (!reverse && GST_CLOCK_TIME_IS_VALID (priv->segment_stop))
+ should_check_objects = TRUE;
+
+ if (should_check_objects) {
+ for (tmp = priv->objects_stop; tmp; tmp = g_list_next (tmp)) {
+ NleObject *object = (NleObject *) tmp->data;
+
+ if (!NLE_IS_SOURCE (object))
+ continue;
+
+ if ((!reverse && priv->segment_stop < object->stop) ||
+ (reverse && priv->segment_start > object->start)) {
+ priv->next_eos_seqnum = stack_seqnum;
+ g_atomic_int_set (&priv->real_eos_seqnum, 0);
+ return FALSE;
+ }
+ }
+ }
+
+ priv->next_eos_seqnum = stack_seqnum;
+ g_atomic_int_set (&priv->real_eos_seqnum, stack_seqnum);
+
+ return TRUE;
+}
+
+/*
+ * update_pipeline:
+ * @comp: The #NleComposition
+ * @currenttime: The #GstClockTime to update at, can be GST_CLOCK_TIME_NONE.
+ * @update_reason: Reason why we are updating the pipeline
+ *
+ * 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 (NleComposition * comp, GstClockTime currenttime, gint32 seqnum,
+ NleUpdateStackReason update_reason)
+{
+
+ GstEvent *toplevel_seek;
+
+ GNode *stack = NULL;
+ gboolean samestack = FALSE;
+ gboolean updatestoponly = FALSE;
+ GstState state = GST_STATE (comp);
+ NleCompositionPrivate *priv = comp->priv;
+ GstClockTime new_stop = GST_CLOCK_TIME_NONE;
+ GstClockTime new_start = GST_CLOCK_TIME_NONE;
+ GstClockTime duration = NLE_OBJECT (comp)->duration - 1;
+
+ GstState nextstate = (GST_STATE_NEXT (comp) == GST_STATE_VOID_PENDING) ?
+ GST_STATE (comp) : GST_STATE_NEXT (comp);
+
+ _assert_proper_thread (comp);
+
+ if (currenttime >= duration) {
+ currenttime = duration;
+ priv->segment->start = GST_CLOCK_TIME_NONE;
+ priv->segment->stop = GST_CLOCK_TIME_NONE;
+ }
+
+ GST_INFO_OBJECT (comp,
+ "currenttime:%" GST_TIME_FORMAT
+ " Reason: %s, Seqnum: %i", GST_TIME_ARGS (currenttime),
+ UPDATE_PIPELINE_REASONS[update_reason], seqnum);
+
+ 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));
+
+ /* Get new stack and compare it to current one */
+ stack = get_clean_toplevel_stack (comp, &currenttime, &new_start, &new_stop);
+ samestack = are_same_stacks (priv->current, stack);
+
+ /* 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;
+ }
+
+# if 0
+ /* FIXME -- We should be ablt to use updatestoponly in that case,
+ * but it simply does not work! Not using it leads to same
+ * behaviour, but less optimized */
+
+ gboolean startchanged, stopchanged;
+
+ 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;
+ }
+
+ if (samestack) {
+ if (startchanged || stopchanged) {
+ /* Update seek events need to be flushing if not in PLAYING,
+ * else we will encounter deadlocks. */
+ updatestoponly = (state == GST_STATE_PLAYING) ? FALSE : TRUE;
+ }
+ }
+#endif
+
+ toplevel_seek = get_new_seek_event (comp, TRUE, updatestoponly);
+ gst_event_set_seqnum (toplevel_seek, seqnum);
+ _set_real_eos_seqnum_from_seek (comp, toplevel_seek);
+
+ _remove_update_actions (comp);
+
+ /* If stacks are different, unlink/relink objects */
+ if (!samestack) {
+ _deactivate_stack (comp, _have_to_flush_downstream (update_reason));
+ _relink_new_stack (comp, stack, toplevel_seek);
+ }
+
+ /* Unlock all elements in new stack */
+ GST_INFO_OBJECT (comp, "Setting current stack [%" GST_TIME_FORMAT " - %"
+ GST_TIME_FORMAT "]", GST_TIME_ARGS (priv->segment_start),
+ GST_TIME_ARGS (priv->segment_stop));
+ priv->current = stack;
+
+ if (priv->current) {
+ UpdateCompositionData *ucompo = g_slice_new0 (UpdateCompositionData);
+
+ GST_INFO_OBJECT (comp, "New stack set and ready to run, probing src pad"
+ " and stopping children thread until we are actually ready with"
+ " that new stack");
+
+ ucompo->comp = comp;
+ ucompo->reason = update_reason;
+ ucompo->seqnum = seqnum;
+
+ comp->priv->updating_reason = update_reason;
+ comp->priv->seqnum_to_restart_task = seqnum;
+
+ GST_OBJECT_LOCK (comp);
+ if (comp->task == NULL) {
+ GST_INFO_OBJECT (comp,
+ "No task set, it must have been stopped, returning");
+ GST_OBJECT_UNLOCK (comp);
+ return FALSE;
+ }
+
+ gst_task_pause (comp->task);
+ GST_OBJECT_UNLOCK (comp);
+ }
+
+ /* Activate stack */
+ if (!samestack)
+ return _activate_new_stack (comp);
+ else
+ return _seek_current_stack (comp, toplevel_seek,
+ _have_to_flush_downstream (update_reason));
+}
+
+static gboolean
+nle_composition_add_object (GstBin * bin, GstElement * element)
+{
+ NleObject *object;
+ NleComposition *comp = (NleComposition *) bin;
+
+ if (element == comp->priv->current_bin) {
+ GST_INFO_OBJECT (comp, "Adding internal bin");
+ return GST_BIN_CLASS (parent_class)->add_element (bin, element);
+ }
+
+ g_return_val_if_fail (NLE_IS_OBJECT (element), FALSE);
+
+ object = NLE_OBJECT (element);
+ gst_object_ref_sink (object);
+
+ object->in_composition = TRUE;
+ _add_add_object_action (comp, object);
+
+ return TRUE;
+}
+
+static gboolean
+_nle_composition_add_object (NleComposition * comp, NleObject * object)
+{
+ gboolean ret = TRUE;
+ NleCompositionPrivate *priv = comp->priv;
+
+ GST_DEBUG_OBJECT (comp, "element %s", GST_OBJECT_NAME (object));
+ GST_DEBUG_OBJECT (object, "%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT,
+ GST_TIME_ARGS (NLE_OBJECT_START (object)),
+ GST_TIME_ARGS (NLE_OBJECT_STOP (object)));
+
+ if ((NLE_OBJECT_IS_EXPANDABLE (object)) &&
+ g_list_find (priv->expandables, object)) {
+ GST_WARNING_OBJECT (comp,
+ "We already have an expandable, remove it before adding new one");
+ ret = FALSE;
+
+ goto chiringuito;
+ }
+
+ nle_object_set_caps (object, NLE_OBJECT (comp)->caps);
+ nle_object_set_commit_needed (NLE_OBJECT (comp));
+
+ if (!ret) {
+ GST_WARNING_OBJECT (comp, "couldn't add object");
+ goto chiringuito;
+ }
+
+ /* lock state of child ! */
+ GST_LOG_OBJECT (comp, "Locking state of %s", GST_ELEMENT_NAME (object));
+
+ if (NLE_OBJECT_IS_EXPANDABLE (object)) {
+ /* Only react on non-default objects properties */
+ g_object_set (object,
+ "start", (GstClockTime) 0,
+ "inpoint", (GstClockTime) 0,
+ "duration", (GstClockTimeDiff) NLE_OBJECT_STOP (comp), NULL);
+
+ GST_INFO_OBJECT (object, "Used as expandable, commiting now");
+ nle_object_commit (NLE_OBJECT (object), FALSE);
+ }
+
+ /* ...and add it to the hash table */
+ g_hash_table_add (priv->objects_hash, object);
+
+ /* Set the caps of the composition on the NleObject it handles */
+ if (G_UNLIKELY (!gst_caps_is_any (((NleObject *) comp)->caps)))
+ nle_object_set_caps ((NleObject *) object, ((NleObject *) comp)->caps);
+
+ /* Special case for default source. */
+ if (NLE_OBJECT_IS_EXPANDABLE (object)) {
+ /* It doesn't get added to objects_start and objects_stop. */
+ priv->expandables = g_list_prepend (priv->expandables, object);
+ goto beach;
+ }
+
+ /* add it sorted to the objects list */
+ priv->objects_start = g_list_insert_sorted
+ (priv->objects_start, object, (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 (NLE_OBJECT_START (priv->objects_start->data)),
+ GST_TIME_ARGS (NLE_OBJECT_STOP (priv->objects_start->data)));
+
+ priv->objects_stop = g_list_insert_sorted
+ (priv->objects_stop, object, (GCompareFunc) objects_stop_compare);
+
+ /* Now the object is ready to be commited and then used */
+
+beach:
+ return ret;
+
+chiringuito:
+ {
+ update_start_stop_duration (comp);
+ goto beach;
+ }
+}
+
+static gboolean
+nle_composition_remove_object (GstBin * bin, GstElement * element)
+{
+ NleObject *object;
+ NleComposition *comp = (NleComposition *) bin;
+
+ if (element == comp->priv->current_bin) {
+ GST_INFO_OBJECT (comp, "Adding internal bin");
+ return GST_BIN_CLASS (parent_class)->remove_element (bin, element);
+ }
+
+ g_return_val_if_fail (NLE_IS_OBJECT (element), FALSE);
+
+ object = NLE_OBJECT (element);
+
+ object->in_composition = FALSE;
+ _add_remove_object_action (comp, object);
+
+ return TRUE;
+}
+
+static gboolean
+_nle_composition_remove_object (NleComposition * comp, NleObject * object)
+{
+ NleCompositionPrivate *priv = comp->priv;
+
+ GST_DEBUG_OBJECT (comp, "removing object %s", GST_OBJECT_NAME (object));
+
+ if (!g_hash_table_contains (priv->objects_hash, object)) {
+ GST_INFO_OBJECT (comp, "object was not in composition");
+ return FALSE;
+ }
+
+ gst_element_set_locked_state (GST_ELEMENT (object), FALSE);
+ gst_element_set_state (GST_ELEMENT (object), GST_STATE_NULL);
+
+ /* handle default source */
+ if (NLE_OBJECT_IS_EXPANDABLE (object)) {
+ /* Find it in the list */
+ priv->expandables = g_list_remove (priv->expandables, object);
+ } else {
+ /* remove it from the objects list and resort the lists */
+ priv->objects_start = g_list_remove (priv->objects_start, object);
+ priv->objects_stop = g_list_remove (priv->objects_stop, object);
+ GST_LOG_OBJECT (object, "Removed from the objects start/stop list");
+ }
+
+ if (priv->current && NLE_OBJECT (priv->current->data) == NLE_OBJECT (object))
+ nle_composition_reset_target_pad (comp);
+
+ g_hash_table_remove (priv->objects_hash, object);
+
+ GST_LOG_OBJECT (object, "Done removing from the composition, now updating");
+
+ /* Make it possible to reuse the same object later */
+ nle_object_reset (NLE_OBJECT (object));
+ gst_object_unref (object);
+
+ return TRUE;
+}
diff --git a/plugins/nle/nlecomposition.h b/plugins/nle/nlecomposition.h
new file mode 100644
index 00000000..9f5da3d8
--- /dev/null
+++ b/plugins/nle/nlecomposition.h
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ * 2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nlecomposition.h: Header for base NleComposition
+ *
+ * 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 __NLE_COMPOSITION_H__
+#define __NLE_COMPOSITION_H__
+
+#include <gst/gst.h>
+#include "nleobject.h"
+
+G_BEGIN_DECLS
+#define NLE_TYPE_COMPOSITION \
+ (nle_composition_get_type())
+#define NLE_COMPOSITION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_COMPOSITION,NleComposition))
+#define NLE_COMPOSITION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_COMPOSITION,NleCompositionClass))
+#define NLE_COMPOSITION_GET_CLASS(obj) \
+ (NLE_COMPOSITION_CLASS (G_OBJECT_GET_CLASS (obj)))
+#define NLE_IS_COMPOSITION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_COMPOSITION))
+#define NLE_IS_COMPOSITION_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_COMPOSITION))
+
+typedef struct _NleCompositionPrivate NleCompositionPrivate;
+
+struct _NleComposition
+{
+ NleObject parent;
+
+ GstTask * task;
+ GRecMutex task_rec_lock;
+
+ /*< private >*/
+ NleCompositionPrivate * priv;
+
+};
+
+struct _NleCompositionClass
+{
+ NleObjectClass parent_class;
+};
+
+GType nle_composition_get_type (void) G_GNUC_INTERNAL;
+
+G_END_DECLS
+#endif /* __NLE_COMPOSITION_H__ */
diff --git a/plugins/nle/nleghostpad.c b/plugins/nle/nleghostpad.c
new file mode 100644
index 00000000..c98364ae
--- /dev/null
+++ b/plugins/nle/nleghostpad.c
@@ -0,0 +1,803 @@
+/* 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 "nle.h"
+
+GST_DEBUG_CATEGORY_STATIC (nleghostpad);
+#define GST_CAT_DEFAULT nleghostpad
+
+typedef struct _NlePadPrivate NlePadPrivate;
+
+struct _NlePadPrivate
+{
+ NleObject *object;
+ NlePadPrivate *ghostpriv;
+ GstPadDirection dir;
+ GstPadEventFunction eventfunc;
+ GstPadQueryFunction queryfunc;
+
+ GstEvent *pending_seek;
+};
+
+GstEvent *
+nle_object_translate_incoming_seek (NleObject * 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)
+ && (nle_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)
+ && (nle_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");
+ nle_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;
+
+ return event2;
+
+ /* ERRORS */
+invalid_format:
+ {
+ GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME");
+ return event;
+ }
+}
+
+static GstEvent *
+translate_outgoing_seek (NleObject * 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)
+ && (nle_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)
+ && (nle_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 (NleObject * 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);
+
+ nle_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 (NleObject * 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 (!nle_object_to_media_time (object, orig->time, &segment.time)) {
+ GST_DEBUG ("Can't convert media_time, using 0");
+ segment.time = 0;
+ };
+
+ if (NLE_IS_OPERATION (object)) {
+ segment.base = NLE_OPERATION (object)->next_base_time;
+ GST_INFO_OBJECT (object, "Using operation base time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (NLE_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)
+{
+ NlePadPrivate *priv = gst_pad_get_element_private (internal);
+ NleObject *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;
+ case GST_EVENT_EOS:
+ 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 (NleObject * 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;
+ }
+
+ nle_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 (NleObject * 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 (!(nle_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 (NleObject * 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)
+{
+ NlePadPrivate *priv = gst_pad_get_element_private (internal);
+ NleObject *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)
+{
+ NlePadPrivate *priv;
+ NleObject *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:
+ {
+ GstPad *target;
+
+ event = nle_object_translate_incoming_seek (object, event);
+ if (!(target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghostpad)))) {
+ g_assert ("Seeked a pad with not target SHOULD NOT HAPPEND");
+ ret = FALSE;
+ event = NULL;
+ } else {
+ gst_object_unref (target);
+ }
+ }
+ 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)
+{
+ NlePadPrivate *priv = gst_pad_get_element_private (ghostpad);
+ NleObject *object = NLE_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 (NlePadPrivate * priv, GObject * pad G_GNUC_UNUSED)
+{
+ g_slice_free (NlePadPrivate, 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, NleObject * object)
+{
+ NlePadPrivate *priv;
+ NlePadPrivate *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 NlePadPrivate to put in element_private");
+ priv = g_slice_new0 (NlePadPrivate);
+
+ /* 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));
+}
+
+
+/**
+ * nle_object_ghost_pad:
+ * @object: #NleObject 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 #NleObject
+ *
+ * /!\ 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 *
+nle_object_ghost_pad (NleObject * 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 = nle_object_ghost_pad_no_target (object, name, dir, NULL);
+ if (!ghost) {
+ GST_WARNING_OBJECT (object, "Couldn't create ghostpad");
+ return NULL;
+ }
+
+ if (!(nle_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;
+}
+
+/*
+ * nle_object_ghost_pad_no_target:
+ * /!\ Doesn't add the pad to the NleObject....
+ */
+GstPad *
+nle_object_ghost_pad_no_target (NleObject * object, const gchar * name,
+ GstPadDirection dir, GstPadTemplate * template)
+{
+ GstPad *ghost;
+ NlePadPrivate *priv;
+
+ /* create a no_target ghostpad */
+ if (template)
+ ghost = gst_ghost_pad_new_no_target_from_template (name, template);
+ else
+ ghost = gst_ghost_pad_new_no_target (name, dir);
+ if (!ghost)
+ return NULL;
+
+
+ /* remember the existing ghostpad event/query/link/unlink functions */
+ priv = g_slice_new0 (NlePadPrivate);
+ 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
+nle_object_remove_ghost_pad (NleObject * object, GstPad * ghost)
+{
+ NlePadPrivate *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 (NlePadPrivate, priv);
+}
+
+gboolean
+nle_object_ghost_pad_set_target (NleObject * object, GstPad * ghost,
+ GstPad * target)
+{
+ NlePadPrivate *priv = gst_pad_get_element_private (ghost);
+
+ g_return_val_if_fail (priv, FALSE);
+ g_return_val_if_fail (GST_IS_PAD (ghost), FALSE);
+
+ if (target) {
+ GST_DEBUG_OBJECT (object, "setting target %s:%s on %s:%s",
+ GST_DEBUG_PAD_NAME (target), GST_DEBUG_PAD_NAME (ghost));
+ } else {
+ GST_DEBUG_OBJECT (object, "removing target from ghostpad");
+ priv->pending_seek = NULL;
+ }
+
+ /* set target */
+ if (!(gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), target))) {
+ GST_WARNING_OBJECT (priv->object, "Could not set ghost %s:%s "
+ "target to: %s:%s", GST_DEBUG_PAD_NAME (ghost),
+ GST_DEBUG_PAD_NAME (target));
+ return FALSE;
+ }
+
+ if (target && priv->pending_seek) {
+ gboolean res = gst_pad_send_event (ghost, priv->pending_seek);
+
+ GST_INFO_OBJECT (object, "Sending our pending seek event: %" GST_PTR_FORMAT
+ " -- Result is %i", priv->pending_seek, res);
+
+ priv->pending_seek = NULL;
+ }
+
+ return TRUE;
+}
+
+void
+nle_init_ghostpad_category (void)
+{
+ GST_DEBUG_CATEGORY_INIT (nleghostpad, "nleghostpad",
+ GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin GhostPad");
+
+}
diff --git a/plugins/nle/nleghostpad.h b/plugins/nle/nleghostpad.h
new file mode 100644
index 00000000..25fd3626
--- /dev/null
+++ b/plugins/nle/nleghostpad.h
@@ -0,0 +1,48 @@
+/* GStreamer
+ * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nleghostpad.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 __NLE_GHOSTPAD_H__
+#define __NLE_GHOSTPAD_H__
+
+#include <gst/gst.h>
+
+#include "nletypes.h"
+
+G_BEGIN_DECLS
+
+GstPad *nle_object_ghost_pad (NleObject * object,
+ const gchar * name, GstPad * target) G_GNUC_INTERNAL;
+
+GstPad *nle_object_ghost_pad_no_target (NleObject * object,
+ const gchar * name, GstPadDirection dir, GstPadTemplate *templ) G_GNUC_INTERNAL;
+
+gboolean nle_object_ghost_pad_set_target (NleObject * object,
+ GstPad * ghost, GstPad * target) G_GNUC_INTERNAL;
+
+void nle_object_remove_ghost_pad (NleObject * object, GstPad * ghost) G_GNUC_INTERNAL;
+GstEvent * nle_object_translate_incoming_seek (NleObject * object, GstEvent * event) G_GNUC_INTERNAL;
+
+void nle_init_ghostpad_category (void) G_GNUC_INTERNAL;
+
+G_END_DECLS
+
+#endif /* __NLE_GHOSTPAD_H__ */
diff --git a/plugins/nle/nleobject.c b/plugins/nle/nleobject.c
new file mode 100644
index 00000000..94be16d2
--- /dev/null
+++ b/plugins/nle/nleobject.c
@@ -0,0 +1,738 @@
+/* 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 "nle.h"
+
+/**
+ * SECTION:nleobject
+ * @short_description: Base class for GNonLin elements
+ *
+ * <refsect2>
+ * <para>
+ * NleObject encapsulates default behaviour and implements standard
+ * properties provided by all the GNonLin elements.
+ * </para>
+ * </refsect2>
+ *
+ */
+
+
+GST_DEBUG_CATEGORY_STATIC (nleobject_debug);
+#define GST_CAT_DEFAULT nleobject_debug
+
+static GObjectClass *parent_class = NULL;
+
+/****************************************************
+ * Helper macros *
+ ****************************************************/
+#define CHECK_AND_SET(PROPERTY, property, prop_str, print_format) \
+{ \
+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); \
+}
+
+#define SET_PENDING_VALUE(property, property_str, type, print_format) \
+nleobject->pending_##property = g_value_get_##type (value); \
+if (nleobject->property != nleobject->pending_##property) { \
+ GST_DEBUG_OBJECT(object, "Setting pending " property_str " to %" \
+ print_format, nleobject->pending_##property); \
+ nle_object_set_commit_needed (nleobject); \
+} 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
+};
+
+enum
+{
+ COMMIT_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint _signals[LAST_SIGNAL] = { 0 };
+
+static GParamSpec *properties[PROP_LAST];
+
+static void nle_object_dispose (GObject * object);
+
+static void nle_object_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void nle_object_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void nle_object_constructed (GObject * object);
+
+static GstStateChangeReturn nle_object_change_state (GstElement * element,
+ GstStateChange transition);
+
+static gboolean nle_object_prepare_func (NleObject * object);
+static gboolean nle_object_cleanup_func (NleObject * object);
+static gboolean nle_object_commit_func (NleObject * object, gboolean recurse);
+
+static GstStateChangeReturn nle_object_prepare (NleObject * object);
+
+static void
+nle_object_class_init (NleObjectClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ NleObjectClass *nleobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ nleobject_class = (NleObjectClass *) klass;
+ GST_DEBUG_CATEGORY_INIT (nleobject_debug, "nleobject",
+ GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin object");
+ parent_class = g_type_class_ref (GST_TYPE_BIN);
+
+ gobject_class->set_property = GST_DEBUG_FUNCPTR (nle_object_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_object_get_property);
+ gobject_class->constructed = GST_DEBUG_FUNCPTR (nle_object_constructed);
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_object_dispose);
+
+ gstelement_class->change_state = GST_DEBUG_FUNCPTR (nle_object_change_state);
+
+ nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_object_prepare_func);
+ nleobject_class->cleanup = GST_DEBUG_FUNCPTR (nle_object_cleanup_func);
+ nleobject_class->commit_signal_handler =
+ GST_DEBUG_FUNCPTR (nle_object_commit);
+ nleobject_class->commit = GST_DEBUG_FUNCPTR (nle_object_commit_func);
+
+ /**
+ * NleObject: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]);
+
+ /**
+ * NleObject: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]);
+
+ /**
+ * NleObject: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]);
+
+ /**
+ * NleObject: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]);
+
+ /**
+ * NleObject: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 NleComposition 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]);
+
+ /**
+ * NleObject:active
+ *
+ * Indicates whether this object should be used by its container.
+ *
+ * Set to #TRUE to temporarily disable this object in a #NleComposition.
+ */
+ properties[PROP_ACTIVE] = g_param_spec_boolean ("active", "Active",
+ "Use this object in the NleComposition", TRUE, G_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class, PROP_ACTIVE,
+ properties[PROP_ACTIVE]);
+
+ /**
+ * NleObject: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]);
+
+ /**
+ * NleObject:expandable
+ *
+ * Indicates whether this object should expand to the full duration of its
+ * container #NleComposition.
+ */
+ 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]);
+
+ /**
+ * NleObject::commit
+ * @object: a #NleObject
+ * @recurse: Whether to commit recursiverly into (NleComposition) children of
+ * @object. This is used in case we have composition inside
+ * a nlesource 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 (NleObjectClass, commit_signal_handler), NULL, NULL, NULL,
+ G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+}
+
+static void
+nle_object_init (NleObject * object, NleObjectClass * klass)
+{
+ 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;
+
+ object->srcpad = nle_object_ghost_pad_no_target (object,
+ "src", GST_PAD_SRC,
+ gst_element_class_get_pad_template ((GstElementClass *) klass, "src"));
+
+ gst_element_add_pad (GST_ELEMENT (object), object->srcpad);
+}
+
+static void
+nle_object_dispose (GObject * object)
+{
+ NleObject *nle = (NleObject *) object;
+
+ if (nle->caps) {
+ gst_caps_unref (nle->caps);
+ nle->caps = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+/**
+ * nle_object_to_media_time:
+ * @object: a #NleObject
+ * @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
+nle_object_to_media_time (NleObject * 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;
+}
+
+/**
+ * nle_media_to_object_time:
+ * @object: The #NleObject
+ * @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
+nle_media_to_object_time (NleObject * 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
+nle_object_prepare_func (NleObject * object)
+{
+ GST_DEBUG_OBJECT (object, "default prepare function, returning TRUE");
+
+ return TRUE;
+}
+
+static GstStateChangeReturn
+nle_object_prepare (NleObject * object)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG_OBJECT (object, "preparing");
+
+ if (!(NLE_OBJECT_GET_CLASS (object)->prepare (object)))
+ ret = GST_STATE_CHANGE_FAILURE;
+
+ GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret);
+
+ return ret;
+}
+
+static gboolean
+nle_object_cleanup_func (NleObject * object)
+{
+ GST_DEBUG_OBJECT (object, "default cleanup function, returning TRUE");
+
+ return TRUE;
+}
+
+GstStateChangeReturn
+nle_object_cleanup (NleObject * object)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG_OBJECT (object, "cleaning-up");
+
+ if (!(NLE_OBJECT_GET_CLASS (object)->cleanup (object)))
+ ret = GST_STATE_CHANGE_FAILURE;
+
+ GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret);
+
+ return ret;
+}
+
+void
+nle_object_set_caps (NleObject * object, const GstCaps * caps)
+{
+ if (object->caps)
+ gst_caps_unref (object->caps);
+
+ object->caps = gst_caps_copy (caps);
+}
+
+static inline void
+_update_stop (NleObject * nleobject)
+{
+ /* check if start/duration has changed */
+
+ if ((nleobject->pending_start + nleobject->pending_duration) !=
+ nleobject->stop) {
+ nleobject->stop = nleobject->pending_start + nleobject->pending_duration;
+
+ GST_LOG_OBJECT (nleobject,
+ "Updating stop value : %" GST_TIME_FORMAT " [start:%" GST_TIME_FORMAT
+ ", duration:%" GST_TIME_FORMAT "]", GST_TIME_ARGS (nleobject->stop),
+ GST_TIME_ARGS (nleobject->pending_start),
+ GST_TIME_ARGS (nleobject->pending_duration));
+ g_object_notify_by_pspec (G_OBJECT (nleobject), properties[PROP_STOP]);
+ }
+}
+
+static void
+update_values (NleObject * 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
+nle_object_commit_func (NleObject * object, gboolean recurse)
+{
+ GST_DEBUG_OBJECT (object, "Commiting object changed");
+
+ if (object->commit_needed == FALSE) {
+ GST_INFO_OBJECT (object, "No changes to commit");
+
+ return FALSE;
+ }
+
+ update_values (object);
+
+ GST_DEBUG_OBJECT (object, "Done commiting");
+
+ return TRUE;
+}
+
+static void
+nle_object_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ NleObject *nleobject = (NleObject *) object;
+
+ g_return_if_fail (NLE_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:
+ nle_object_set_caps (nleobject, gst_value_get_caps (value));
+ break;
+ case PROP_EXPANDABLE:
+ if (g_value_get_boolean (value))
+ GST_OBJECT_FLAG_SET (nleobject, NLE_OBJECT_EXPANDABLE);
+ else
+ GST_OBJECT_FLAG_UNSET (nleobject, NLE_OBJECT_EXPANDABLE);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ GST_OBJECT_UNLOCK (object);
+}
+
+static void
+nle_object_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ NleObject *nleobject = (NleObject *) object;
+
+ switch (prop_id) {
+ case PROP_START:
+ g_value_set_uint64 (value, nleobject->pending_start);
+ break;
+ case PROP_DURATION:
+ g_value_set_int64 (value, nleobject->pending_duration);
+ break;
+ case PROP_STOP:
+ g_value_set_uint64 (value, nleobject->stop);
+ break;
+ case PROP_INPOINT:
+ g_value_set_uint64 (value, nleobject->pending_inpoint);
+ break;
+ case PROP_PRIORITY:
+ g_value_set_uint (value, nleobject->pending_priority);
+ break;
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, nleobject->pending_active);
+ break;
+ case PROP_CAPS:
+ gst_value_set_caps (value, nleobject->caps);
+ break;
+ case PROP_EXPANDABLE:
+ g_value_set_boolean (value, NLE_OBJECT_IS_EXPANDABLE (object));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nle_object_constructed (GObject * object)
+{
+ NleObject *nleobject = (NleObject *) object;
+
+ _update_stop (nleobject);
+}
+
+static GstStateChangeReturn
+nle_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 (g_strcmp0 (gst_element_get_name (GST_ELEMENT (parent)),
+ "current-bin")
+ && !NLE_OBJECT_IS_COMPOSITION (NLE_OBJECT (element))) {
+ GST_INFO ("Adding nleobject to something that is not a composition,"
+ " commiting ourself");
+ nle_object_commit (NLE_OBJECT (element), FALSE);
+ }
+
+ gst_object_unref (parent);
+ }
+ }
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ if (nle_object_prepare (NLE_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 nleobject */
+ if (nle_object_cleanup (NLE_OBJECT (element)) == GST_STATE_CHANGE_FAILURE)
+ ret = GST_STATE_CHANGE_FAILURE;
+ break;
+ default:
+ break;
+ }
+
+beach:
+ return ret;
+}
+
+void
+nle_object_set_commit_needed (NleObject * 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
+nle_object_commit (NleObject * object, gboolean recurse)
+{
+ gboolean ret;
+
+ GST_DEBUG_OBJECT (object, "Commiting object state");
+
+ object->commiting = TRUE;
+ ret = NLE_OBJECT_GET_CLASS (object)->commit (object, recurse);
+ object->commiting = FALSE;
+
+ return ret;
+
+}
+
+static void
+_send_seek_event (const GValue * item, gpointer seek_event)
+{
+ GstElement *child = g_value_get_object (item);
+
+ gst_element_send_event (child, gst_event_ref (seek_event));
+}
+
+void
+nle_object_seek_all_children (NleObject * object, GstEvent * seek_event)
+{
+ GstIterator *it = gst_bin_iterate_recurse (GST_BIN (object));
+
+ while (gst_iterator_foreach (it, _send_seek_event,
+ seek_event) == GST_ITERATOR_RESYNC)
+ gst_iterator_resync (it);
+
+ gst_iterator_free (it);
+ gst_event_unref (seek_event);
+}
+
+void
+nle_object_reset (NleObject * 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;
+}
+
+GType
+nle_object_get_type (void)
+{
+ static volatile gsize type = 0;
+
+ if (g_once_init_enter (&type)) {
+ GType _type;
+ static const GTypeInfo info = {
+ sizeof (NleObjectClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) nle_object_class_init,
+ NULL,
+ NULL,
+ sizeof (NleObject),
+ 0,
+ (GInstanceInitFunc) nle_object_init,
+ };
+
+ _type = g_type_register_static (GST_TYPE_BIN,
+ "NleObject", &info, G_TYPE_FLAG_ABSTRACT);
+ g_once_init_leave (&type, _type);
+ }
+ return type;
+}
diff --git a/plugins/nle/nleobject.h b/plugins/nle/nleobject.h
new file mode 100644
index 00000000..1e0272e4
--- /dev/null
+++ b/plugins/nle/nleobject.h
@@ -0,0 +1,173 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ * 2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nleobject.h: Header for base NleObject
+ *
+ * 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 __NLE_OBJECT_H__
+#define __NLE_OBJECT_H__
+
+#include <gst/gst.h>
+
+#include "nletypes.h"
+
+G_BEGIN_DECLS
+#define NLE_TYPE_OBJECT \
+ (nle_object_get_type())
+#define NLE_OBJECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_OBJECT,NleObject))
+#define NLE_OBJECT_CAST(obj) ((NleObject*) (obj))
+#define NLE_OBJECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_OBJECT,NleObjectClass))
+#define NLE_OBJECT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NLE_TYPE_OBJECT, NleObjectClass))
+#define NLE_IS_OBJECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_OBJECT))
+#define NLE_IS_OBJECT_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_OBJECT))
+
+#define NLE_OBJECT_SRC(obj) (((NleObject *) obj)->srcpad)
+
+/**
+ * NleObjectFlags:
+ * @NLE_OBJECT_IS_SOURCE:
+ * @NLE_OBJECT_IS_OPERATION:
+ * @NLE_OBJECT_IS_EXPANDABLE: The #NleObject start/stop will extend accross the full composition.
+ * @NLE_OBJECT_LAST_FLAG:
+*/
+
+typedef enum
+{
+ NLE_OBJECT_SOURCE = (GST_BIN_FLAG_LAST << 0),
+ NLE_OBJECT_OPERATION = (GST_BIN_FLAG_LAST << 1),
+ NLE_OBJECT_EXPANDABLE = (GST_BIN_FLAG_LAST << 2),
+ NLE_OBJECT_COMPOSITION = (GST_BIN_FLAG_LAST << 3),
+ /* padding */
+ NLE_OBJECT_LAST_FLAG = (GST_BIN_FLAG_LAST << 5)
+} NleObjectFlags;
+
+
+#define NLE_OBJECT_IS_SOURCE(obj) \
+ (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_SOURCE))
+#define NLE_OBJECT_IS_OPERATION(obj) \
+ (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_OPERATION))
+#define NLE_OBJECT_IS_EXPANDABLE(obj) \
+ (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_EXPANDABLE))
+#define NLE_OBJECT_IS_COMPOSITION(obj) \
+ (GST_OBJECT_FLAG_IS_SET(obj, NLE_OBJECT_COMPOSITION))
+
+/* For internal usage only */
+#define NLE_OBJECT_START(obj) (NLE_OBJECT_CAST (obj)->start)
+#define NLE_OBJECT_STOP(obj) (NLE_OBJECT_CAST (obj)->stop)
+#define NLE_OBJECT_DURATION(obj) (NLE_OBJECT_CAST (obj)->duration)
+#define NLE_OBJECT_INPOINT(obj) (NLE_OBJECT_CAST (obj)->inpoint)
+#define NLE_OBJECT_PRIORITY(obj) (NLE_OBJECT_CAST (obj)->priority)
+#define NLE_OBJECT_ACTIVE(obj) (NLE_OBJECT_DURATION(obj) > 0 && NLE_OBJECT_CAST (obj)->active)
+
+#define NLE_OBJECT_IS_COMMITING(obj) (NLE_OBJECT_CAST (obj)->commiting)
+
+struct _NleObject
+{
+ GstBin parent;
+
+ GstPad *srcpad;
+
+ /* 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;
+
+ gboolean in_composition;
+};
+
+struct _NleObjectClass
+{
+ GstBinClass parent_class;
+
+ /* Signal method handler */
+ gboolean (*commit_signal_handler) (NleObject * object, gboolean recurse);
+
+ /* virtual methods for subclasses */
+ gboolean (*prepare) (NleObject * object);
+ gboolean (*cleanup) (NleObject * object);
+ gboolean (*commit) (NleObject * object, gboolean recurse);
+};
+
+GType nle_object_get_type (void)G_GNUC_INTERNAL;
+
+gboolean
+nle_object_to_media_time (NleObject * object, GstClockTime otime,
+ GstClockTime * mtime) G_GNUC_INTERNAL;
+
+gboolean
+nle_media_to_object_time (NleObject * object, GstClockTime mtime,
+ GstClockTime * otime) G_GNUC_INTERNAL;
+
+void
+nle_object_set_caps (NleObject * object, const GstCaps * caps) G_GNUC_INTERNAL;
+
+void
+nle_object_set_commit_needed (NleObject *object) G_GNUC_INTERNAL;
+
+gboolean
+nle_object_commit (NleObject *object, gboolean recurse) G_GNUC_INTERNAL;
+
+void
+nle_object_reset (NleObject *object) G_GNUC_INTERNAL;
+
+GstStateChangeReturn
+nle_object_cleanup (NleObject * object) G_GNUC_INTERNAL;
+
+void nle_object_seek_all_children (NleObject *object, GstEvent *seek_event) G_GNUC_INTERNAL;
+
+G_END_DECLS
+#endif /* __NLE_OBJECT_H__ */
diff --git a/plugins/nle/nleoperation.c b/plugins/nle/nleoperation.c
new file mode 100644
index 00000000..fa59734c
--- /dev/null
+++ b/plugins/nle/nleoperation.c
@@ -0,0 +1,843 @@
+/* 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 "nle.h"
+
+/**
+ * SECTION:element-nleoperation
+ *
+ * <refsect2>
+ * <para>
+ * A NleOperation performs a transformation or mixing operation on the
+ * data from one or more #NleSources, which is used to implement filters or
+ * effects.
+ * </para>
+ * </refsect2>
+ */
+
+static GstStaticPadTemplate nle_operation_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate nle_operation_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink%d",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (nleoperation);
+#define GST_CAT_DEFAULT nleoperation
+
+#define _do_init \
+ GST_DEBUG_CATEGORY_INIT (nleoperation, "nleoperation", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Operation element");
+#define nle_operation_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (NleOperation, nle_operation, NLE_TYPE_OBJECT,
+ _do_init);
+
+enum
+{
+ ARG_0,
+ ARG_SINKS,
+};
+
+enum
+{
+ INPUT_PRIORITY_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint nle_operation_signals[LAST_SIGNAL] = { 0 };
+
+static void nle_operation_dispose (GObject * object);
+
+static void nle_operation_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void nle_operation_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean nle_operation_prepare (NleObject * object);
+static gboolean nle_operation_cleanup (NleObject * object);
+
+static gboolean nle_operation_add_element (GstBin * bin, GstElement * element);
+static gboolean nle_operation_remove_element (GstBin * bin,
+ GstElement * element);
+
+static GstPad *nle_operation_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
+static void nle_operation_release_pad (GstElement * element, GstPad * pad);
+
+static void synchronize_sinks (NleOperation * operation);
+static gboolean remove_sink_pad (NleOperation * operation, GstPad * sinkpad);
+
+
+static gboolean
+nle_operation_send_event (GstElement * element, GstEvent * event)
+{
+ gboolean res = TRUE;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ nle_object_seek_all_children (NLE_OBJECT (element), event);
+ break;
+ default:
+ res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+ break;
+ }
+
+ return res;
+}
+
+static void
+nle_operation_class_init (NleOperationClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstBinClass *gstbin_class = (GstBinClass *) klass;
+
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+ NleObjectClass *nleobject_class = (NleObjectClass *) klass;
+
+ gst_element_class_set_static_metadata (gstelement_class, "GNonLin Operation",
+ "Filter/Editor",
+ "Encapsulates filters/effects for use with NLE Objects",
+ "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>");
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_operation_dispose);
+
+ gobject_class->set_property = GST_DEBUG_FUNCPTR (nle_operation_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_operation_get_property);
+
+ /**
+ * NleOperation: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));
+
+ /**
+ * NleOperation: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.
+ */
+ nle_operation_signals[INPUT_PRIORITY_CHANGED] =
+ g_signal_new ("input-priority-changed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NleOperationClass,
+ 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 (nle_operation_request_new_pad);
+ gstelement_class->release_pad = GST_DEBUG_FUNCPTR (nle_operation_release_pad);
+ gstelement_class->send_event = GST_DEBUG_FUNCPTR (nle_operation_send_event);
+
+ gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_operation_add_element);
+ gstbin_class->remove_element =
+ GST_DEBUG_FUNCPTR (nle_operation_remove_element);
+
+ nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_operation_prepare);
+ nleobject_class->cleanup = GST_DEBUG_FUNCPTR (nle_operation_cleanup);
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&nle_operation_src_template));
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&nle_operation_sink_template));
+
+}
+
+static void
+nle_operation_dispose (GObject * object)
+{
+ NleOperation *oper = (NleOperation *) object;
+
+ GST_DEBUG_OBJECT (object, "Disposing of source pad");
+
+ nle_object_ghost_pad_set_target (NLE_OBJECT (object),
+ NLE_OBJECT (object)->srcpad, 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
+nle_operation_reset (NleOperation * operation)
+{
+ operation->num_sinks = 1;
+ operation->realsinks = 0;
+ operation->next_base_time = 0;
+}
+
+static void
+nle_operation_init (NleOperation * operation)
+{
+ nle_operation_reset (operation);
+ 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 (NleOperation * 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
+nle_operation_add_element (GstBin * bin, GstElement * element)
+{
+ NleOperation *operation = (NleOperation *) 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;
+
+ nle_object_ghost_pad_set_target (NLE_OBJECT (operation),
+ NLE_OBJECT (operation)->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
+nle_operation_remove_element (GstBin * bin, GstElement * element)
+{
+ NleOperation *operation = (NleOperation *) 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
+nle_operation_set_sinks (NleOperation * operation, guint sinks)
+{
+ /* FIXME : Check if sinkpad of element is on-demand .... */
+
+ operation->num_sinks = sinks;
+ synchronize_sinks (operation);
+}
+
+static void
+nle_operation_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ NleOperation *operation = (NleOperation *) object;
+
+ switch (prop_id) {
+ case ARG_SINKS:
+ nle_operation_set_sinks (operation, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nle_operation_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ NleOperation *operation = (NleOperation *) 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 (NleOperation * 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 (NleOperation * 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 (NleOperation * 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 (NleOperation * 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 = nle_object_ghost_pad ((NleObject *) operation, GST_PAD_NAME (ret),
+ ret);
+ gst_object_unref (ret);
+ }
+ }
+
+ if (!gpad) {
+ /* request sink pads */
+ ret = get_request_sink_pad (operation);
+ if (ret) {
+ gpad = nle_object_ghost_pad ((NleObject *) 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 (NleOperation * 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 */
+ nle_object_ghost_pad_set_target ((NleObject *) 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);
+ nle_object_remove_ghost_pad ((NleObject *) operation, sinkpad);
+ operation->realsinks--;
+ }
+
+beach:
+ return ret;
+}
+
+static void
+synchronize_sinks (NleOperation * 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
+nle_operation_prepare (NleObject * object)
+{
+ /* Prepare the pads */
+ synchronize_sinks ((NleOperation *) object);
+
+ return TRUE;
+}
+
+static gboolean
+nle_operation_cleanup (NleObject * object)
+{
+ NleOperation *oper = (NleOperation *) object;
+
+ if (oper->dynamicsinks) {
+ GST_DEBUG ("Resetting dynamic sinks");
+ nle_operation_set_sinks (oper, 0);
+ }
+
+ return TRUE;
+}
+
+void
+nle_operation_hard_cleanup (NleOperation * operation)
+{
+ gboolean done = FALSE;
+
+ GValue item = { 0, };
+ GstIterator *pads;
+
+ GST_INFO_OBJECT (operation, "Hard reset of the operation");
+
+ pads = gst_element_iterate_sink_pads (GST_ELEMENT (operation));
+ while (!done) {
+ switch (gst_iterator_next (pads, &item)) {
+ case GST_ITERATOR_OK:
+ {
+ GstPad *sinkpad = g_value_get_object (&item);
+ GstPad *srcpad = gst_pad_get_peer (sinkpad);
+
+ if (srcpad) {
+ GST_ERROR ("Unlinking %" GST_PTR_FORMAT " and %"
+ GST_PTR_FORMAT, srcpad, sinkpad);
+ gst_pad_unlink (srcpad, sinkpad);
+ }
+
+ g_value_reset (&item);
+ break;
+ }
+ case GST_ITERATOR_RESYNC:
+ gst_iterator_resync (pads);
+ break;
+ default:
+ /* ERROR and DONE */
+ done = TRUE;
+ break;
+ }
+ }
+ nle_object_cleanup (NLE_OBJECT (operation));
+}
+
+
+static GstPad *
+nle_operation_request_new_pad (GstElement * element, GstPadTemplate * templ,
+ const gchar * name, const GstCaps * caps)
+{
+ NleOperation *operation = (NleOperation *) 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 ((NleOperation *) element);
+
+ return ret;
+}
+
+static void
+nle_operation_release_pad (GstElement * element, GstPad * pad)
+{
+ GST_DEBUG ("pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ remove_sink_pad ((NleOperation *) element, pad);
+}
+
+void
+nle_operation_signal_input_priority_changed (NleOperation * operation,
+ GstPad * pad, guint32 priority)
+{
+ GST_DEBUG_OBJECT (operation, "pad:%s:%s, priority:%d",
+ GST_DEBUG_PAD_NAME (pad), priority);
+ g_signal_emit (operation, nle_operation_signals[INPUT_PRIORITY_CHANGED],
+ 0, pad, priority);
+}
+
+void
+nle_operation_update_base_time (NleOperation * operation,
+ GstClockTime timestamp)
+{
+ if (!nle_object_to_media_time (NLE_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/plugins/nle/nleoperation.h b/plugins/nle/nleoperation.h
new file mode 100644
index 00000000..3f68f5d8
--- /dev/null
+++ b/plugins/nle/nleoperation.h
@@ -0,0 +1,90 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@chello.be>
+ * 2004 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nleoperation.h: Header for base NleOperation
+ *
+ * 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 __NLE_OPERATION_H__
+#define __NLE_OPERATION_H__
+
+#include <gst/gst.h>
+#include "nleobject.h"
+
+G_BEGIN_DECLS
+#define NLE_TYPE_OPERATION \
+ (nle_operation_get_type())
+#define NLE_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_OPERATION,NleOperation))
+#define NLE_OPERATION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_OPERATION,NleOperationClass))
+#define NLE_IS_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_OPERATION))
+#define NLE_IS_OPERATION_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_OPERATION))
+ struct _NleOperation
+{
+ NleObject 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 */
+
+ GstElement *element; /* controlled element */
+
+ GstClockTime next_base_time;
+};
+
+struct _NleOperationClass
+{
+ NleObjectClass parent_class;
+
+ void (*input_priority_changed) (NleOperation * operation, GstPad *pad, guint32 priority);
+};
+
+GstPad * get_unlinked_sink_ghost_pad (NleOperation * operation) G_GNUC_INTERNAL;
+
+void
+nle_operation_signal_input_priority_changed(NleOperation * operation, GstPad *pad,
+ guint32 priority) G_GNUC_INTERNAL;
+
+void nle_operation_update_base_time (NleOperation *operation,
+ GstClockTime timestamp) G_GNUC_INTERNAL;
+
+void nle_operation_hard_cleanup (NleOperation *operation) G_GNUC_INTERNAL;
+
+
+/* normal GOperation stuff */
+GType nle_operation_get_type (void) G_GNUC_INTERNAL;
+
+G_END_DECLS
+#endif /* __NLE_OPERATION_H__ */
diff --git a/plugins/nle/nlesource.c b/plugins/nle/nlesource.c
new file mode 100644
index 00000000..fcb98506
--- /dev/null
+++ b/plugins/nle/nlesource.c
@@ -0,0 +1,505 @@
+/* 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 "nle.h"
+
+/**
+ * SECTION:element-nlesource
+ *
+ * The NleSource encapsulates a pipeline which produces data for processing
+ * in a #NleComposition.
+ */
+
+static GstStaticPadTemplate nle_source_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (nlesource);
+#define GST_CAT_DEFAULT nlesource
+
+#define _do_init \
+ GST_DEBUG_CATEGORY_INIT (nlesource, "nlesource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Source Element");
+#define nle_source_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (NleSource, nle_source, NLE_TYPE_OBJECT, _do_init);
+
+struct _NleSourcePrivate
+{
+ gboolean dispose_has_run;
+
+ gboolean dynamicpads; /* TRUE if the controlled element has dynamic pads */
+
+ gulong padremovedid; /* signal handler for element pad-removed signal */
+ gulong padaddedid; /* signal handler for element pad-added signal */
+
+ 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 */
+
+ GstEvent *seek_event;
+ gulong probeid;
+};
+
+static gboolean nle_source_prepare (NleObject * object);
+static gboolean nle_source_send_event (GstElement * element, GstEvent * event);
+static gboolean nle_source_add_element (GstBin * bin, GstElement * element);
+static gboolean nle_source_remove_element (GstBin * bin, GstElement * element);
+static void nle_source_dispose (GObject * object);
+
+static gboolean
+nle_source_control_element_func (NleSource * source, GstElement * element);
+
+static void
+nle_source_class_init (NleSourceClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBinClass *gstbin_class;
+ NleObjectClass *nleobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbin_class = (GstBinClass *) klass;
+ nleobject_class = (NleObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (NleSourcePrivate));
+
+ 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>");
+
+ gstelement_class->send_event = GST_DEBUG_FUNCPTR (nle_source_send_event);
+
+ parent_class = g_type_class_ref (NLE_TYPE_OBJECT);
+
+ klass->control_element = GST_DEBUG_FUNCPTR (nle_source_control_element_func);
+
+ nleobject_class->prepare = GST_DEBUG_FUNCPTR (nle_source_prepare);
+
+ gstbin_class->add_element = GST_DEBUG_FUNCPTR (nle_source_add_element);
+ gstbin_class->remove_element = GST_DEBUG_FUNCPTR (nle_source_remove_element);
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (nle_source_dispose);
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&nle_source_src_template));
+
+}
+
+
+static void
+nle_source_init (NleSource * source)
+{
+ GST_OBJECT_FLAG_SET (source, NLE_OBJECT_SOURCE);
+ source->element = NULL;
+ source->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (source, NLE_TYPE_SOURCE, NleSourcePrivate);
+
+ GST_DEBUG_OBJECT (source, "Setting GstBin async-handling to TRUE");
+ g_object_set (G_OBJECT (source), "async-handling", TRUE, NULL);
+}
+
+static void
+nle_source_dispose (GObject * object)
+{
+ NleObject *nleobject = (NleObject *) object;
+ NleSource *source = (NleSource *) object;
+ NleSourcePrivate *priv = source->priv;
+
+ GST_DEBUG_OBJECT (object, "dispose");
+
+ if (priv->dispose_has_run)
+ return;
+
+ GST_OBJECT_LOCK (object);
+ if (priv->probeid) {
+ GST_DEBUG_OBJECT (source, "Removing blocking probe! %lu", priv->probeid);
+ priv->areblocked = FALSE;
+ gst_pad_remove_probe (priv->ghostedpad, priv->probeid);
+ priv->probeid = 0;
+ }
+ GST_OBJECT_UNLOCK (object);
+
+
+ if (source->element) {
+ gst_object_unref (source->element);
+ source->element = NULL;
+ }
+
+ priv->dispose_has_run = TRUE;
+ if (priv->ghostedpad)
+ nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, 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,
+ NleSource * source)
+{
+ GstCaps *srccaps;
+ NleSourcePrivate *priv = source->priv;
+ NleObject *nleobject = (NleObject *) source;
+
+ GST_DEBUG_OBJECT (source, "pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ if (priv->ghostedpad) {
+ GST_DEBUG_OBJECT (source,
+ "We already have a target, not doing anything with %s:%s",
+ GST_DEBUG_PAD_NAME (pad));
+
+ return;
+ }
+
+ /* FIXME: pass filter caps to query_caps directly */
+ srccaps = gst_pad_query_caps (pad, NULL);
+ if (nleobject->caps && !gst_caps_can_intersect (srccaps, nleobject->caps)) {
+ gst_caps_unref (srccaps);
+ GST_DEBUG_OBJECT (source, "Pad doesn't have valid caps, ignoring");
+ return;
+ }
+ gst_caps_unref (srccaps);
+
+ priv->ghostedpad = pad;
+ GST_DEBUG_OBJECT (nleobject, "SET target %" GST_PTR_FORMAT, pad);
+ nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, pad);
+
+ GST_DEBUG_OBJECT (source, "Using pad pad %s:%s as a target now!",
+ GST_DEBUG_PAD_NAME (pad));
+}
+
+static void
+element_pad_removed_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad,
+ NleSource * source)
+{
+ NleSourcePrivate *priv = source->priv;
+ NleObject *nleobject = (NleObject *) source;
+
+ 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");
+
+ GST_DEBUG_OBJECT (source, "Clearing up ghostpad");
+
+ nle_object_ghost_pad_set_target (NLE_OBJECT (source), nleobject->srcpad,
+ NULL);
+ 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 NleObject caps in the
+ given element. Fills in pad if so. The returned pad has an incremented refcount
+*/
+
+static gboolean
+get_valid_src_pad (NleSource * 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,
+ NLE_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;
+}
+
+/*
+ * 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
+nle_source_control_element_func (NleSource * source, GstElement * element)
+{
+ NleSourcePrivate *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;
+ nle_object_ghost_pad_set_target (NLE_OBJECT (source),
+ NLE_OBJECT_SRC (source), pad);
+ 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
+nle_source_add_element (GstBin * bin, GstElement * element)
+{
+ NleSource *source = (NleSource *) bin;
+ gboolean pret;
+
+ GST_DEBUG_OBJECT (source, "Adding element %s", GST_ELEMENT_NAME (element));
+
+ if (source->element) {
+ GST_WARNING_OBJECT (bin, "NleSource 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) {
+ nle_source_control_element_func (source, element);
+ }
+ return pret;
+}
+
+static gboolean
+nle_source_remove_element (GstBin * bin, GstElement * element)
+{
+ NleSource *source = (NleSource *) bin;
+ NleObject *nleobject = (NleObject *) element;
+ NleSourcePrivate *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) {
+ nle_object_ghost_pad_set_target (NLE_OBJECT (source), nleobject->srcpad,
+ 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
+nle_source_send_event (GstElement * element, GstEvent * event)
+{
+ gboolean res = TRUE;
+ NleSource *source = (NleSource *) element;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ source->priv->seek_event = event;
+ break;
+ default:
+ res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+ break;
+ }
+
+ return res;
+}
+
+static gpointer
+ghost_seek_pad (NleSource * source)
+{
+ NleSourcePrivate *priv = source->priv;
+
+ if (priv->seek_event) {
+ GstEvent *seek_event = priv->seek_event;
+ priv->seek_event = NULL;
+
+ if (!(gst_pad_send_event (priv->ghostedpad, seek_event)))
+ GST_ELEMENT_ERROR (source, RESOURCE, SEEK,
+ (NULL), ("Sending initial seek to upstream element failed"));
+ }
+
+ GST_OBJECT_LOCK (source);
+ if (priv->probeid) {
+ GST_DEBUG_OBJECT (source, "Removing blocking probe! %lu", priv->probeid);
+ priv->areblocked = FALSE;
+ gst_pad_remove_probe (priv->ghostedpad, priv->probeid);
+ priv->probeid = 0;
+ }
+ GST_OBJECT_UNLOCK (source);
+
+ return NULL;
+}
+
+static GstPadProbeReturn
+pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source)
+{
+ GThread *lthread;
+
+ if (!source->priv->areblocked) {
+ GST_INFO_OBJECT (pad, "Blocked now, launching seek");
+ source->priv->areblocked = TRUE;
+ lthread =
+ g_thread_new ("gnlsourceseek", (GThreadFunc) ghost_seek_pad, source);
+ g_thread_unref (lthread);
+ }
+
+ return GST_PAD_PROBE_OK;
+}
+
+static gboolean
+nle_source_prepare (NleObject * object)
+{
+ GstPad *pad;
+ NleSource *source = NLE_SOURCE (object);
+ NleSourcePrivate *priv = source->priv;
+ GstElement *parent =
+ (GstElement *) gst_element_get_parent ((GstElement *) object);
+
+ if (!source->element) {
+ GST_WARNING_OBJECT (source,
+ "NleSource doesn't have an element to control !");
+ if (parent)
+ gst_object_unref (parent);
+ return FALSE;
+ }
+
+ if (object->in_composition == FALSE) {
+ gst_element_send_event (GST_ELEMENT_CAST (parent),
+ 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_LOG_OBJECT (source, "srcpad:%p, dynamicpads:%d",
+ object->srcpad, priv->dynamicpads);
+
+ 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);
+ priv->ghostedpad = pad;
+ GST_OBJECT_LOCK (source);
+ priv->probeid = gst_pad_add_probe (pad,
+ GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+ (GstPadProbeCallback) pad_blocked_cb, source, NULL);
+ GST_OBJECT_UNLOCK (source);
+ gst_object_unref (pad);
+ }
+
+ gst_object_unref (parent);
+
+ return TRUE;
+}
diff --git a/plugins/nle/nlesource.h b/plugins/nle/nlesource.h
new file mode 100644
index 00000000..6fcb1af4
--- /dev/null
+++ b/plugins/nle/nlesource.h
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ * 2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nlesource.h: Header for base NleSource
+ *
+ * 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 __NLE_SOURCE_H__
+#define __NLE_SOURCE_H__
+
+#include <gst/gst.h>
+#include "nleobject.h"
+
+G_BEGIN_DECLS
+#define NLE_TYPE_SOURCE \
+ (nle_source_get_type())
+#define NLE_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_SOURCE,NleSource))
+#define NLE_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_SOURCE,NleSourceClass))
+#define NLE_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), NLE_TYPE_SOURCE, NleSourceClass))
+#define NLE_IS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_SOURCE))
+#define NLE_IS_SOURCE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_SOURCE))
+typedef struct _NleSourcePrivate NleSourcePrivate;
+
+struct _NleSource
+{
+ NleObject parent;
+
+ /* controlled source element, acces with gst_bin_[add|remove]_element */
+ GstElement *element;
+
+ NleSourcePrivate *priv;
+};
+
+struct _NleSourceClass
+{
+ NleObjectClass parent_class;
+
+ /* control_element() takes care of controlling the given element */
+ gboolean (*control_element) (NleSource * source, GstElement * element);
+};
+
+GType nle_source_get_type (void) G_GNUC_INTERNAL;
+
+G_END_DECLS
+#endif /* __NLE_SOURCE_H__ */
diff --git a/plugins/nle/nletypes.h b/plugins/nle/nletypes.h
new file mode 100644
index 00000000..f84cb23b
--- /dev/null
+++ b/plugins/nle/nletypes.h
@@ -0,0 +1,42 @@
+/* GStreamer
+ * Copyright (C) 2004 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nletypes.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 __NLE_TYPES_H__
+#define __NLE_TYPES_H__
+
+#include <glib.h>
+
+typedef struct _NleObject NleObject;
+typedef struct _NleObjectClass NleObjectClass;
+
+typedef struct _NleComposition NleComposition;
+typedef struct _NleCompositionClass NleCompositionClass;
+
+typedef struct _NleOperation NleOperation;
+typedef struct _NleOperationClass NleOperationClass;
+
+typedef struct _NleSource NleSource;
+typedef struct _NleSourceClass NleSourceClass;
+
+typedef struct _NleURISource NleURISource;
+typedef struct _NleURISourceClass NleURISourceClass;
+
+#endif
diff --git a/plugins/nle/nleurisource.c b/plugins/nle/nleurisource.c
new file mode 100644
index 00000000..8fd2950a
--- /dev/null
+++ b/plugins/nle/nleurisource.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 "nle.h"
+#include "nleurisource.h"
+
+/**
+ * SECTION:element-nleurisource
+ *
+ * NleURISource is a #NleSource which reads and decodes the contents
+ * of a given file. The data in the file is decoded using any available
+ * GStreamer plugins.
+ */
+
+static GstStaticPadTemplate nle_urisource_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (nleurisource);
+#define GST_CAT_DEFAULT nleurisource
+
+#define _do_init \
+ GST_DEBUG_CATEGORY_INIT (nleurisource, "nleurisource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin URI Source Element");
+#define nle_urisource_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (NleURISource, nle_urisource, NLE_TYPE_SOURCE,
+ _do_init);
+
+enum
+{
+ ARG_0,
+ ARG_URI,
+};
+
+static gboolean nle_urisource_prepare (NleObject * object);
+
+static void
+nle_urisource_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static void
+nle_urisource_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void
+nle_urisource_class_init (NleURISourceClass * klass)
+{
+ GObjectClass *gobject_class;
+ NleObjectClass *nleobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ nleobject_class = (NleObjectClass *) klass;
+ parent_class = g_type_class_ref (NLE_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 (nle_urisource_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR (nle_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 (&nle_urisource_src_template));
+
+ nleobject_class->prepare = nle_urisource_prepare;
+}
+
+static void
+nle_urisource_init (NleURISource * urisource)
+{
+ GstElement *decodebin = NULL;
+
+ GST_OBJECT_FLAG_SET (urisource, NLE_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
+nle_urisource_set_uri (NleURISource * fs, const gchar * uri)
+{
+ g_object_set (NLE_SOURCE (fs)->element, "uri", uri, NULL);
+}
+
+static void
+nle_urisource_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ NleURISource *fs = (NleURISource *) object;
+
+ switch (prop_id) {
+ case ARG_URI:
+ nle_urisource_set_uri (fs, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nle_urisource_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ NleURISource *fs = (NleURISource *) object;
+
+ switch (prop_id) {
+ case ARG_URI:
+ g_object_get_property ((GObject *) NLE_SOURCE (fs)->element, "uri",
+ value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+}
+
+static gboolean
+nle_urisource_prepare (NleObject * object)
+{
+ NleSource *fs = (NleSource *) 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 NLE_OBJECT_CLASS (parent_class)->prepare (object);
+}
diff --git a/plugins/nle/nleurisource.h b/plugins/nle/nleurisource.h
new file mode 100644
index 00000000..a2750e59
--- /dev/null
+++ b/plugins/nle/nleurisource.h
@@ -0,0 +1,57 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ * 2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * nleurisource.h: Header for NleURISource
+ *
+ * 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 __NLE_URI_SOURCE_H__
+#define __NLE_URI_SOURCE_H__
+
+#include <gst/gst.h>
+#include "nlesource.h"
+
+G_BEGIN_DECLS
+#define NLE_TYPE_URI_SOURCE \
+ (nle_urisource_get_type())
+#define NLE_URI_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),NLE_TYPE_URI_SOURCE,NleURIsource))
+#define NLE_URI_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),NLE_TYPE_URI_SOURCE,NleURIsourceClass))
+#define NLE_IS_URI_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),NLE_TYPE_URI_SOURCE))
+#define NLE_IS_URI_SOURCE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),NLE_TYPE_URI_SOURCE))
+
+struct _NleURISource
+{
+ NleSource parent;
+
+ gchar *uri;
+};
+
+struct _NleURISourceClass
+{
+ NleSourceClass parent_class;
+};
+
+GType nle_urisource_get_type (void) G_GNUC_INTERNAL;
+
+G_END_DECLS
+#endif /* __NLE_URI_SOURCE_H__ */