android: make it ready for androgenizer
[platform/upstream/gstreamer.git] / plugins / elements / gstoutputselector.c
index 74191cb..9a4db2a 100644 (file)
@@ -1,4 +1,4 @@
-/* GStreamer
+/* GStreamer output selector
  * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.com>)
  *
  * This library is free software; you can redistribute it and/or
 
 /**
  * SECTION:element-output-selector
- * @see_also: #GstTee, #GstInputSelector
+ * @see_also: #GstOutputSelector, #GstInputSelector
  *
  * Direct input stream to one out of N output pads.
+ *
+ * Since: 0.10.32
  */
 
 #ifdef HAVE_CONFIG_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,
@@ -53,16 +49,49 @@ GST_STATIC_PAD_TEMPLATE ("src%d",
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
 
+enum GstOutputSelectorPadNegotiationMode
+{
+  GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE,
+  GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL,
+  GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE
+};
+#define GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE (gst_output_selector_pad_negotiation_mode_get_type())
+static GType
+gst_output_selector_pad_negotiation_mode_get_type (void)
+{
+  static GType pad_negotiation_mode_type = 0;
+  static GEnumValue pad_negotiation_modes[] = {
+    {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE, "None", "none"},
+    {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL, "All", "all"},
+    {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE, "Active", "active"},
+    {0, NULL, NULL}
+  };
+
+  if (!pad_negotiation_mode_type) {
+    pad_negotiation_mode_type =
+        g_enum_register_static ("GstOutputSelectorPadNegotiationMode",
+        pad_negotiation_modes);
+  }
+  return pad_negotiation_mode_type;
+}
+
+
 enum
 {
   PROP_0,
   PROP_ACTIVE_PAD,
   PROP_RESEND_LATEST,
-  PROP_LAST
+  PROP_PAD_NEGOTIATION_MODE
 };
 
-GST_BOILERPLATE (GstOutputSelector, gst_output_selector, GstElement,
-    GST_TYPE_ELEMENT);
+#define DEFAULT_PAD_NEGOTIATION_MODE GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL
+
+#define _do_init(bla) \
+GST_DEBUG_CATEGORY_INIT (output_selector_debug, \
+        "output-selector", 0, "Output stream selector");
+
+GST_BOILERPLATE_FULL (GstOutputSelector, gst_output_selector, GstElement,
+    GST_TYPE_ELEMENT, _do_init);
 
 static void gst_output_selector_dispose (GObject * object);
 static void gst_output_selector_set_property (GObject * object,
@@ -74,17 +103,23 @@ static GstPad *gst_output_selector_request_new_pad (GstElement * element,
 static void gst_output_selector_release_pad (GstElement * element,
     GstPad * pad);
 static GstFlowReturn gst_output_selector_chain (GstPad * pad, GstBuffer * buf);
+static GstFlowReturn gst_output_selector_buffer_alloc (GstPad * pad,
+    guint64 offset, guint size, GstCaps * caps, 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 void gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector *
+    sel, gint mode);
 
 static void
 gst_output_selector_base_init (gpointer g_class)
 {
   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
 
-  gst_element_class_set_details (element_class, &gst_output_selector_details);
+  gst_element_class_set_details_simple (element_class, "Output selector",
+      "Generic", "1-to-N output stream selector",
+      "Stefan Kost <stefan.kost@nokia.com>");
   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,
@@ -97,22 +132,25 @@ 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->dispose = gst_output_selector_dispose;
 
-  gobject_class->set_property =
-      GST_DEBUG_FUNCPTR (gst_output_selector_set_property);
-  gobject_class->get_property =
-      GST_DEBUG_FUNCPTR (gst_output_selector_get_property);
+  gobject_class->set_property = gst_output_selector_set_property;
+  gobject_class->get_property = gst_output_selector_get_property;
 
   g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
       g_param_spec_object ("active-pad", "Active pad",
-          "Currently active src pad", GST_TYPE_PAD, G_PARAM_READWRITE));
+          "Currently active src pad", GST_TYPE_PAD,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   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));
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PAD_NEGOTIATION_MODE,
+      g_param_spec_enum ("pad-negotiation-mode", "Pad negotiation mode",
+          "The mode to be used for pad negotiation",
+          GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE,
+          DEFAULT_PAD_NEGOTIATION_MODE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   gstelement_class->request_new_pad =
       GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
@@ -120,9 +158,6 @@ gst_output_selector_class_init (GstOutputSelectorClass * klass)
       GST_DEBUG_FUNCPTR (gst_output_selector_release_pad);
 
   gstelement_class->change_state = gst_output_selector_change_state;
-
-  GST_DEBUG_CATEGORY_INIT (output_selector_debug,
-      "output-selector", 0, "An output stream selector element");
 }
 
 static void
@@ -136,32 +171,26 @@ gst_output_selector_init (GstOutputSelector * sel,
       GST_DEBUG_FUNCPTR (gst_output_selector_chain));
   gst_pad_set_event_function (sel->sinkpad,
       GST_DEBUG_FUNCPTR (gst_output_selector_handle_sink_event));
+  gst_pad_set_bufferalloc_function (sel->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_output_selector_buffer_alloc));
 
   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);
