Replace the switch plugin with the selector plugin. Add output- selector as the oppos...
authorStefan Kost <ensonic@users.sourceforge.net>
Tue, 29 Jan 2008 07:38:31 +0000 (07:38 +0000)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Fri, 31 Dec 2010 00:51:12 +0000 (00:51 +0000)
Original commit message from CVS:
* configure.ac:
* docs/plugins/Makefile.am:
* docs/plugins/gst-plugins-bad-plugins-docs.sgml:
* docs/plugins/gst-plugins-bad-plugins-sections.txt:
* docs/plugins/gst-plugins-bad-plugins.args:
* docs/plugins/gst-plugins-bad-plugins.hierarchy:
* docs/plugins/gst-plugins-bad-plugins.interfaces:
* docs/plugins/gst-plugins-bad-plugins.signals:
* docs/plugins/inspect/plugin-metadata.xml:
* docs/plugins/inspect/plugin-selector.xml:
* docs/plugins/inspect/plugin-soundtouch.xml:
* docs/plugins/inspect/plugin-switch.xml:
* plugins/elements/.cvsignore:
* plugins/elements/Makefile.am:
* plugins/elements/gstinputselector.c:
* plugins/elements/gstinputselector.h:
* plugins/elements/gstoutputselector.c:
* plugins/elements/gstoutputselector.h:
* plugins/elements/gstselector-marshal.list:
* plugins/elements/gstselector.c:
* plugins/elements/selector.vcproj:
* gst/switch/.cvsignore:
* gst/switch/Makefile.am:
* gst/switch/gstswitch-marshal.list:
* gst/switch/gstswitch.c:
* gst/switch/gstswitch.h:
* gst/switch/switch.vcproj:
* tests/icles/.cvsignore:
* tests/icles/Makefile.am:
* tests/icles/output-selector-test.c:
Replace the switch plugin with the selector plugin. Add output-
selector as the opposite of input-selectoo (was switch). Add a test
for output-selector. Add docs for the elements. The vcproj needs
update. Fixes #500142.

plugins/elements/gstinputselector.c [new file with mode: 0644]
plugins/elements/gstinputselector.h [new file with mode: 0644]
plugins/elements/gstoutputselector.c [new file with mode: 0644]
plugins/elements/gstoutputselector.h [new file with mode: 0644]
plugins/elements/gstselector-marshal.list [new file with mode: 0644]
plugins/elements/gstselector.c [new file with mode: 0644]
tests/icles/output-selector-test.c [new file with mode: 0755]

