/* Gnonlin * Copyright (C) <2001> Wim Taymans * <2004-2008> Edward Hervey * * 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); enum { PROP_0, PROP_REVERSE, PROP_LAST, }; 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 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 */ GMutex seek_lock; GstEvent *seek_event; guint32 flush_seqnum; gulong probeid; /* Identity automatically created to handle reverse playback */ GstElement *identity; }; G_DEFINE_TYPE_WITH_CODE (NleSource, nle_source, NLE_TYPE_OBJECT, G_ADD_PRIVATE (NleSource) _do_init); 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 GstStateChangeReturn nle_source_change_state (GstElement * element, GstStateChange transition); static gboolean nle_source_commit (NleObject * object, gboolean recurse) { NleSource *self = NLE_SOURCE (object); if (!NLE_OBJECT_CLASS (parent_class)->commit (object, recurse)) return FALSE; self->reverse = self->pending_reverse; g_object_set (self->priv->identity, "single-segment", self->reverse, NULL); return TRUE; } static void nle_source_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { NleSource *self = NLE_SOURCE (object); GST_OBJECT_LOCK (self); switch (property_id) { case PROP_REVERSE: g_value_set_boolean (value, self->pending_reverse); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } GST_OBJECT_UNLOCK (self); } static void nle_source_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { NleSource *self = NLE_SOURCE (object); GST_OBJECT_LOCK (self); switch (property_id) { case PROP_REVERSE: self->pending_reverse = g_value_get_boolean (value); if (self->pending_reverse != self->reverse) nle_object_set_commit_needed ((NleObject *) self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } GST_OBJECT_UNLOCK (self); } 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; gobject_class->get_property = nle_source_get_property; gobject_class->set_property = nle_source_set_property; /** * NleSource:reverse: * @reverse: Whether to playback the source reverse or not * * Since: 1.26 */ g_object_class_install_property (gobject_class, PROP_REVERSE, g_param_spec_boolean ("reverse", "Reverse", "Whether to playback the source reverse or not", FALSE, G_PARAM_READWRITE)); gst_element_class_set_static_metadata (gstelement_class, "GNonLin Source", "Filter/Editor", "Manages source elements", "Wim Taymans , Edward Hervey "); gstelement_class->send_event = GST_DEBUG_FUNCPTR (nle_source_send_event); gstelement_class->change_state = GST_DEBUG_FUNCPTR (nle_source_change_state); 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); nleobject_class->commit = GST_DEBUG_FUNCPTR (nle_source_commit); 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_static_pad_template (gstelement_class, &nle_source_src_template); } static GstPadProbeReturn srcpad_probe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source) { GstEvent *event = info->data; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: GST_OBJECT_LOCK (source); source->priv->flush_seqnum = GST_EVENT_SEQNUM (event); GST_DEBUG_OBJECT (pad, "Seek seqnum: %d", source->priv->flush_seqnum); GST_OBJECT_UNLOCK (source); break; default: break; } return GST_PAD_PROBE_OK; } static void nle_source_init (NleSource * source) { NleSourcePrivate *priv; NleObject *nleobject = NLE_OBJECT (source); GST_OBJECT_FLAG_SET (source, NLE_OBJECT_SOURCE); source->element = NULL; priv = source->priv = nle_source_get_instance_private (source); priv->identity = gst_element_factory_make ("identity", NULL); gst_bin_add (GST_BIN (source), priv->identity); nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, priv->identity->srcpads->data); g_mutex_init (&priv->seek_lock); gst_pad_add_probe (NLE_OBJECT_SRC (source), GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) srcpad_probe_cb, source, NULL); 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; GstElement *tmpidentity; 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); gst_clear_object (&source->element); tmpidentity = priv->identity; priv->identity = NULL; gst_bin_remove (GST_BIN (source), tmpidentity); priv->dispose_has_run = TRUE; nle_object_ghost_pad_set_target (nleobject, nleobject->srcpad, NULL); gst_clear_object (&priv->staticpad); g_mutex_lock (&priv->seek_lock); gst_clear_event (&priv->seek_event); g_mutex_unlock (&priv->seek_lock); 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); if (gst_pad_link (pad, priv->identity->sinkpads->data) != GST_PAD_LINK_OK) { GST_ERROR_OBJECT (source, "Could not link pads: %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT, pad, priv->identity->sinkpads->data); } else { GST_DEBUG_OBJECT (source, "Linked pads: %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT, pad, priv->identity->sinkpads->data); priv->ghostedpad = pad; } } static void element_pad_removed_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad, NleSource * source) { NleSourcePrivate *priv = source->priv; GST_DEBUG_OBJECT (source, "pad %s:%s (controlled pad %s:%s)", GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->ghostedpad)); if (pad == priv->ghostedpad) { GST_DEBUG_OBJECT (source, "The removed pad is the controlled pad, clearing up"); GST_DEBUG_OBJECT (source, "Clearing up ghostpad"); if (priv->identity && !gst_pad_unlink (pad, priv->identity->sinkpads->data)) GST_ERROR_OBJECT (source, "Could not unlink pads: %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT, pad, priv->identity->sinkpads->data); 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; if (element == priv->identity) return TRUE; g_return_val_if_fail (source->element == NULL, FALSE); GST_DEBUG_OBJECT (source, "element: %" GST_PTR_FORMAT ", source->element:%" GST_PTR_FORMAT, element, source->element); source->element = element; gst_object_ref (element); if (get_valid_src_pad (source, source->element, &pad)) { priv->staticpad = pad; if (gst_pad_link (pad, priv->identity->sinkpads->data) != GST_PAD_LINK_OK) { GST_ERROR_OBJECT (source, "Could not link pads: %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT, pad, priv->identity->sinkpads->data); } else { GST_DEBUG_OBJECT (source, "Linked pads: %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT, pad, priv->identity->sinkpads->data); } 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: g_mutex_lock (&source->priv->seek_lock); gst_event_replace (&source->priv->seek_event, event); g_mutex_unlock (&source->priv->seek_lock); break; default: res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); break; } return res; } static void ghost_seek_pad (GstElement * source, gpointer user_data) { NleSourcePrivate *priv = NLE_SOURCE (source)->priv; g_assert (!NLE_OBJECT (source)->in_composition); g_mutex_lock (&priv->seek_lock); if (priv->seek_event) { GstEvent *seek_event = priv->seek_event; priv->seek_event = NULL; GST_INFO_OBJECT (source, "Sending seek: %" GST_PTR_FORMAT, seek_event); GST_OBJECT_LOCK (source); priv->flush_seqnum = GST_EVENT_SEQNUM (seek_event); GST_OBJECT_UNLOCK (source); if (!(gst_pad_send_event (priv->ghostedpad, seek_event))) GST_ELEMENT_ERROR (source, RESOURCE, SEEK, (NULL), ("Sending initial seek to upstream element failed")); } g_mutex_unlock (&priv->seek_lock); } static GstPadProbeReturn pad_brobe_cb (GstPad * pad, GstPadProbeInfo * info, NleSource * source) { NleSourcePrivate *priv = source->priv; GstPadProbeReturn res = GST_PAD_PROBE_OK; GST_OBJECT_LOCK (source); if (!priv->areblocked && priv->seek_event) { GST_INFO_OBJECT (pad, "Blocked now, launching seek"); priv->areblocked = TRUE; gst_element_call_async (GST_ELEMENT (source), ghost_seek_pad, NULL, NULL); GST_OBJECT_UNLOCK (source); return GST_PAD_PROBE_OK; } if (priv->probeid && GST_EVENT_SEQNUM (info->data) == priv->flush_seqnum) { priv->flush_seqnum = GST_SEQNUM_INVALID; priv->areblocked = FALSE; priv->probeid = 0; res = GST_PAD_PROBE_REMOVE; } else { res = GST_PAD_PROBE_DROP; GST_DEBUG_OBJECT (source, "Dropping %" GST_PTR_FORMAT " - %d ? %d", info->data, GST_EVENT_SEQNUM (info->data), priv->flush_seqnum); } GST_OBJECT_UNLOCK (source); return res; } 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 (!priv->staticpad && !(get_valid_src_pad (source, source->element, &pad))) { GST_DEBUG_OBJECT (source, "Couldn't find a valid source pad"); gst_object_unref (parent); return FALSE; } if (priv->staticpad) pad = gst_object_ref (priv->staticpad); priv->ghostedpad = pad; if (object->in_composition == FALSE) { GstClockTime start = GST_CLOCK_TIME_IS_VALID (object->inpoint) ? object->inpoint : 0; GstClockTime stop = GST_CLOCK_TIME_NONE; if (GST_CLOCK_TIME_IS_VALID (object->inpoint) && GST_CLOCK_TIME_IS_VALID (object->duration) && object->duration) stop = object->inpoint + object->duration; g_mutex_lock (&source->priv->seek_lock); source->priv->seek_event = gst_event_new_seek (source->reverse ? -1.0 : 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, stop); g_mutex_unlock (&source->priv->seek_lock); GST_OBJECT_LOCK (source); priv->probeid = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) pad_brobe_cb, source, NULL); GST_OBJECT_UNLOCK (source); } GST_LOG_OBJECT (source, "srcpad:%p, dynamicpads:%d", object->srcpad, priv->dynamicpads); gst_object_unref (pad); gst_object_unref (parent); return TRUE; } static GstStateChangeReturn nle_source_change_state (GstElement * element, GstStateChange transition) { NleSourcePrivate *priv = NLE_SOURCE (element)->priv; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: g_mutex_lock (&priv->seek_lock); gst_clear_event (&priv->seek_event); g_mutex_unlock (&priv->seek_lock); break; default: break; } return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); }