+  gst_segment_init (&sel->segment, GST_FORMAT_TIME);
   sel->pending_srcpad = NULL;
 
   sel->resend_latest = FALSE;
   sel->latest_buffer = NULL;
+  gst_output_selector_switch_pad_negotiation_mode (sel,
+      DEFAULT_PAD_NEGOTIATION_MODE);
 }
 
 static void
-gst_output_selector_dispose (GObject * object)
+gst_output_selector_reset (GstOutputSelector * osel)
 {
-  GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object);
-
   if (osel->pending_srcpad != NULL) {
     gst_object_unref (osel->pending_srcpad);
     osel->pending_srcpad = NULL;
@@ -170,6 +199,15 @@ gst_output_selector_dispose (GObject * object)
     gst_buffer_unref (osel->latest_buffer);
     osel->latest_buffer = NULL;
   }
+  gst_segment_init (&osel->segment, GST_FORMAT_UNDEFINED);
+}
+
+static void
+gst_output_selector_dispose (GObject * object)
+{
+  GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object);
+
+  gst_output_selector_reset (osel);
 
   G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -187,7 +225,7 @@ gst_output_selector_set_property (GObject * object, guint prop_id,
 
       next_pad = g_value_get_object (value);
 
-      GST_LOG_OBJECT (sel, "Activating pad %s:%s",
+      GST_INFO_OBJECT (sel, "Activating pad %s:%s",
           GST_DEBUG_PAD_NAME (next_pad));
 
       GST_OBJECT_LOCK (object);
@@ -214,6 +252,11 @@ gst_output_selector_set_property (GObject * object, guint prop_id,
       sel->resend_latest = g_value_get_boolean (value);
       break;
     }
+    case PROP_PAD_NEGOTIATION_MODE:{
+      gst_output_selector_switch_pad_negotiation_mode (sel,
+          g_value_get_enum (value));
+      break;
+    }
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -229,7 +272,8 @@ gst_output_selector_get_property (GObject * object, guint prop_id,
   switch (prop_id) {
     case PROP_ACTIVE_PAD:
       GST_OBJECT_LOCK (object);
-      g_value_set_object (value, sel->active_srcpad);
+      g_value_set_object (value,
+          sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad);
       GST_OBJECT_UNLOCK (object);
       break;
     case PROP_RESEND_LATEST:{
@@ -238,12 +282,117 @@ gst_output_selector_get_property (GObject * object, guint prop_id,
       GST_OBJECT_UNLOCK (object);
       break;
     }
+    case PROP_PAD_NEGOTIATION_MODE:
+      g_value_set_enum (value, sel->pad_negotiation_mode);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
 }
 
+static GstCaps *
+gst_output_selector_sink_getcaps (GstPad * pad)
+{
+  GstOutputSelector *sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad));
+  GstPad *active = NULL;
+  GstCaps *caps = NULL;
+
+  GST_OBJECT_LOCK (sel);
+  if (sel->pending_srcpad)
+    active = gst_object_ref (sel->pending_srcpad);
+  else if (sel->active_srcpad)
+    active = gst_object_ref (sel->active_srcpad);
+  GST_OBJECT_UNLOCK (sel);
+
+  if (active) {
+    caps = gst_pad_peer_get_caps_reffed (active);
+    gst_object_unref (active);
+  }
+  if (caps == NULL) {
+    caps = gst_caps_new_any ();
+  }
+  return caps;
+}
+
+static gboolean
+gst_output_selector_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstOutputSelector *sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad));
+  GstPad *active = NULL;
+  gboolean ret = TRUE;
+
+  GST_OBJECT_LOCK (sel);
+  if (sel->pending_srcpad)
+    active = gst_object_ref (sel->pending_srcpad);
+  else if (sel->active_srcpad)
+    active = gst_object_ref (sel->active_srcpad);
+  GST_OBJECT_UNLOCK (sel);
+
+  if (active) {
+    ret = gst_pad_set_caps (active, caps);
+    gst_object_unref (active);
+  }
+  return ret;
+}
+
+static void
+gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector * sel,
+    gint mode)
+{
+  sel->pad_negotiation_mode = mode;
+  if (mode == GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL) {
+    gst_pad_set_getcaps_function (sel->sinkpad, gst_pad_proxy_getcaps);
+    gst_pad_set_setcaps_function (sel->sinkpad, gst_pad_proxy_setcaps);
+  } else if (mode == GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE) {
+    gst_pad_set_getcaps_function (sel->sinkpad, NULL);
+    gst_pad_set_setcaps_function (sel->sinkpad, NULL);
+  } else {                      /* active */
+    gst_pad_set_getcaps_function (sel->sinkpad,
+        gst_output_selector_sink_getcaps);
+    gst_pad_set_setcaps_function (sel->sinkpad,
+        gst_output_selector_sink_setcaps);
+  }
+}
+
+static GstFlowReturn
+gst_output_selector_buffer_alloc (GstPad * pad, guint64 offset, guint size,
+    GstCaps * caps, GstBuffer ** buf)
+{
+  GstOutputSelector *sel;
+  GstFlowReturn res;
+  GstPad *allocpad;
+
+  sel = GST_OUTPUT_SELECTOR (GST_PAD_PARENT (pad));
+  res = GST_FLOW_NOT_LINKED;
+
+  GST_OBJECT_LOCK (sel);
+  allocpad = sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad;
+  if (allocpad) {
+    /* if we had a previous pad we used for allocating a buffer, continue using
+     * it. */
+    GST_DEBUG_OBJECT (sel, "using pad %s:%s for alloc",
+        GST_DEBUG_PAD_NAME (allocpad));
+    gst_object_ref (allocpad);
+    GST_OBJECT_UNLOCK (sel);
+
+    res = gst_pad_alloc_buffer (allocpad, offset, size, caps, buf);
+    gst_object_unref (allocpad);
+
+    GST_OBJECT_LOCK (sel);
+  } else {
+    /* fallback case, allocate a buffer of our own, add pad caps. */
+    GST_DEBUG_OBJECT (pad, "fallback buffer alloc");
+    *buf = NULL;
+    res = GST_FLOW_OK;
+  }
+  GST_OBJECT_UNLOCK (sel);
+
+  GST_DEBUG_OBJECT (sel, "buffer alloc finished: %s", gst_flow_get_name (res));
+
+  return res;
+}
+
 static GstPad *
 gst_output_selector_request_new_pad (GstElement * element,
     GstPadTemplate * templ, const gchar * name)