diff --git a/plugins/elements/gstinputselector.c b/plugins/elements/gstinputselector.c
new file mode 100644 (file)
index 0000000..a4d4618
--- /dev/null
@@ -0,0 +1,981 @@
+/* GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
+ * Copyright (C) 2005 Jan Schmidt <thaytan@mad.scientist.com>
+ * Copyright (C) 2007 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) 2007 Andy Wingo <wingo@pobox.com>
+ * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-input-selector
+ * @short_description: N-to-1 stream selectoring
+ * @see_also: #GstOutputSelector
+ *
+ * Direct one out of N input streams to the output pad.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "gstinputselector.h"
+#include "gstselector-marshal.h"
+
+GST_DEBUG_CATEGORY_STATIC (input_selector_debug);
+#define GST_CAT_DEFAULT input_selector_debug
+
+static const GstElementDetails gst_input_selector_details =
+GST_ELEMENT_DETAILS ("Input selector",
+    "Generic",
+    "N-to-1 input stream selectoring",
+    "Julien Moutte <julien@moutte.net>\n"
+    "Ronald S. Bultje <rbultje@ronald.bitfreak.net>\n"
+    "Jan Schmidt <thaytan@mad.scientist.com>\n"
+    "Wim Taymans <wim.taymans@gmail.com>");
+
+static GstStaticPadTemplate gst_input_selector_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink%d",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate gst_input_selector_src_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+enum
+{
+  PROP_ACTIVE_PAD = 1
+};
+
+enum
+{
+  PAD_PROP_RUNNING_TIME = 1
+};
+
+enum
+{
+  /* methods */
+  SIGNAL_BLOCK,
+  SIGNAL_SWITCH,
+  LAST_SIGNAL
+};
+static guint gst_input_selector_signals[LAST_SIGNAL] = { 0 };
+
+static gboolean gst_input_selector_is_active_sinkpad (GstInputSelector * sel,
+    GstPad * pad);
+static GstPad *gst_input_selector_activate_sinkpad (GstInputSelector * sel,
+    GstPad * pad);
+static GstPad *gst_input_selector_get_linked_pad (GstPad * pad,
+    gboolean strict);
+static void gst_input_selector_push_pending_stop (GstInputSelector * self);
+
+#define GST_TYPE_SELECTOR_PAD \
+  (gst_selector_pad_get_type())
+#define GST_SELECTOR_PAD(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SELECTOR_PAD, GstSelectorPad))
+#define GST_SELECTOR_PAD_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SELECTOR_PAD, GstSelectorPadClass))
+#define GST_IS_SELECTOR_PAD(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SELECTOR_PAD))
+#define GST_IS_SELECTOR_PAD_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SELECTOR_PAD))
+#define GST_SELECTOR_PAD_CAST(obj) \
+  ((GstSelectorPad *)(obj))
+
+typedef struct _GstSelectorPad GstSelectorPad;
+typedef struct _GstSelectorPadClass GstSelectorPadClass;
+
+struct _GstSelectorPad
+{
+  GstPad parent;
+
+  gboolean active;
+  gboolean eos;
+  gboolean segment_pending;
+  GstSegment segment;
+};
+
+struct _GstSelectorPadClass
+{
+  GstPadClass parent;
+};
+
+static void gst_selector_pad_class_init (GstSelectorPadClass * klass);
+static void gst_selector_pad_init (GstSelectorPad * pad);
+static void gst_selector_pad_finalize (GObject * object);
+static void gst_selector_pad_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+static GstPadClass *selector_pad_parent_class = NULL;
+
+static gint64 gst_selector_pad_get_running_time (GstSelectorPad * pad);
+static void gst_selector_pad_reset (GstSelectorPad * pad);
+static gboolean gst_selector_pad_event (GstPad * pad, GstEvent * event);
+static GstCaps *gst_selector_pad_getcaps (GstPad * pad);
+static GList *gst_selector_pad_get_linked_pads (GstPad * pad);
+static GstFlowReturn gst_selector_pad_chain (GstPad * pad, GstBuffer * buf);
+static GstFlowReturn gst_selector_pad_bufferalloc (GstPad * pad,
+    guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
+
+static GType
+gst_selector_pad_get_type (void)
+{
+  static GType selector_pad_type = 0;
+
+  if (!selector_pad_type) {
+    static const GTypeInfo selector_pad_info = {
+      sizeof (GstSelectorPadClass),
+      NULL,
+      NULL,
+      (GClassInitFunc) gst_selector_pad_class_init,
+      NULL,
+      NULL,
+      sizeof (GstSelectorPad),
+      0,
+      (GInstanceInitFunc) gst_selector_pad_init,
+    };
+
+    selector_pad_type =
+        g_type_register_static (GST_TYPE_PAD, "GstSelectorPad",
+        &selector_pad_info, 0);
+  }
+  return selector_pad_type;
+}
+
+static void
+gst_selector_pad_class_init (GstSelectorPadClass * klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = (GObjectClass *) klass;
+
+  selector_pad_parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (gst_selector_pad_get_property);
+  g_object_class_install_property (gobject_class, PAD_PROP_RUNNING_TIME,
+      g_param_spec_int64 ("running-time", "Running time",
+          "Running time of stream on pad", 0, G_MAXINT64, 0, G_PARAM_READABLE));
+
+  gobject_class->finalize = gst_selector_pad_finalize;
+}
+
+static void
+gst_selector_pad_init (GstSelectorPad * pad)
+{
+  gst_selector_pad_reset (pad);
+}
+
+static void
+gst_selector_pad_finalize (GObject * object)
+{
+  GstSelectorPad *pad;
+
+  pad = GST_SELECTOR_PAD_CAST (object);
+
+  G_OBJECT_CLASS (selector_pad_parent_class)->finalize (object);
+}
+
+static void
+gst_selector_pad_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstSelectorPad *spad = GST_SELECTOR_PAD_CAST (object);
+
+  switch (prop_id) {
+    case PAD_PROP_RUNNING_TIME:
+      g_value_set_int64 (value, gst_selector_pad_get_running_time (spad));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gint64
+gst_selector_pad_get_running_time (GstSelectorPad * pad)
+{
+  gint64 ret = 0;
+
+  GST_OBJECT_LOCK (pad);
+
+  if (pad->active) {
+    gint64 last_stop = pad->segment.last_stop;
+
+    if (last_stop >= 0)
+      ret = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME,
+          last_stop);
+  }
+
+  GST_OBJECT_UNLOCK (pad);
+
+  GST_DEBUG_OBJECT (pad, "running time: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (ret));
+
+  return ret;
+}
+
+static void
+gst_selector_pad_reset (GstSelectorPad * pad)
+{
+  pad->active = FALSE;
+  pad->eos = FALSE;
+  gst_segment_init (&pad->segment, GST_FORMAT_UNDEFINED);
+}
+
+/* strictly get the linked pad from the sinkpad. If the pad is active we return
+ * the srcpad else we return NULL */
+static GList *
+gst_selector_pad_get_linked_pads (GstPad * pad)
+{
+  GstPad *otherpad;
+
+  otherpad = gst_input_selector_get_linked_pad (pad, TRUE);
+  if (!otherpad)
+    return NULL;
+
+  /* need to drop the ref, internal linked pads is not MT safe */
+  gst_object_unref (otherpad);
+
+  return g_list_append (NULL, otherpad);
+}
+
+static gboolean
+gst_selector_pad_event (GstPad * pad, GstEvent * event)
+{
+  gboolean res = TRUE;
+  gboolean forward = TRUE;
+  GstInputSelector *sel;
+  GstSelectorPad *selpad;
+
+  sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad));
+  selpad = GST_SELECTOR_PAD_CAST (pad);
+
+  /* only forward if we are dealing with the active sinkpad */
+  forward = gst_input_selector_is_active_sinkpad (sel, pad);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_FLUSH_STOP:
+      gst_selector_pad_reset (selpad);
+      break;
+    case GST_EVENT_NEWSEGMENT:
+    {
+      gboolean update;
+      GstFormat format;
+      gdouble rate, arate;
+      gint64 start, stop, time;
+
+      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+          &start, &stop, &time);
+
+      GST_DEBUG_OBJECT (sel,
+          "configured NEWSEGMENT update %d, rate %lf, applied rate %lf, "
+          "format %d, "
+          "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %"
+          G_GINT64_FORMAT, update, rate, arate, format, start, stop, time);
+
+      gst_segment_set_newsegment_full (&selpad->segment, update,
+          rate, arate, format, start, stop, time);
+      /* if we are not going to forward the segment, mark the segment as
+       * pending */
+      if (!forward)
+        selpad->segment_pending = TRUE;
+      break;
+    }
+    case GST_EVENT_EOS:
+      selpad->eos = TRUE;
+      break;
+    default:
+      break;
+  }
+  if (forward)
+    res = gst_pad_push_event (sel->srcpad, event);
+
+  gst_object_unref (sel);
+
+  return res;
+}
+
+static GstCaps *
+gst_selector_pad_getcaps (GstPad * pad)
+{
+  GstInputSelector *sel;
+  GstCaps *caps;
+
+  sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad));
+
+  GST_DEBUG_OBJECT (sel, "Getting caps of srcpad peer");
+  caps = gst_pad_peer_get_caps (sel->srcpad);
+  if (caps == NULL)
+    caps = gst_caps_new_any ();
+
+  gst_object_unref (sel);
+
+  return caps;
+}
+
+static GstFlowReturn
+gst_selector_pad_bufferalloc (GstPad * pad, guint64 offset,
+    guint size, GstCaps * caps, GstBuffer ** buf)
+{
+  GstInputSelector *sel;
+  GstFlowReturn result;
+  GstPad *active_sinkpad;
+
+  sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad));
+
+  active_sinkpad = gst_input_selector_activate_sinkpad (sel, pad);
+
+  /* Fallback allocation for buffers from pads except the selected one */
+  if (pad != active_sinkpad) {
+    GST_DEBUG_OBJECT (sel,
+        "Pad %s:%s is not selected. Performing fallback allocation",
+        GST_DEBUG_PAD_NAME (pad));
+
+    *buf = NULL;
+    result = GST_FLOW_OK;
+  } else {
+    result = gst_pad_alloc_buffer (sel->srcpad, offset, size, caps, buf);
+
+    /* FIXME: HACK. If buffer alloc returns not-linked, perform a fallback
+     * allocation.  This should NOT be necessary, because playbin should
+     * properly block the source pad from running until it's finished hooking 
+     * everything up, but playbin needs refactoring first. */
+    if (result == GST_FLOW_NOT_LINKED) {
+      GST_DEBUG_OBJECT (sel,
+          "No peer pad yet - performing fallback allocation for pad %s:%s",
+          GST_DEBUG_PAD_NAME (pad));
+
+      *buf = NULL;
+      result = GST_FLOW_OK;
+    }
+  }
+
+  gst_object_unref (sel);
+
+  return result;
+}
+
+static gboolean
+gst_input_selector_wait (GstInputSelector * self, GstPad * pad)
+{
+  gboolean flushing;
+
+  GST_OBJECT_LOCK (self);
+
+  while (self->blocked)
+    g_cond_wait (self->blocked_cond, GST_OBJECT_GET_LOCK (self));
+
+  GST_OBJECT_UNLOCK (self);
+
+  GST_OBJECT_LOCK (pad);
+  flushing = GST_PAD_IS_FLUSHING (pad);
+  GST_OBJECT_UNLOCK (pad);
+
+  return flushing;
+}
+
+static GstFlowReturn
+gst_selector_pad_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstInputSelector *sel;
+  GstFlowReturn res;
+  GstPad *active_sinkpad;
+  GstSelectorPad *selpad;
+  GstClockTime end_time, duration;
+  GstSegment *seg;
+
+  sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad));
+  selpad = GST_SELECTOR_PAD_CAST (pad);
+  seg = &selpad->segment;
+
+  if (gst_input_selector_wait (sel, pad))
+    goto ignore;
+
+  active_sinkpad = gst_input_selector_activate_sinkpad (sel, pad);
+
+  end_time = GST_BUFFER_TIMESTAMP (buf);
+  if (GST_CLOCK_TIME_IS_VALID (end_time)) {
+    duration = GST_BUFFER_DURATION (buf);
+    if (GST_CLOCK_TIME_IS_VALID (duration))
+      end_time += duration;
+    GST_DEBUG_OBJECT (sel, "received end time %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (end_time));
+    gst_segment_set_last_stop (seg, seg->format, end_time);
+  }
+
+  /* Ignore buffers from pads except the selected one */
+  if (pad != active_sinkpad)
+    goto ignore;
+
+  gst_input_selector_push_pending_stop (sel);
+
+  /* if we have a pending segment, push it out now */
+  if (selpad->segment_pending) {
+    gst_pad_push_event (sel->srcpad, gst_event_new_new_segment_full (FALSE,
+            seg->rate, seg->applied_rate, seg->format, seg->start, seg->stop,
+            seg->time));
+
+    selpad->segment_pending = FALSE;
+  }
+
+  /* forward */
+  GST_DEBUG_OBJECT (sel, "Forwarding buffer %p from pad %s:%s", buf,
+      GST_DEBUG_PAD_NAME (pad));
+  res = gst_pad_push (sel->srcpad, buf);
+done:
+  gst_object_unref (sel);
+  return res;
+  /* dropped buffers */
+ignore:
+  {
+    GST_DEBUG_OBJECT (sel, "Ignoring buffer %p from pad %s:%s",
+        buf, GST_DEBUG_PAD_NAME (pad));
+    gst_buffer_unref (buf);
+    res = GST_FLOW_OK;
+    goto done;
+  }
+}
+
+static void gst_input_selector_dispose (GObject * object);
+static void gst_input_selector_init (GstInputSelector * sel);
+static void gst_input_selector_base_init (GstInputSelectorClass * klass);
+static void gst_input_selector_class_init (GstInputSelectorClass * klass);
+static void gst_input_selector_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_input_selector_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+static GstPad *gst_input_selector_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * unused);
+static void gst_input_selector_release_pad (GstElement * element, GstPad * pad);
+static GstStateChangeReturn gst_input_selector_change_state (GstElement *
+    element, GstStateChange transition);
+static GList *gst_input_selector_get_linked_pads (GstPad * pad);
+static GstCaps *gst_input_selector_getcaps (GstPad * pad);
+static gint64 gst_input_selector_block (GstInputSelector * self);
+static void gst_input_selector_switch (GstInputSelector * self,
+    const gchar * pad_name, gint64 stop_time, gint64 start_time);
+
+static GstElementClass *parent_class = NULL;
+
+GType
+gst_input_selector_get_type (void)
+{
+  static GType input_selector_type = 0;
+
+  if (!input_selector_type) {
+    static const GTypeInfo input_selector_info = {
+      sizeof (GstInputSelectorClass),
+      (GBaseInitFunc) gst_input_selector_base_init,
+      NULL,
+      (GClassInitFunc) gst_input_selector_class_init,
+      NULL,
+      NULL,
+      sizeof (GstInputSelector),
+      0,
+      (GInstanceInitFunc) gst_input_selector_init,
+    };
+    input_selector_type =
+        g_type_register_static (GST_TYPE_ELEMENT,
+        "GstInputSelector", &input_selector_info, 0);
+    GST_DEBUG_CATEGORY_INIT (input_selector_debug,
+        "input-selector", 0, "An input stream selector element");
+  }
+
+  return input_selector_type;
+}
+
+static void
+gst_input_selector_base_init (GstInputSelectorClass * klass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_set_details (element_class, &gst_input_selector_details);
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_input_selector_sink_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_input_selector_src_factory));
+}
+
+static void
+gst_input_selector_class_init (GstInputSelectorClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+  gobject_class->set_property =
+      GST_DEBUG_FUNCPTR (gst_input_selector_set_property);
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (gst_input_selector_get_property);
+  g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
+      g_param_spec_string ("active-pad", "Active pad",
+          "Name of the currently" " active sink pad", NULL, G_PARAM_READWRITE));
+  gobject_class->dispose = gst_input_selector_dispose;
+  gstelement_class->request_new_pad = gst_input_selector_request_new_pad;
+  gstelement_class->release_pad = gst_input_selector_release_pad;
+  gstelement_class->change_state = gst_input_selector_change_state;
+
+  /**
+   * GstInputSelector::block:
+   * @inputselector: the #GstInputSelector
+   *
+   * Block all sink pads in preparation for a switch. Returns the stop time of
+   * the current switch segment, as a running time, or 0 if there is no current
+   * active pad or the current active pad never received data.
+   */
+  gst_input_selector_signals[SIGNAL_BLOCK] =
+      g_signal_new ("block", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+      G_STRUCT_OFFSET (GstInputSelectorClass, block),
+      NULL, NULL, gst_selector_marshal_INT64__VOID, G_TYPE_INT64, 0);
+  /**
+   * GstInputSelector::switch:
+   * @inputselector: the #GstInputSelector
+   * @pad:            name of pad to switch to
+   * @stop_time:      running time at which to close the previous segment, or -1
+   *                  to use the running time of the previously active sink pad
+   * @start_time:     running time at which to start the new segment, or -1 to
+   *                  use the running time of the newly active sink pad
+   *
+   * Switch to a new feed. The segment opened by the previously active pad, if
+   * any, will be closed, and a new segment opened before data flows again.
+   *
+   * This signal must be emitted when the element has been blocked via the <link
+   * linkend="GstInputSelector-block">block</link> signal.
+   *
+   * If you have a stream with only one switch element, such as an audio-only
+   * stream, a stream switch should be performed by first emitting the block
+   * signal, and then emitting the switch signal with -1 for the stop and start
+   * time values.
+   *
+   * The intention of the @stop_time and @start_time arguments is to allow
+   * multiple switch elements to switch and maintain stream synchronization.
+   * When switching a stream with multiple feeds, you will need as many switch
+   * elements as you have feeds. For example, a feed with audio and video will
+   * have one switch element between the audio feeds and one for video.
+   *
+   * A switch over multiple switch elements should be performed as follows:
+   * First, emit the <link linkend="GstInputSelector-block">block</link>
+   * signal, collecting the returned values. The maximum running time returned
+   * by block should then be used as the time at which to close the previous
+   * segment.
+   *
+   * Then, query the running times of the new audio and video pads that you will
+   * switch to. Naturally, these pads are on separate switch elements. Take the
+   * minimum running time for those streams and use it for the time at which to
+   * open the new segment.
+   *
+   * If @pad is the same as the current active pad, the element will cancel any
+   * previous block without adjusting segments.
+   */
+  gst_input_selector_signals[SIGNAL_SWITCH] =
+      g_signal_new ("switch", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+      G_STRUCT_OFFSET (GstInputSelectorClass, switch_),
+      NULL, NULL, gst_selector_marshal_VOID__STRING_INT64_INT64,
+      G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT64, G_TYPE_INT64);
+
+  klass->block = GST_DEBUG_FUNCPTR (gst_input_selector_block);
+  klass->switch_ = GST_DEBUG_FUNCPTR (gst_input_selector_switch);
+}
+
+static void
+gst_input_selector_init (GstInputSelector * sel)
+{
+  sel->srcpad = gst_pad_new ("src", GST_PAD_SRC);
+  gst_pad_set_internal_link_function (sel->srcpad,
+      GST_DEBUG_FUNCPTR (gst_input_selector_get_linked_pads));
+  gst_pad_set_getcaps_function (sel->srcpad,
+      GST_DEBUG_FUNCPTR (gst_input_selector_getcaps));
+  gst_element_add_pad (GST_ELEMENT (sel), sel->srcpad);
+  /* sinkpad management */
+  sel->active_sinkpad = NULL;
+  sel->nb_sinkpads = 0;
+  gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED);
+
+  sel->blocked_cond = g_cond_new ();
+  sel->blocked = FALSE;
+}
+
+static void
+gst_input_selector_dispose (GObject * object)
+{
+  GstInputSelector *sel = GST_INPUT_SELECTOR (object);
+
+  if (sel->active_sinkpad) {
+    gst_object_unref (sel->active_sinkpad);
+    sel->active_sinkpad = NULL;
+  }
+
+  if (sel->blocked_cond) {
+    g_cond_free (sel->blocked_cond);
+    sel->blocked_cond = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+/* Solve the following equation for B.timestamp, and set that as the segment
+ * stop:
+ * B.running_time = (B.timestamp - NS.start) / NS.abs_rate + NS.accum
+ */
+static gint64
+gst_segment_get_timestamp (GstSegment * segment, gint64 running_time)
+{
+  return (running_time - segment->accum) * segment->abs_rate + segment->start;
+}
+
+static void
+gst_segment_set_stop (GstSegment * segment, gint64 running_time)
+{
+  segment->stop = gst_segment_get_timestamp (segment, running_time);
+  segment->last_stop = -1;
+}
+
+static void
+gst_segment_set_start (GstSegment * segment, gint64 running_time)
+{
+  segment->start = gst_segment_get_timestamp (segment, running_time);
+}
+
+static void
+gst_input_selector_set_active_pad (GstInputSelector * self,
+    const gchar * pad_name, gint64 stop_time, gint64 start_time)
+{
+  GstPad *pad;
+  GstSelectorPad *old, *new;
+  GstPad **active_pad_p;
+
+  if (strcmp (pad_name, "") != 0)
+    pad = gst_element_get_pad (GST_ELEMENT (self), pad_name);
+  else
+    pad = NULL;
+
+  GST_OBJECT_LOCK (self);
+
+  if (pad == self->active_sinkpad)
+    goto done;
+
+  old = GST_SELECTOR_PAD_CAST (self->active_sinkpad);
+  new = GST_SELECTOR_PAD_CAST (pad);
+
+  if (old && old->active && !self->pending_stop && stop_time >= 0) {
+    /* schedule a last_stop update if one isn't already scheduled, and a
+       segment has been pushed before. */
+    memcpy (&self->pending_stop_segment, &old->segment,
+        sizeof (self->pending_stop_segment));
+    gst_segment_set_stop (&self->pending_stop_segment, stop_time);
+    self->pending_stop = TRUE;
+  }
+
+  if (new && new->active && start_time >= 0) {
+    /* schedule a new segment push */
+    gst_segment_set_start (&new->segment, start_time);
+    new->segment_pending = TRUE;
+  }
+
+  active_pad_p = &self->active_sinkpad;
+  gst_object_replace ((GstObject **) active_pad_p, GST_OBJECT_CAST (pad));
+  GST_DEBUG_OBJECT (self, "New active pad is %" GST_PTR_FORMAT,
+      self->active_sinkpad);
+
+done:
+  GST_OBJECT_UNLOCK (self);
+
+  if (pad)
+    gst_object_unref (pad);
+}
+
+
+static void
+gst_input_selector_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstInputSelector *sel = GST_INPUT_SELECTOR (object);
+
+  switch (prop_id) {
+    case PROP_ACTIVE_PAD:
+      gst_input_selector_set_active_pad (sel,
+          g_value_get_string (value), GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_input_selector_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstInputSelector *sel = GST_INPUT_SELECTOR (object);
+
+  switch (prop_id) {
+    case PROP_ACTIVE_PAD:{
+      GST_OBJECT_LOCK (object);
+      if (sel->active_sinkpad != NULL) {
+        g_value_take_string (value, gst_pad_get_name (sel->active_sinkpad));
+      } else {
+        g_value_set_string (value, "");
+      }
+      GST_OBJECT_UNLOCK (object);
+      break;
+    }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstPad *
+gst_input_selector_get_linked_pad (GstPad * pad, gboolean strict)
+{
+  GstInputSelector *sel;
+  GstPad *otherpad = NULL;
+
+  sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad));
+  GST_OBJECT_LOCK (sel);
+  if (pad == sel->srcpad)
+    otherpad = sel->active_sinkpad;
+  else if (pad == sel->active_sinkpad || !strict)
+    otherpad = sel->srcpad;
+  if (otherpad)
+    gst_object_ref (otherpad);
+  GST_OBJECT_UNLOCK (sel);
+  gst_object_unref (sel);
+  return otherpad;
+}
+
+static GstCaps *
+gst_input_selector_getcaps (GstPad * pad)
+{
+  GstPad *otherpad;
+  GstObject *parent;
+  GstCaps *caps;
+
+  otherpad = gst_input_selector_get_linked_pad (pad, FALSE);
+  parent = gst_object_get_parent (GST_OBJECT (pad));
+  if (!otherpad) {
+    GST_DEBUG_OBJECT (parent,
+        "Pad %s:%s not linked, returning ANY", GST_DEBUG_PAD_NAME (pad));
+    caps = gst_caps_new_any ();
+  } else {
+    GST_DEBUG_OBJECT (parent,
+        "Pad %s:%s is linked (to %s:%s), returning peer caps",
+        GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (otherpad));
+    /* if the peer has caps, use those. If the pad is not linked, this function
+     * returns NULL and we return ANY */
+    if (!(caps = gst_pad_peer_get_caps (otherpad)))
+      caps = gst_caps_new_any ();
+    gst_object_unref (otherpad);
+  }
+
+  gst_object_unref (parent);
+  return caps;
+}
+
+/* check if the pad is the active sinkpad */
+static gboolean
+gst_input_selector_is_active_sinkpad (GstInputSelector * sel, GstPad * pad)
+{
+  GstSelectorPad *selpad;
+  gboolean res;
+
+  selpad = GST_SELECTOR_PAD_CAST (pad);
+
+  GST_OBJECT_LOCK (sel);
+  res = (pad == sel->active_sinkpad);
+  GST_OBJECT_UNLOCK (sel);
+
+  return res;
+}
+
+/* Get or create the active sinkpad */
+static GstPad *
+gst_input_selector_activate_sinkpad (GstInputSelector * sel, GstPad * pad)
+{
+  GstPad *active_sinkpad;
+  GstSelectorPad *selpad;
+
+  selpad = GST_SELECTOR_PAD_CAST (pad);
+
+  GST_OBJECT_LOCK (sel);
+  selpad->active = TRUE;
+  active_sinkpad = sel->active_sinkpad;
+  if (active_sinkpad == NULL) {
+    /* first pad we get an alloc on becomes the activated pad by default */
+    active_sinkpad = sel->active_sinkpad = gst_object_ref (pad);
+    GST_DEBUG_OBJECT (sel, "Activating pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+  }
+  GST_OBJECT_UNLOCK (sel);
+
+  return active_sinkpad;
+}
+
+static GList *
+gst_input_selector_get_linked_pads (GstPad * pad)
+{
+  GstPad *otherpad;
+
+  otherpad = gst_input_selector_get_linked_pad (pad, TRUE);
+  if (!otherpad)
+    return NULL;
+  /* need to drop the ref, internal linked pads is not MT safe */
+  gst_object_unref (otherpad);
+  return g_list_append (NULL, otherpad);
+}
+
+static GstPad *
+gst_input_selector_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * unused)
+{
+  GstInputSelector *sel;
+  gchar *name = NULL;
+  GstPad *sinkpad = NULL;
+
+  sel = GST_INPUT_SELECTOR (element);
+  g_return_val_if_fail (templ->direction == GST_PAD_SINK, NULL);
+  GST_LOG_OBJECT (sel, "Creating new pad %d", sel->nb_sinkpads);
+  GST_OBJECT_LOCK (sel);
+  name = g_strdup_printf ("sink%d", sel->nb_sinkpads++);
+  sinkpad = g_object_new (GST_TYPE_SELECTOR_PAD,
+      "name", name, "direction", templ->direction, "template", templ, NULL);
+  g_free (name);
+  GST_OBJECT_UNLOCK (sel);
+
+  gst_pad_set_event_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_selector_pad_event));
+  gst_pad_set_getcaps_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_selector_pad_getcaps));
+  gst_pad_set_chain_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_selector_pad_chain));
+  gst_pad_set_internal_link_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_selector_pad_get_linked_pads));
+  gst_pad_set_bufferalloc_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_selector_pad_bufferalloc));
+
+  gst_pad_set_active (sinkpad, TRUE);
+  gst_element_add_pad (GST_ELEMENT (sel), sinkpad);
+  return sinkpad;
+}
+
+static void
+gst_input_selector_release_pad (GstElement * element, GstPad * pad)
+{
+  GstInputSelector *sel;
+
+  sel = GST_INPUT_SELECTOR (element);
+  GST_LOG_OBJECT (sel, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+  GST_OBJECT_LOCK (sel);
+  /* if the pad was the active pad, makes us select a new one */
+  if (sel->active_sinkpad == pad) {
+    GST_DEBUG_OBJECT (sel, "Deactivating pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+    sel->active_sinkpad = NULL;
+  }
+  GST_OBJECT_UNLOCK (sel);
+
+  gst_pad_set_active (pad, FALSE);
+  gst_element_remove_pad (GST_ELEMENT (sel), pad);
+}
+
+static GstStateChangeReturn
+gst_input_selector_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstInputSelector *self = GST_INPUT_SELECTOR (element);
+  GstStateChangeReturn result;
+
+  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
+    GST_OBJECT_LOCK (self);
+    self->blocked = FALSE;
+    g_cond_broadcast (self->blocked_cond);
+    GST_OBJECT_UNLOCK (self);
+  }
+
+  return result;
+}
+
+static gint64
+gst_input_selector_block (GstInputSelector * self)
+{
+  gint64 ret = 0;
+  GstSelectorPad *spad;
+
+  GST_OBJECT_LOCK (self);
+
+  if (self->blocked)
+    GST_WARNING_OBJECT (self, "switch already blocked");
+
+  self->blocked = TRUE;
+  spad = GST_SELECTOR_PAD_CAST (self->active_sinkpad);
+
+  if (self->active_sinkpad)
+    ret = gst_selector_pad_get_running_time (spad);
+  else
+    GST_DEBUG_OBJECT (self, "no active pad while blocking");
+
+  GST_OBJECT_UNLOCK (self);
+
+  return ret;
+}
+
+static void
+gst_input_selector_push_pending_stop (GstInputSelector * self)
+{
+  GstEvent *event = NULL;
+
+  GST_OBJECT_LOCK (self);
+
+  if (G_UNLIKELY (self->pending_stop)) {
+    GstSegment *seg = &self->pending_stop_segment;
+
+    event = gst_event_new_new_segment_full (TRUE, seg->rate,
+        seg->applied_rate, seg->format, seg->start, seg->stop, seg->stop);
+
+    self->pending_stop = FALSE;
+  }
+
+  GST_OBJECT_UNLOCK (self);
+
+  if (event)
+    gst_pad_push_event (self->srcpad, event);
+}
+
+/* stop_time and start_time are running times */
+static void
+gst_input_selector_switch (GstInputSelector * self, const gchar * pad_name,
+    gint64 stop_time, gint64 start_time)
+{
+  g_return_if_fail (self->blocked == TRUE);
+
+  gst_input_selector_set_active_pad (self, pad_name, stop_time, start_time);
+
+  GST_OBJECT_LOCK (self);
+  self->blocked = FALSE;
+  g_cond_broadcast (self->blocked_cond);
+  GST_OBJECT_UNLOCK (self);
+}
diff --git a/plugins/elements/gstinputselector.h b/plugins/elements/gstinputselector.h
new file mode 100644 (file)
index 0000000..bb1a28d
--- /dev/null
@@ -0,0 +1,71 @@
+/* GStreamer
+ * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
+ * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
+ * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GST_INPUT_SELECTOR_H__
+#define __GST_INPUT_SELECTOR_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_INPUT_SELECTOR \
+  (gst_input_selector_get_type())
+#define GST_INPUT_SELECTOR(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_INPUT_SELECTOR, GstInputSelector))
+#define GST_INPUT_SELECTOR_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_INPUT_SELECTOR, GstInputSelectorClass))
+#define GST_IS_INPUT_SELECTOR(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_INPUT_SELECTOR))
+#define GST_IS_INPUT_SELECTOR_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_INPUT_SELECTOR))
+
+typedef struct _GstInputSelector GstInputSelector;
+typedef struct _GstInputSelectorClass GstInputSelectorClass;
+
+struct _GstInputSelector {
+  GstElement element;
+
+  GstPad *srcpad;
+
+  GstPad *active_sinkpad;
+  guint nb_sinkpads;
+
+  GstSegment segment;
+
+  GCond *blocked_cond;
+  gboolean blocked;
+  gboolean pending_stop;
+  GstSegment pending_stop_segment;
+};
+
+struct _GstInputSelectorClass {
+  GstElementClass parent_class;
+
+  gint64 (*block)      (GstInputSelector *self);
+  void (*switch_)      (GstInputSelector *self, const gchar *pad_name,
+                         gint64 stop_time, gint64 start_time);
+};
+
+GType gst_input_selector_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_INPUT_SELECTOR_H__ */
diff --git a/plugins/elements/gstoutputselector.c b/plugins/elements/gstoutputselector.c
new file mode 100644 (file)
index 0000000..1d1dc05
--- /dev/null
@@ -0,0 +1,448 @@
+/* GStreamer
+ * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-output-selector
+ * @short_description: 1-to-N stream selectoring
+ * @see_also: #GstTee, #GstInputSelector
+ *
+ * Direct input stream to one out of N output pads.
+ */
+
+/* FIXME: By default basesinks require some prerolled data before changing
+   to playing state. Also pipeline with output-selector connected to multiple 
+   sink elements won't change to playing until all sink elements have received
+   the preroll data. Currently this can be worked around using live source element
+   and and exporting GST_COMPAT="no-live-preroll".
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "gstoutputselector.h"
+
+GST_DEBUG_CATEGORY_STATIC (output_selector_debug);
+#define GST_CAT_DEFAULT output_selector_debug
+
+static const GstElementDetails gst_output_selector_details =
+GST_ELEMENT_DETAILS ("Output selector",
+    "Generic",
+    "1-to-N output stream selectoring",
+    "Stefan Kost <stefan.kost@nokia.com>");
+
+static GstStaticPadTemplate gst_output_selector_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate gst_output_selector_src_factory =
+GST_STATIC_PAD_TEMPLATE ("src%d",
+    GST_PAD_SRC,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+enum
+{
+  PROP_ACTIVE_PAD = 1,
+  PROP_RESEND_LATEST
+};
+
+static void gst_output_selector_dispose (GObject * object);
+static void gst_output_selector_init (GstOutputSelector * sel);
+static void gst_output_selector_base_init (GstOutputSelectorClass * klass);
+static void gst_output_selector_class_init (GstOutputSelectorClass * klass);
+static void gst_output_selector_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_output_selector_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+static GstPad *gst_output_selector_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * unused);
+static void gst_output_selector_release_pad (GstElement * element,
+    GstPad * pad);
+static GstFlowReturn gst_output_selector_chain (GstPad * pad, GstBuffer * buf);
+static GstStateChangeReturn gst_output_selector_change_state (GstElement *
+    element, GstStateChange transition);
+static gboolean gst_output_selector_handle_sink_event (GstPad * pad,
+    GstEvent * event);
+
+static GstElementClass *parent_class = NULL;
+
+GType
+gst_output_selector_get_type (void)
+{
+  static GType output_selector_type = 0;
+
+  if (!output_selector_type) {
+    static const GTypeInfo output_selector_info = {
+      sizeof (GstOutputSelectorClass),
+      (GBaseInitFunc) gst_output_selector_base_init,
+      NULL,
+      (GClassInitFunc) gst_output_selector_class_init,
+      NULL,
+      NULL,
+      sizeof (GstOutputSelector),
+      0,
+      (GInstanceInitFunc) gst_output_selector_init,
+    };
+    output_selector_type =
+        g_type_register_static (GST_TYPE_ELEMENT,
+        "GstOutputSelector", &output_selector_info, 0);
+    GST_DEBUG_CATEGORY_INIT (output_selector_debug,
+        "output-selector", 0, "An output stream selector element");
+  }
+
+  return output_selector_type;
+}
+
+static void
+gst_output_selector_base_init (GstOutputSelectorClass * klass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_set_details (element_class, &gst_output_selector_details);
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_output_selector_sink_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&gst_output_selector_src_factory));
+}
+
+static void
+gst_output_selector_class_init (GstOutputSelectorClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+  gobject_class->set_property =
+      GST_DEBUG_FUNCPTR (gst_output_selector_set_property);
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (gst_output_selector_get_property);
+  g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
+      g_param_spec_string ("active-pad", "Active pad",
+          "Name of the currently active src pad", NULL, G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_RESEND_LATEST,
+      g_param_spec_boolean ("resend-latest", "Resend latest buffer",
+          "Resend latest buffer after a switch to a new pad", FALSE,
+          G_PARAM_READWRITE));
+  gobject_class->dispose = gst_output_selector_dispose;
+  gstelement_class->request_new_pad =
+      GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
+  gstelement_class->release_pad =
+      GST_DEBUG_FUNCPTR (gst_output_selector_release_pad);
+  gstelement_class->change_state = gst_output_selector_change_state;
+
+}
+
+static void
+gst_output_selector_init (GstOutputSelector * sel)
+{
+  sel->sinkpad =
+      gst_pad_new_from_static_template (&gst_output_selector_sink_factory,
+      "sink");
+  gst_pad_set_chain_function (sel->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_output_selector_chain));
+  gst_pad_set_event_function (sel->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_output_selector_handle_sink_event));
+
+  gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad);
+
+  /*
+     gst_pad_set_bufferalloc_function (sel->sinkpad,
+     GST_DEBUG_FUNCPTR (gst_output_selector_bufferalloc));
+     gst_pad_set_setcaps_function (sel->sinkpad,
+     GST_DEBUG_FUNCPTR (gst_pad_proxy_setcaps));
+     gst_pad_set_getcaps_function (sel->sinkpad,
+     GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
+   */
+  /* srcpad management */
+  sel->active_srcpad = NULL;
+  sel->nb_srcpads = 0;
+  gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED);
+  sel->pending_srcpad = NULL;
+
+  sel->resend_latest = FALSE;
+  sel->latest_buffer = NULL;
+}
+
+static void
+gst_output_selector_dispose (GObject * object)
+{
+  GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object);
+
+  if (osel->pending_srcpad != NULL) {
+    gst_object_unref (osel->pending_srcpad);
+    osel->pending_srcpad = NULL;
+  }
+  if (osel->latest_buffer != NULL) {
+    gst_buffer_unref (osel->latest_buffer);
+    osel->latest_buffer = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_output_selector_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
+
+  switch (prop_id) {
+    case PROP_ACTIVE_PAD:{
+      GstPad *next_pad =
+          gst_element_get_static_pad (GST_ELEMENT (sel),
+          g_value_get_string (value));
+      if (next_pad && (next_pad != sel->active_srcpad)) {
+        /* switch to new srcpad in next chain run */
+        if (sel->pending_srcpad != NULL) {
+          GST_INFO ("replacing pending switch");
+          gst_object_unref (sel->pending_srcpad);
+        }
+        sel->pending_srcpad = next_pad;
+      } else {
+        GST_WARNING ("setting active pad failed");
+      }
+      break;
+    }
+    case PROP_RESEND_LATEST:{
+      sel->resend_latest = g_value_get_boolean (value);
+      break;
+    }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_output_selector_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
+
+  switch (prop_id) {
+    case PROP_ACTIVE_PAD:{
+      GST_OBJECT_LOCK (object);
+      if (sel->active_srcpad != NULL) {
+        g_value_take_string (value, gst_pad_get_name (sel->active_srcpad));
+      } else {
+        g_value_set_string (value, "");
+      }
+      GST_OBJECT_UNLOCK (object);
+      break;
+    }
+    case PROP_RESEND_LATEST:{
+      GST_OBJECT_LOCK (object);
+      g_value_set_boolean (value, sel->resend_latest);
+      GST_OBJECT_UNLOCK (object);
+      break;
+    }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstPad *
+gst_output_selector_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * name)
+{
+  gchar *padname;
+  GstPad *srcpad;
+  GstOutputSelector *osel;
+
+  osel = GST_OUTPUT_SELECTOR (element);
+
+  GST_DEBUG_OBJECT (osel, "requesting pad");
+
+  GST_OBJECT_LOCK (osel);
+  padname = g_strdup_printf ("src%d", osel->nb_srcpads++);
+  srcpad = gst_pad_new_from_template (templ, padname);
+  GST_OBJECT_UNLOCK (osel);
+
+  gst_pad_set_active (srcpad, TRUE);
+  gst_element_add_pad (GST_ELEMENT (osel), srcpad);
+
+  /* Set the first requested src pad as active by default */
+  if (osel->active_srcpad == NULL) {
+    osel->active_srcpad = srcpad;
+  }
+  g_free (padname);
+
+  return srcpad;
+}
+
+static void
+gst_output_selector_release_pad (GstElement * element, GstPad * pad)
+{
+  GstOutputSelector *osel;
+
+  osel = GST_OUTPUT_SELECTOR (element);
+
+  GST_DEBUG_OBJECT (osel, "releasing pad");
+
+  gst_pad_set_active (pad, FALSE);
+
+  gst_element_remove_pad (GST_ELEMENT_CAST (osel), pad);
+}
+
+static gboolean
+gst_output_selector_switch (GstOutputSelector * osel)
+{
+  gboolean res = TRUE;
+  GstEvent *ev = NULL;
+  GstSegment *seg = NULL;
+  gint64 start = 0, position = 0;
+
+  GST_INFO ("switching to pad %" GST_PTR_FORMAT, osel->pending_srcpad);
+
+  if (gst_pad_is_linked (osel->pending_srcpad)) {
+    /* Send NEWSEGMENT to the pad we are going to switch to */
+    seg = &osel->segment;
+    /* If resending then mark newsegment start and position accordingly */
+    if (osel->resend_latest && osel->latest_buffer &&
+        GST_BUFFER_TIMESTAMP_IS_VALID (osel->latest_buffer)) {
+      start = position = GST_BUFFER_TIMESTAMP (osel->latest_buffer);
+    } else {
+      start = position = seg->last_stop;
+    }
+    ev = gst_event_new_new_segment (TRUE, seg->rate,
+        seg->format, start, seg->stop, position);
+    if (!gst_pad_push_event (osel->pending_srcpad, ev)) {
+      GST_WARNING ("newsegment handling failed in %" GST_PTR_FORMAT,
+          osel->pending_srcpad);
+    }
+
+    /* Resend latest buffer to newly switched pad */
+    if (osel->resend_latest && osel->latest_buffer) {
+      GST_INFO ("resending latest buffer");
+      gst_pad_push (osel->pending_srcpad, osel->latest_buffer);
+      osel->latest_buffer = NULL;
+    }
+
+    /* Switch */
+    osel->active_srcpad = osel->pending_srcpad;
+  } else {
+    GST_WARNING ("switch failed, pad not linked");
+    res = FALSE;
+  }
+
+  gst_object_unref (osel->pending_srcpad);
+  osel->pending_srcpad = NULL;
+
+  return res;
+}
+
+static GstFlowReturn
+gst_output_selector_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstFlowReturn res;
+  GstOutputSelector *osel;
+  GstClockTime last_stop, duration;
+
+  osel = GST_OUTPUT_SELECTOR (gst_pad_get_parent (pad));
+
+  if (osel->pending_srcpad) {
+    /* Do the switch */
+    gst_output_selector_switch (osel);
+  }
+
+  /* Keep reference to latest buffer to resend it after switch */
+  if (osel->resend_latest) {
+    if (osel->latest_buffer)
+      gst_buffer_unref (osel->latest_buffer);
+    osel->latest_buffer = gst_buffer_ref (buf);
+  }
+
+  /* Keep track of last stop and use it in NEWSEGMENT start after 
+     switching to a new src pad */
+  last_stop = GST_BUFFER_TIMESTAMP (buf);
+  if (GST_CLOCK_TIME_IS_VALID (last_stop)) {
+    duration = GST_BUFFER_DURATION (buf);
+    if (GST_CLOCK_TIME_IS_VALID (duration)) {
+      last_stop += duration;
+    }
+    GST_LOG ("setting last stop %" GST_TIME_FORMAT, GST_TIME_ARGS (last_stop));
+    gst_segment_set_last_stop (&osel->segment, osel->segment.format, last_stop);
+  }
+
+  GST_LOG ("pushing buffer to %" GST_PTR_FORMAT, osel->active_srcpad);
+  res = gst_pad_push (osel->active_srcpad, buf);
+  gst_object_unref (osel);
+
+  return res;
+}
+
+static GstStateChangeReturn
+gst_output_selector_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+}
+
+static gboolean
+gst_output_selector_handle_sink_event (GstPad * pad, GstEvent * event)
+{
+  gboolean res = TRUE;
+  GstOutputSelector *sel;
+
+  sel = GST_OUTPUT_SELECTOR (gst_pad_get_parent (pad));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:
+    {
+      gboolean update;
+      GstFormat format;
+      gdouble rate, arate;
+      gint64 start, stop, time;
+
+      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+          &start, &stop, &time);
+
+      GST_DEBUG ("configured NEWSEGMENT update %d, rate %lf, applied rate %lf, "
+          "format %d, "
+          "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %"
+          G_GINT64_FORMAT, update, rate, arate, format, start, stop, time);
+
+      gst_segment_set_newsegment_full (&sel->segment, update,
+          rate, arate, format, start, stop, time);
+
+      /* Send newsegment to all src pads */
+      gst_pad_event_default (pad, event);
+
+      break;
+    }
+    case GST_EVENT_EOS:
+      /* Send eos to all src pads */
+      gst_pad_event_default (pad, event);
+      break;
+    default:
+      /* Send other events to active src pad */
+      res = gst_pad_push_event (sel->active_srcpad, event);
+      break;
+  }
+
+  gst_object_unref (sel);
+
+  return res;
+}
diff --git a/plugins/elements/gstoutputselector.h b/plugins/elements/gstoutputselector.h
new file mode 100644 (file)
index 0000000..5389d47
--- /dev/null
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GST_OUTPUT_SELECTOR_H__
+#define __GST_OUTPUT_SELECTOR_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_OUTPUT_SELECTOR \
+  (gst_output_selector_get_type())
+#define GST_OUTPUT_SELECTOR(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_OUTPUT_SELECTOR, GstOutputSelector))
+#define GST_OUTPUT_SELECTOR_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_OUTPUT_SELECTOR, GstOutputSelectorClass))
+#define GST_IS_OUTPUT_SELECTOR(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_OUTPUT_SELECTOR))
+#define GST_IS_OUTPUT_SELECTOR_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_OUTPUT_SELECTOR))
+
+typedef struct _GstOutputSelector GstOutputSelector;
+typedef struct _GstOutputSelectorClass GstOutputSelectorClass;
+
+struct _GstOutputSelector {
+  GstElement element;
+
+  GstPad *sinkpad;
+
+  GstPad *active_srcpad;
+  GstPad *pending_srcpad;
+  guint nb_srcpads;
+
+  GstSegment segment;
+
+  /* resend latest buffer after switch */
+  gboolean resend_latest;
+  GstBuffer *latest_buffer;
+
+};
+
+struct _GstOutputSelectorClass {
+  GstElementClass parent_class;
+};
+
+GType gst_output_selector_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_OUTPUT_SELECTOR_H__ */
diff --git a/plugins/elements/gstselector-marshal.list b/plugins/elements/gstselector-marshal.list
new file mode 100644 (file)
index 0000000..97c741c
--- /dev/null
@@ -0,0 +1,2 @@
+INT64:VOID
+VOID:STRING,INT64,INT64
diff --git a/plugins/elements/gstselector.c b/plugins/elements/gstselector.c
new file mode 100644 (file)
index 0000000..008e457
--- /dev/null
@@ -0,0 +1,44 @@
+/* GStreamer
+ * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+
+#include "gstinputselector.h"
+#include "gstoutputselector.h"
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+
+  return gst_element_register (plugin, "input-selector",
+      GST_RANK_NONE, GST_TYPE_INPUT_SELECTOR) &&
+      gst_element_register (plugin, "output-selector",
+      GST_RANK_NONE, GST_TYPE_OUTPUT_SELECTOR);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "selector",
+    "input/output stream selector elements",
+    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/tests/icles/output-selector-test.c b/tests/icles/output-selector-test.c
new file mode 100755 (executable)
index 0000000..304295c
--- /dev/null
@@ -0,0 +1,149 @@
+#include <gst/gst.h>
+
+//[.. my_bus_callback goes here ..]
+
+static GMainLoop *loop;
+
+static gboolean
+my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
+{
+  g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err;
+      gchar *debug;
+
+      gst_message_parse_error (message, &err, &debug);
+      g_print ("Error: %s\n", err->message);
+      g_error_free (err);
+      g_free (debug);
+
+      g_main_loop_quit (loop);
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      /* end-of-stream */
+      g_main_loop_quit (loop);
+      break;
+    default:
+      /* unhandled message */
+      break;
+  }
+  /* we want to be notified again the next time there is a message
+   * on the bus, so returning TRUE (FALSE means we want to stop watching
+   * for messages on the bus and our callback should not be called again)
+   */
+  return TRUE;
+}
+
+static gboolean
+switch_cb (gpointer user_data)
+{
+
+  GstElement *sel = GST_ELEMENT (user_data);
+  gchar *old_pad_name, *new_pad_name;
+
+  g_object_get (G_OBJECT (sel), "active-pad", &old_pad_name, NULL);
+
+  if (g_str_equal (old_pad_name, "src0"))
+    new_pad_name = "src1";
+  else
+    new_pad_name = "src0";
+
+  g_object_set (G_OBJECT (sel), "active-pad", new_pad_name, NULL);
+
+  g_print ("switched from %s to %s\n", old_pad_name, new_pad_name);
+  g_free (old_pad_name);
+
+  return TRUE;
+
+}
+
+gint
+main (gint argc, gchar * argv[])
+{
+  GstElement *pipeline, *src, *toverlay, *osel, *sink1, *sink2, *convert;
+  GstPad *osel_src1, *osel_src2, *sinkpad;
+  GstBus *bus;
+
+  /* init GStreamer */
+  gst_init (&argc, &argv);
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* create elements */
+  pipeline = gst_element_factory_make ("pipeline", "pipeline");
+  src = gst_element_factory_make ("videotestsrc", "src");
+  toverlay = gst_element_factory_make ("timeoverlay", "timeoverlay");
+  osel = gst_element_factory_make ("output-selector", "osel");
+  convert = gst_element_factory_make ("ffmpegcolorspace", "convert");
+  sink1 = gst_element_factory_make ("xvimagesink", "sink1");
+  sink2 = gst_element_factory_make ("ximagesink", "sink2");
+
+  if (!pipeline || !src || !toverlay || !osel || !convert || !sink1 || !sink2) {
+    g_print ("missing element\n");
+    return -1;
+  }
+
+  /* add them to bin */
+  gst_bin_add_many (GST_BIN (pipeline), src, toverlay, osel, convert, sink1,
+      sink2, NULL);
+
+  /* set properties */
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  g_object_set (G_OBJECT (src), "do-timestamp", TRUE, NULL);
+  g_object_set (G_OBJECT (src), "num-buffers", 500, NULL);
+  g_object_set (G_OBJECT (sink1), "sync", FALSE, NULL);
+  g_object_set (G_OBJECT (sink2), "sync", FALSE, NULL);
+  g_object_set (G_OBJECT (osel), "resend-latest", TRUE, NULL);
+
+  /* link src ! timeoverlay ! osel */
+  if (!gst_element_link_many (src, toverlay, osel, NULL)) {
+    g_print ("linking failed\n");
+    return -1;
+  }
+
+  /* link output 1 */
+  sinkpad = gst_element_get_static_pad (sink1, "sink");
+  osel_src1 = gst_element_get_request_pad (osel, "src%d");
+  if (gst_pad_link (osel_src1, sinkpad) != GST_PAD_LINK_OK) {
+    g_print ("linking output 1 failed\n");
+    return -1;
+  }
+  gst_object_unref (sinkpad);
+
+  /* link output 2 */
+  sinkpad = gst_element_get_static_pad (convert, "sink");
+  osel_src2 = gst_element_get_request_pad (osel, "src%d");
+  if (gst_pad_link (osel_src2, sinkpad) != GST_PAD_LINK_OK) {
+    g_print ("linking output 2 failed\n");
+    return -1;
+  }
+  gst_object_unref (sinkpad);
+
+  if (!gst_element_link (convert, sink2)) {
+    g_print ("linking output 2 failed\n");
+    return -1;
+  }
+
+  /* add switch callback */
+  g_timeout_add (1000, switch_cb, osel);
+
+  /* change to playing */
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+  gst_bus_add_watch (bus, my_bus_callback, loop);
+  gst_object_unref (bus);
+
+  gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+  /* now run */
+  g_main_loop_run (loop);
+
+  /* also clean up */
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+  gst_element_release_request_pad (osel, osel_src1);
+  gst_element_release_request_pad (osel, osel_src2);
+  gst_object_unref (GST_OBJECT (pipeline));
+
+  return 0;
+}