/* GStreamer * Copyright (C) 2018 Ognyan Tonchev * * 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 #include "rtsp-latency-bin.h" struct _GstRTSPLatencyBinPrivate { GstPad *sinkpad; GstElement *element; }; enum { PROP_0, PROP_ELEMENT, PROP_LAST }; GST_DEBUG_CATEGORY_STATIC (rtsp_latency_bin_debug); #define GST_CAT_DEFAULT rtsp_latency_bin_debug static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static void gst_rtsp_latency_bin_get_property (GObject * object, guint propid, GValue * value, GParamSpec * pspec); static void gst_rtsp_latency_bin_set_property (GObject * object, guint propid, const GValue * value, GParamSpec * pspec); static gboolean gst_rtsp_latency_bin_element_query (GstElement * element, GstQuery * query); static gboolean gst_rtsp_latency_bin_element_event (GstElement * element, GstEvent * event); static void gst_rtsp_latency_bin_message_handler (GstBin * bin, GstMessage * message); static gboolean gst_rtsp_latency_bin_add_element (GstRTSPLatencyBin * latency_bin, GstElement * element); static GstStateChangeReturn gst_rtsp_latency_bin_change_state (GstElement * element, GstStateChange transition); G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPLatencyBin, gst_rtsp_latency_bin, GST_TYPE_BIN); static void gst_rtsp_latency_bin_class_init (GstRTSPLatencyBinClass * klass) { GObjectClass *gobject_klass = G_OBJECT_CLASS (klass); GstElementClass *gstelement_klass = GST_ELEMENT_CLASS (klass); GstBinClass *gstbin_klass = GST_BIN_CLASS (klass); GST_DEBUG_CATEGORY_INIT (rtsp_latency_bin_debug, "rtsplatencybin", 0, "GstRTSPLatencyBin"); gobject_klass->get_property = gst_rtsp_latency_bin_get_property; gobject_klass->set_property = gst_rtsp_latency_bin_set_property; g_object_class_install_property (gobject_klass, PROP_ELEMENT, g_param_spec_object ("element", "The Element", "The GstElement to prevent from affecting piplines latency", GST_TYPE_ELEMENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); gstelement_klass->change_state = GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_change_state); gstelement_klass->query = GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_element_query); gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_element_event); gstbin_klass->handle_message = GST_DEBUG_FUNCPTR (gst_rtsp_latency_bin_message_handler); } static void gst_rtsp_latency_bin_init (GstRTSPLatencyBin * latency_bin) { GST_OBJECT_FLAG_SET (latency_bin, GST_ELEMENT_FLAG_SINK); } static void gst_rtsp_latency_bin_get_property (GObject * object, guint propid, GValue * value, GParamSpec * pspec) { GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (object); GstRTSPLatencyBinPrivate *priv = gst_rtsp_latency_bin_get_instance_private (latency_bin); switch (propid) { case PROP_ELEMENT: g_value_set_object (value, priv->element); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } } static void gst_rtsp_latency_bin_set_property (GObject * object, guint propid, const GValue * value, GParamSpec * pspec) { GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (object); switch (propid) { case PROP_ELEMENT: if (!gst_rtsp_latency_bin_add_element (latency_bin, g_value_get_object (value))) { GST_WARNING_OBJECT (latency_bin, "Could not add the element"); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } } static gboolean gst_rtsp_latency_bin_add_element (GstRTSPLatencyBin * latency_bin, GstElement * element) { GstRTSPLatencyBinPrivate *priv = gst_rtsp_latency_bin_get_instance_private (latency_bin); GstPad *pad; GstPadTemplate *templ; GST_DEBUG_OBJECT (latency_bin, "Adding element to latencybin : %s", GST_ELEMENT_NAME (element)); if (!element) { goto no_element; } /* add the element to ourself */ gst_object_ref (element); gst_bin_add (GST_BIN (latency_bin), element); priv->element = element; /* add ghost pad first */ templ = gst_static_pad_template_get (&sinktemplate); priv->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ); gst_object_unref (templ); g_assert (priv->sinkpad); gst_element_add_pad (GST_ELEMENT (latency_bin), priv->sinkpad); /* and link it to our element */ pad = gst_element_get_static_pad (element, "sink"); if (!pad) { goto no_sink_pad; } if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (priv->sinkpad), pad)) { goto set_target_failed; } gst_object_unref (pad); return TRUE; /* ERRORs */ no_element: { GST_WARNING_OBJECT (latency_bin, "No element, not adding"); return FALSE; } no_sink_pad: { GST_WARNING_OBJECT (latency_bin, "The element has no sink pad"); return FALSE; } set_target_failed: { GST_WARNING_OBJECT (latency_bin, "Could not set target pad"); gst_object_unref (pad); return FALSE; } } static gboolean gst_rtsp_latency_bin_element_query (GstElement * element, GstQuery * query) { gboolean ret = TRUE; GST_LOG_OBJECT (element, "got query %s", GST_QUERY_TYPE_NAME (query)); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_LATENCY: /* ignoring latency query, we do not want our element to affect latency on * the rest of the pipeline */ GST_DEBUG_OBJECT (element, "ignoring latency query"); gst_query_set_latency (query, FALSE, 0, -1); break; default: ret = GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->query (GST_ELEMENT (element), query); break; } return ret; } static gboolean gst_rtsp_latency_bin_element_event (GstElement * element, GstEvent * event) { gboolean ret = TRUE; GST_LOG_OBJECT (element, "got event %s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_LATENCY: /* ignoring latency event, we will configure latency on our element when * going to PLAYING */ GST_DEBUG_OBJECT (element, "ignoring latency event"); gst_event_unref (event); break; default: ret = GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->send_event (GST_ELEMENT (element), event); break; } return ret; } static gboolean gst_rtsp_latency_bin_recalculate_latency (GstRTSPLatencyBin * latency_bin) { GstRTSPLatencyBinPrivate *priv = gst_rtsp_latency_bin_get_instance_private (latency_bin); GstEvent *latency; GstQuery *query; GstClockTime min_latency; GST_DEBUG_OBJECT (latency_bin, "Recalculating latency"); if (!priv->element) { GST_WARNING_OBJECT (latency_bin, "We do not have an element"); return FALSE; } query = gst_query_new_latency (); if (!gst_element_query (priv->element, query)) { GST_WARNING_OBJECT (latency_bin, "Latency query failed"); gst_query_unref (query); return FALSE; } gst_query_parse_latency (query, NULL, &min_latency, NULL); gst_query_unref (query); GST_LOG_OBJECT (latency_bin, "Got min_latency from stream: %" GST_TIME_FORMAT, GST_TIME_ARGS (min_latency)); latency = gst_event_new_latency (min_latency); if (!gst_element_send_event (priv->element, latency)) { GST_WARNING_OBJECT (latency_bin, "Sending latency event to stream failed"); return FALSE; } return TRUE; } static void gst_rtsp_latency_bin_message_handler (GstBin * bin, GstMessage * message) { GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (bin); GST_LOG_OBJECT (bin, "Got message %s", GST_MESSAGE_TYPE_NAME (message)); switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_LATENCY:{ if (!gst_rtsp_latency_bin_recalculate_latency (latency_bin)) { GST_WARNING_OBJECT (latency_bin, "Could not recalculate latency"); } break; } default: GST_BIN_CLASS (gst_rtsp_latency_bin_parent_class)->handle_message (bin, message); break; } } static GstStateChangeReturn gst_rtsp_latency_bin_change_state (GstElement * element, GstStateChange transition) { GstRTSPLatencyBin *latency_bin = GST_RTSP_LATENCY_BIN (element); GstStateChangeReturn ret; GST_LOG_OBJECT (latency_bin, "Changing state %s", gst_state_change_get_name (transition)); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_PLAYING: case GST_STATE_CHANGE_PLAYING_TO_PLAYING: if (!gst_rtsp_latency_bin_recalculate_latency (latency_bin)) { GST_WARNING_OBJECT (latency_bin, "Could not recalculate latency"); } default: break; } ret = GST_ELEMENT_CLASS (gst_rtsp_latency_bin_parent_class)->change_state (element, transition); return ret; } /** * gst_rtsp_latency_bin_new: * @element: (transfer full): a #GstElement * * Create a bin that encapsulates an @element and prevents it from affecting * latency on the whole pipeline. * * Returns: (nullable): A newly created #GstRTSPLatencyBin element, or %NULL on failure */ GstElement * gst_rtsp_latency_bin_new (GstElement * element) { GstElement *gst_rtsp_latency_bin; g_return_val_if_fail (element, NULL); gst_rtsp_latency_bin = g_object_new (GST_RTSP_LATENCY_BIN_TYPE, "element", element, NULL); gst_object_unref (element); return gst_rtsp_latency_bin; }