@@ -290,14 +439,24 @@ gst_output_selector_release_pad (GstElement * element, GstPad * pad)
 static gboolean
 gst_output_selector_switch (GstOutputSelector * osel)
 {
-  gboolean res = TRUE;
+  gboolean res = FALSE;
   GstEvent *ev = NULL;
   GstSegment *seg = NULL;
   gint64 start = 0, position = 0;
 
+  /* Switch */
+  GST_OBJECT_LOCK (GST_OBJECT (osel));
   GST_INFO ("switching to pad %" GST_PTR_FORMAT, osel->pending_srcpad);
-
   if (gst_pad_is_linked (osel->pending_srcpad)) {
+    osel->active_srcpad = osel->pending_srcpad;
+    res = TRUE;
+  }
+  gst_object_unref (osel->pending_srcpad);
+  osel->pending_srcpad = NULL;
+  GST_OBJECT_UNLOCK (GST_OBJECT (osel));
+
+  /* Send NEWSEGMENT event and latest buffer if switching succeeded */
+  if (res) {
     /* Send NEWSEGMENT to the pad we are going to switch to */
     seg = &osel->segment;
     /* If resending then mark newsegment start and position accordingly */
@@ -309,29 +468,21 @@ gst_output_selector_switch (GstOutputSelector * osel)
     }
     ev = gst_event_new_new_segment (TRUE, seg->rate,
         seg->format, start, seg->stop, position);
-    if (!gst_pad_push_event (osel->pending_srcpad, ev)) {
+    if (!gst_pad_push_event (osel->active_srcpad, ev)) {
       GST_WARNING_OBJECT (osel,
           "newsegment handling failed in %" GST_PTR_FORMAT,
-          osel->pending_srcpad);
+          osel->active_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;
+      gst_pad_push (osel->active_srcpad, gst_buffer_ref (osel->latest_buffer));
     }
-
-    /* Switch */
-    osel->active_srcpad = osel->pending_srcpad;
   } else {
     GST_WARNING_OBJECT (osel, "switch failed, pad not linked");
-    res = FALSE;
   }
 
-  gst_object_unref (osel->pending_srcpad);
-  osel->pending_srcpad = NULL;
-
   return res;
 }
 
@@ -344,15 +495,31 @@ gst_output_selector_chain (GstPad * pad, GstBuffer * buf)
 
   osel = GST_OUTPUT_SELECTOR (gst_pad_get_parent (pad));
 
-  if (osel->pending_srcpad) {
+  /*
+   * The _switch function might push a buffer if 'resend-latest' is true.
+   *
+   * Elements/Applications (e.g. camerabin) might use pad probes to
+   * switch output-selector's active pad. If we simply switch and don't
+   * recheck any pending pad switch the following codepath could end
+   * up pushing a buffer on a non-active pad. This is bad.
+   *
+   * So we always should check the pending_srcpad before going further down
+   * the chain and pushing the new buffer
+   */
+  while (osel->pending_srcpad) {
     /* Do the switch */
     gst_output_selector_switch (osel);
   }
 
-  /* Keep reference to latest buffer to resend it after switch */
-  if (osel->latest_buffer)
+  if (osel->latest_buffer) {
     gst_buffer_unref (osel->latest_buffer);
-  osel->latest_buffer = gst_buffer_ref (buf);
+    osel->latest_buffer = NULL;
+  }
+
+  if (osel->resend_latest) {
+    /* Keep reference to latest buffer to resend it after switch */
+    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 */
@@ -379,7 +546,29 @@ static GstStateChangeReturn
 gst_output_selector_change_state (GstElement * element,
     GstStateChange transition)
 {
-  return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+  GstOutputSelector *sel;
+  GstStateChangeReturn result;
+
+  sel = GST_OUTPUT_SELECTOR (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      break;
+    default:
+      break;
+  }
+
+  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_output_selector_reset (sel);
+      break;
+    default:
+      break;
+  }
+
+  return result;
 }
 
 static gboolean
@@ -412,7 +601,6 @@ gst_output_selector_handle_sink_event (GstPad * pad, GstEvent * event)
 
       /* Send newsegment to all src pads */
       gst_pad_event_default (pad, event);
-
       break;
     }
     case GST_EVENT_EOS: