Apply GStreamer 1.22.0 into Tizen
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / gst / rtsp / gstrtspsrc.c
index d28f241..8e0f1d9 100644 (file)
 #include <gst/sdp/gstmikey.h>
 #include <gst/rtp/rtp.h>
 
-#include "gst/gst-i18n-plugin.h"
+#include <glib/gi18n-lib.h>
 
 #include "gstrtspelements.h"
 #include "gstrtspsrc.h"
@@ -151,6 +151,7 @@ enum
   SIGNAL_GET_PARAMETER,
   SIGNAL_GET_PARAMETERS,
   SIGNAL_SET_PARAMETER,
+  SIGNAL_PUSH_BACKCHANNEL_SAMPLE,
   LAST_SIGNAL
 };
 
@@ -301,6 +302,7 @@ gst_rtsp_backchannel_get_type (void)
 #define DEFAULT_USER_AGENT       "GStreamer/" PACKAGE_VERSION
 #define DEFAULT_MAX_RTCP_RTP_TIME_DIFF 1000
 #define DEFAULT_RFC7273_SYNC         FALSE
+#define DEFAULT_ADD_REFERENCE_TIMESTAMP_META FALSE
 #define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT   G_GUINT64_CONSTANT(0)
 #define DEFAULT_MAX_TS_OFFSET   G_GINT64_CONSTANT(3000000000)
 #define DEFAULT_VERSION         GST_RTSP_VERSION_1_0
@@ -358,6 +360,7 @@ enum
   PROP_USER_AGENT,
   PROP_MAX_RTCP_RTP_TIME_DIFF,
   PROP_RFC7273_SYNC,
+  PROP_ADD_REFERENCE_TIMESTAMP_META,
   PROP_MAX_TS_OFFSET_ADJUSTMENT,
   PROP_MAX_TS_OFFSET,
   PROP_DEFAULT_VERSION,
@@ -473,6 +476,9 @@ static gboolean set_parameter (GstRTSPSrc * src, const gchar * name,
 static GstFlowReturn gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src,
     guint id, GstSample * sample);
 
+static GstFlowReturn gst_rtspsrc_push_backchannel_sample (GstRTSPSrc * src,
+    guint id, GstSample * sample);
+
 typedef struct
 {
   guint8 pt;
@@ -851,6 +857,16 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
    * TLS certificate validation flags used to validate server
    * certificate.
    *
+   * GLib guarantees that if certificate verification fails, at least one
+   * error will be set, but it does not guarantee that all possible errors
+   * will be set. Accordingly, you may not safely decide to ignore any
+   * particular type of error.
+   *
+   * For example, it would be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if
+   * you want to allow expired certificates, because this could potentially be
+   * the only error flag set even if other problems exist with the
+   * certificate.
+   *
    * Since: 1.2.1
    */
   g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS,
@@ -941,6 +957,23 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
+   * GstRTSPSrc:add-reference-timestamp-meta:
+   *
+   * When syncing to a RFC7273 clock, add #GstReferenceTimestampMeta
+   * to buffers with the original reconstructed reference clock timestamp.
+   *
+   * Since: 1.22
+   */
+  g_object_class_install_property (gobject_class,
+      PROP_ADD_REFERENCE_TIMESTAMP_META,
+      g_param_spec_boolean ("add-reference-timestamp-meta",
+          "Add Reference Timestamp Meta",
+          "Add Reference Timestamp Meta to buffers with the original clock timestamp "
+          "before any adjustments when syncing to an RFC7273 clock.",
+          DEFAULT_ADD_REFERENCE_TIMESTAMP_META,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
    * GstRTSPSrc:default-rtsp-version:
    *
    * The preferred RTSP version to use while negotiating the version with the server.
@@ -1238,15 +1271,34 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
   /**
    * GstRTSPSrc::push-backchannel-buffer:
    * @rtspsrc: a #GstRTSPSrc
+   * @id: stream ID where the sample should be sent
    * @sample: RTP sample to send back
    *
+   * Deprecated: 1.22: Use action signal GstRTSPSrc::push-backchannel-sample instead.
+   * IMPORTANT: Please note that this signal decrements the reference count 
+   *            of sample internally! So it cannot be used from other
+   *            language bindings in general.
    *
    */
   gst_rtspsrc_signals[SIGNAL_PUSH_BACKCHANNEL_BUFFER] =
       g_signal_new ("push-backchannel-buffer", G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass,
-          push_backchannel_buffer), NULL, NULL, NULL,
-      GST_TYPE_FLOW_RETURN, 2, G_TYPE_UINT, GST_TYPE_SAMPLE);
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION | G_SIGNAL_DEPRECATED,
+      G_STRUCT_OFFSET (GstRTSPSrcClass, push_backchannel_buffer), NULL, NULL,
+      NULL, GST_TYPE_FLOW_RETURN, 2, G_TYPE_UINT, GST_TYPE_SAMPLE);
+
+  /**
+   * GstRTSPSrc::push-backchannel-sample:
+   * @rtspsrc: a #GstRTSPSrc
+   * @id: stream ID where the sample should be sent
+   * @sample: RTP sample to send back
+   *
+   * Since: 1.22
+   */
+  gst_rtspsrc_signals[SIGNAL_PUSH_BACKCHANNEL_SAMPLE] =
+      g_signal_new ("push-backchannel-sample", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION | G_SIGNAL_DEPRECATED,
+      G_STRUCT_OFFSET (GstRTSPSrcClass, push_backchannel_buffer), NULL, NULL,
+      NULL, GST_TYPE_FLOW_RETURN, 2, G_TYPE_UINT, GST_TYPE_SAMPLE);
 
   /**
    * GstRTSPSrc::get-parameter:
@@ -1319,6 +1371,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
   gstbin_class->handle_message = gst_rtspsrc_handle_message;
 
   klass->push_backchannel_buffer = gst_rtspsrc_push_backchannel_buffer;
+  klass->push_backchannel_sample = gst_rtspsrc_push_backchannel_sample;
   klass->get_parameter = GST_DEBUG_FUNCPTR (get_parameter);
   klass->get_parameters = GST_DEBUG_FUNCPTR (get_parameters);
   klass->set_parameter = GST_DEBUG_FUNCPTR (set_parameter);
@@ -1506,6 +1559,7 @@ gst_rtspsrc_init (GstRTSPSrc * src)
   src->user_agent = g_strdup (DEFAULT_USER_AGENT);
   src->max_rtcp_rtp_time_diff = DEFAULT_MAX_RTCP_RTP_TIME_DIFF;
   src->rfc7273_sync = DEFAULT_RFC7273_SYNC;
+  src->add_reference_timestamp_meta = DEFAULT_ADD_REFERENCE_TIMESTAMP_META;
   src->max_ts_offset_adjustment = DEFAULT_MAX_TS_OFFSET_ADJUSTMENT;
   src->max_ts_offset = DEFAULT_MAX_TS_OFFSET;
   src->max_ts_offset_is_set = FALSE;
@@ -1614,6 +1668,9 @@ gst_rtspsrc_finalize (GObject * object)
   if (rtspsrc->tls_interaction)
     g_object_unref (rtspsrc->tls_interaction);
 
+  if (rtspsrc->initial_seek)
+    gst_event_unref (rtspsrc->initial_seek);
+
   /* free locks */
   g_rec_mutex_clear (&rtspsrc->stream_rec_lock);
   g_rec_mutex_clear (&rtspsrc->state_rec_lock);
@@ -1863,6 +1920,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value,
     case PROP_RFC7273_SYNC:
       rtspsrc->rfc7273_sync = g_value_get_boolean (value);
       break;
+    case PROP_ADD_REFERENCE_TIMESTAMP_META:
+      rtspsrc->add_reference_timestamp_meta = g_value_get_boolean (value);
+      break;
     case PROP_MAX_TS_OFFSET_ADJUSTMENT:
       rtspsrc->max_ts_offset_adjustment = g_value_get_uint64 (value);
       break;
@@ -2042,6 +2102,9 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value,
     case PROP_RFC7273_SYNC:
       g_value_set_boolean (value, rtspsrc->rfc7273_sync);
       break;
+    case PROP_ADD_REFERENCE_TIMESTAMP_META:
+      g_value_set_boolean (value, rtspsrc->add_reference_timestamp_meta);
+      break;
     case PROP_MAX_TS_OFFSET_ADJUSTMENT:
       g_value_set_uint64 (value, rtspsrc->max_ts_offset_adjustment);
       break;
@@ -2529,41 +2592,13 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx,
     if (g_str_has_prefix (control_path, "rtsp://"))
       stream->conninfo.location = g_strdup (control_path);
     else {
+      const gchar *base;
+
+      base = get_aggregate_control (src);
       if (g_strcmp0 (control_path, "*") == 0)
-        control_path = "";
-      /* handle url with query */
-      if (src->conninfo.url && src->conninfo.url->query) {
-        stream->conninfo.location =
-            gst_rtsp_url_get_request_uri_with_control (src->conninfo.url,
-            control_path);
-      } else {
-        const gchar *base;
-        gboolean has_slash;
-        const gchar *slash;
-        const gchar *actual_control_path = NULL;
-
-        base = get_aggregate_control (src);
-        has_slash = g_str_has_suffix (base, "/");
-        /* manage existence or non-existence of / in control path */
-        if (control_path && strlen (control_path) > 0) {
-          gboolean control_has_slash = g_str_has_prefix (control_path, "/");
-
-          actual_control_path = control_path;
-          if (has_slash && control_has_slash) {
-            if (strlen (control_path) == 1) {
-              actual_control_path = NULL;
-            } else {
-              actual_control_path = control_path + 1;
-            }
-          } else {
-            has_slash = has_slash || control_has_slash;
-          }
-        }
-        slash = (!has_slash && (actual_control_path != NULL)) ? "/" : "";
-        /* concatenate the two strings, insert / when not present */
-        stream->conninfo.location =
-            g_strdup_printf ("%s%s%s", base, slash, control_path);
-      }
+        control_path = g_strdup (base);
+      else
+        stream->conninfo.location = gst_uri_join_strings (base, control_path);
     }
   }
   GST_DEBUG_OBJECT (src, " setup: %s",
@@ -2906,8 +2941,7 @@ gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state)
 }
 
 static void
-gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing,
-    guint32 seqnum)
+gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing)
 {
   GstEvent *event;
   gint cmd;
@@ -2915,13 +2949,11 @@ gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing,
 
   if (flush) {
     event = gst_event_new_flush_start ();
-    gst_event_set_seqnum (event, seqnum);
     GST_DEBUG_OBJECT (src, "start flush");
     cmd = CMD_WAIT;
     state = GST_STATE_PAUSED;
   } else {
     event = gst_event_new_flush_stop (TRUE);
-    gst_event_set_seqnum (event, seqnum);
     GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing);
     cmd = CMD_LOOP;
     if (playing)
@@ -3054,7 +3086,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event)
    * blocking in preroll). */
   if (flush) {
     GST_DEBUG_OBJECT (src, "starting flush");
-    gst_rtspsrc_flush (src, TRUE, FALSE, gst_event_get_seqnum (event));
+    gst_rtspsrc_flush (src, TRUE, FALSE);
   } else {
     if (src->task) {
       gst_task_pause (src->task);
@@ -3104,7 +3136,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event)
   if (flush) {
     /* if we started flush, we stop now */
     GST_DEBUG_OBJECT (src, "stopping flush");
-    gst_rtspsrc_flush (src, FALSE, playing, gst_event_get_seqnum (event));
+    gst_rtspsrc_flush (src, FALSE, playing);
   }
 
   /* now we did the seek and can activate the new segment values */
@@ -3235,15 +3267,10 @@ gst_rtspsrc_stream_start_event_add_group_id (GstRTSPSrc * src, GstEvent * event)
   gst_event_set_group_id (event, src->group_id);
 }
 
-static gboolean
-gst_rtspsrc_handle_src_sink_event (GstPad * pad, GstObject * parent,
+static GstEvent *
+gst_rtspsrc_update_src_event (GstRTSPSrc * self, GstRTSPStream * stream,
     GstEvent * event)
 {
-  GstRTSPStream *stream;
-  GstRTSPSrc *self = GST_RTSPSRC (GST_OBJECT_PARENT (parent));
-
-  stream = gst_pad_get_element_private (pad);
-
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_STREAM_START:{
       GChecksum *cs;
@@ -3263,16 +3290,30 @@ gst_rtspsrc_handle_src_sink_event (GstPad * pad, GstObject * parent,
       event = gst_event_new_stream_start (stream_id);
       gst_rtspsrc_stream_start_event_add_group_id (self, event);
       g_free (stream_id);
+
+      gst_event_set_seqnum (event, self->seek_seqnum);
       break;
     }
-    case GST_EVENT_SEGMENT:
-      if (self->seek_seqnum != GST_SEQNUM_INVALID)
-        GST_EVENT_SEQNUM (event) = self->seek_seqnum;
-      break;
     default:
+      event = gst_event_make_writable (event);
+      gst_event_set_seqnum (event, self->seek_seqnum);
       break;
   }
 
+  return event;
+}
+
+static gboolean
+gst_rtspsrc_handle_src_sink_event (GstPad * pad, GstObject * parent,
+    GstEvent * event)
+{
+  GstRTSPStream *stream;
+  GstRTSPSrc *self = GST_RTSPSRC (GST_OBJECT_PARENT (parent));
+
+  stream = gst_pad_get_element_private (pad);
+
+  event = gst_rtspsrc_update_src_event (self, stream, event);
+
   return gst_pad_push_event (stream->srcpad, event);
 }
 
@@ -3482,6 +3523,19 @@ static GstFlowReturn
 gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, guint id,
     GstSample * sample)
 {
+  GstFlowReturn res;
+
+  res = gst_rtspsrc_push_backchannel_sample (src, id, sample);
+
+  gst_sample_unref (sample);
+
+  return res;
+}
+
+static GstFlowReturn
+gst_rtspsrc_push_backchannel_sample (GstRTSPSrc * src, guint id,
+    GstSample * sample)
+{
   GstFlowReturn res = GST_FLOW_OK;
   GstRTSPStream *stream;
 
@@ -3527,8 +3581,6 @@ gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, guint id,
   }
 
 out:
-  gst_sample_unref (sample);
-
   return res;
 }
 
@@ -3566,10 +3618,8 @@ udpsrc_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
 
   switch (GST_EVENT_TYPE (info->data)) {
     case GST_EVENT_SEGMENT:
-      if (!gst_event_is_writable (info->data))
-        info->data = gst_event_make_writable (info->data);
-
       *segment_seqnum = gst_event_get_seqnum (info->data);
+      break;
     default:
       break;
   }
@@ -3577,13 +3627,25 @@ udpsrc_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
   return GST_PAD_PROBE_OK;
 }
 
+typedef struct
+{
+  GstRTSPSrc *src;
+  GstRTSPStream *stream;
+} CopyStickyEventsData;
+
 static gboolean
 copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
 {
-  GstPad *gpad = GST_PAD_CAST (user_data);
+  CopyStickyEventsData *data = user_data;
+  GstEvent *new_event;
 
-  GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event);
-  gst_pad_store_sticky_event (gpad, *event);
+  GST_DEBUG_OBJECT (data->stream->srcpad, "send sticky event %" GST_PTR_FORMAT,
+      *event);
+  new_event =
+      gst_rtspsrc_update_src_event (data->src, data->stream,
+      gst_event_ref (*event));
+  gst_pad_store_sticky_event (data->stream->srcpad, new_event);
+  gst_event_unref (new_event);
 
   return TRUE;
 }
@@ -3629,6 +3691,7 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src)
   GstRTSPStream *stream;
   gboolean all_added;
   GstPad *internal_src;
+  CopyStickyEventsData copy_sticky_events_data;
 
   GST_DEBUG_OBJECT (src, "got new manager pad %" GST_PTR_FORMAT, pad);
 
@@ -3678,12 +3741,17 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src)
       GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (stream->srcpad)));
   gst_pad_set_element_private (internal_src, stream);
   gst_pad_set_event_function (internal_src, gst_rtspsrc_handle_src_sink_event);
-  gst_object_unref (internal_src);
 
   gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event);
   gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query);
   gst_pad_set_active (stream->srcpad, TRUE);
-  gst_pad_sticky_events_foreach (pad, copy_sticky_events, stream->srcpad);
+
+  copy_sticky_events_data.src = src;
+  copy_sticky_events_data.stream = stream;
+  gst_pad_sticky_events_foreach (pad, copy_sticky_events,
+      &copy_sticky_events_data);
+
+  gst_object_unref (internal_src);
 
   /* don't add the srcpad if this is a sendonly stream */
   if (stream->is_backchannel)
@@ -3756,19 +3824,7 @@ gst_rtspsrc_do_stream_eos (GstRTSPSrc * src, GstRTSPStream * stream)
 {
   GST_DEBUG_OBJECT (src, "setting stream for session %u to EOS", stream->id);
 
-  if (stream->eos)
-    goto was_eos;
-
-  stream->eos = TRUE;
   gst_rtspsrc_stream_push_event (src, stream, gst_event_new_eos ());
-  return;
-
-  /* ERRORS */
-was_eos:
-  {
-    GST_DEBUG_OBJECT (src, "stream for session %u was already EOS", stream->id);
-    return;
-  }
 }
 
 static void
@@ -3797,8 +3853,39 @@ on_timeout_common (GObject * session, GObject * source, GstRTSPStream * stream)
   GST_WARNING_OBJECT (src, "source %08x, stream %08x in session %u timed out",
       ssrc, stream->ssrc, stream->id);
 
-  if (ssrc == stream->ssrc)
-    gst_rtspsrc_do_stream_eos (src, stream);
+  if (ssrc == stream->ssrc) {
+    GList *walk;
+    gboolean all_eos = TRUE;
+
+    GST_DEBUG_OBJECT (src, "setting stream for session %u to EOS", stream->id);
+    stream->eos = TRUE;
+
+    /* Only EOS all streams at once if they're all EOS. Otherwise it is
+     * possible for timed out streams to reappear at a later time time: they
+     * might just be inactive currently.
+     */
+
+    for (walk = src->streams; walk; walk = g_list_next (walk)) {
+      GstRTSPStream *stream = (GstRTSPStream *) walk->data;
+
+      /* Skip streams that were not set up at all */
+      if (!stream->setup)
+        continue;
+
+      if (!stream->eos) {
+        all_eos = FALSE;
+        break;
+      }
+    }
+
+    if (all_eos) {
+      GST_DEBUG_OBJECT (src, "sending EOS on all streams");
+      for (walk = src->streams; walk; walk = g_list_next (walk)) {
+        GstRTSPStream *stream = (GstRTSPStream *) walk->data;
+        gst_rtspsrc_stream_push_event (src, stream, gst_event_new_eos ());
+      }
+    }
+  }
 }
 
 static void
@@ -3839,6 +3926,8 @@ on_ssrc_active (GObject * session, GObject * source, GstRTSPStream * stream)
 {
   GST_DEBUG_OBJECT (stream->parent, "source in session %u is active",
       stream->id);
+
+  stream->eos = FALSE;
 }
 
 static void
@@ -4197,6 +4286,11 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream,
         g_object_set (src->manager, "rfc7273-sync", src->rfc7273_sync, NULL);
       }
 
+      if (g_object_class_find_property (klass, "add-reference-timestamp-meta")) {
+        g_object_set (src->manager, "add-reference-timestamp-meta",
+            src->add_reference_timestamp_meta, NULL);
+      }
+
       if (src->use_pipeline_clock) {
         if (g_object_class_find_property (klass, "use-pipeline-clock")) {
           g_object_set (src->manager, "use-pipeline-clock", TRUE, NULL);
@@ -4535,12 +4629,47 @@ gst_rtspsrc_get_transport_info (GstRTSPSrc * src, GstRTSPStream * stream,
   }
 }
 
+static GstElement *
+element_make_from_addr (const GstURIType type, const char *addr_s,
+    int port, const char *name, GError ** error)
+{
+  GInetAddress *addr;
+  GstElement *element = NULL;
+  char *uri = NULL;
+
+  addr = g_inet_address_new_from_string (addr_s);
+  if (addr == NULL) {
+    /* Address is a hostname, not an IP address */
+    uri = g_strdup_printf ("udp://%s:%i", addr_s, port);
+  } else {
+    switch (g_inet_address_get_family (addr)) {
+      case G_SOCKET_FAMILY_IPV6:
+        uri = g_strdup_printf ("udp://[%s]:%i", addr_s, port);
+        break;
+      case G_SOCKET_FAMILY_INVALID:
+        GST_ERROR ("Unknown family type for %s", addr_s);
+        goto out;
+      case G_SOCKET_FAMILY_UNIX:
+        GST_ERROR ("Unexpected family type UNIX for %s", addr_s);
+        goto out;
+      case G_SOCKET_FAMILY_IPV4:
+        uri = g_strdup_printf ("udp://%s:%i", addr_s, port);
+        break;
+    }
+  }
+
+  element = gst_element_make_from_uri (type, uri, name, error);
+out:
+  g_clear_object (&addr);
+  g_free (uri);
+  return element;
+}
+
 /* For multicast create UDP sources and join the multicast group. */
 static gboolean
 gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream,
     GstRTSPTransport * transport, GstPad ** outpad)
 {
-  gchar *uri;
   const gchar *destination;
   gint min, max;
 
@@ -4565,10 +4694,8 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream,
 
   /* creating UDP source for RTP */
   if (min != -1) {
-    uri = g_strdup_printf ("udp://%s:%d", destination, min);
     stream->udpsrc[0] =
-        gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL);
-    g_free (uri);
+        element_make_from_addr (GST_URI_SRC, destination, min, NULL, NULL);
     if (stream->udpsrc[0] == NULL)
       goto no_element;
 
@@ -4592,10 +4719,8 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream,
   if (max != -1) {
     GstCaps *caps;
 
-    uri = g_strdup_printf ("udp://%s:%d", destination, max);
     stream->udpsrc[1] =
-        gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL);
-    g_free (uri);
+        element_make_from_addr (GST_URI_SRC, destination, max, NULL, NULL);
     if (stream->udpsrc[1] == NULL)
       goto no_element;
 
@@ -4735,7 +4860,7 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src,
   gint rtp_port, rtcp_port;
   gboolean do_rtp, do_rtcp;
   const gchar *destination;
-  gchar *uri, *name;
+  gchar *name;
   guint ttl = 0;
   GSocket *socket;
 
@@ -4759,10 +4884,8 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src,
     GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination,
         rtp_port);
 
-    uri = g_strdup_printf ("udp://%s:%d", destination, rtp_port);
-    stream->udpsink[0] =
-        gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL);
-    g_free (uri);
+    stream->udpsink[0] = element_make_from_addr (GST_URI_SINK, destination,
+        rtp_port, NULL, NULL);
     if (stream->udpsink[0] == NULL)
       goto no_sink_element;
 
@@ -4824,10 +4947,8 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src,
     GST_DEBUG_OBJECT (src, "configure RTCP UDP sink for %s:%d", destination,
         rtcp_port);
 
-    uri = g_strdup_printf ("udp://%s:%d", destination, rtcp_port);
-    stream->udpsink[1] =
-        gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL);
-    g_free (uri);
+    stream->udpsink[1] = element_make_from_addr (GST_URI_SINK, destination,
+        rtcp_port, NULL, NULL);
     if (stream->udpsink[1] == NULL)
       goto no_sink_element;
 
@@ -4980,6 +5101,8 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream,
     add_backchannel_fakesink (src, stream, outpad);
     gst_object_unref (outpad);
   } else if (outpad) {
+    GstPad *internal_src;
+
     GST_DEBUG_OBJECT (src, "creating ghostpad for stream %p", stream);
 
     gst_pad_use_fixed_caps (outpad);
@@ -4994,6 +5117,17 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream,
     gst_object_unref (template);
     g_free (name);
 
+    /* We intercept and modify the stream start event */
+    internal_src =
+        GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (stream->srcpad)));
+    gst_pad_set_element_private (internal_src, stream);
+    gst_pad_set_event_function (internal_src,
+        gst_rtspsrc_handle_src_sink_event);
+    gst_object_unref (internal_src);
+
+    gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event);
+    gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query);
+
     gst_object_unref (outpad);
   }
   /* mark pad as ok */
@@ -5216,11 +5350,22 @@ gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream,
   if (!stream->setup)
     goto done;
 
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+      stream->eos = TRUE;
+      break;
+    case GST_EVENT_FLUSH_STOP:
+      stream->eos = FALSE;
+      break;
+    default:
+      break;
+  }
+
   if (stream->udpsrc[0]) {
     GstEvent *sent_event;
 
-    if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
-      sent_event = gst_event_new_eos ();
+    if (stream->segment_seqnum[0] != GST_SEQNUM_INVALID) {
+      sent_event = gst_event_copy (event);
       gst_event_set_seqnum (sent_event, stream->segment_seqnum[0]);
     } else {
       sent_event = gst_event_ref (event);
@@ -5228,32 +5373,38 @@ gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream,
 
     res = gst_element_send_event (stream->udpsrc[0], sent_event);
   } else if (stream->channelpad[0]) {
-    gst_event_ref (event);
+    GstEvent *sent_event;
+
+    sent_event = gst_event_copy (event);
+    gst_event_set_seqnum (sent_event, src->seek_seqnum);
+
     if (GST_PAD_IS_SRC (stream->channelpad[0]))
-      res = gst_pad_push_event (stream->channelpad[0], event);
+      res = gst_pad_push_event (stream->channelpad[0], sent_event);
     else
-      res = gst_pad_send_event (stream->channelpad[0], event);
+      res = gst_pad_send_event (stream->channelpad[0], sent_event);
   }
 
   if (stream->udpsrc[1]) {
     GstEvent *sent_event;
 
-    if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
-      sent_event = gst_event_new_eos ();
-      if (stream->segment_seqnum[1] != GST_SEQNUM_INVALID) {
-        gst_event_set_seqnum (sent_event, stream->segment_seqnum[1]);
-      }
+    if (stream->segment_seqnum[1] != GST_SEQNUM_INVALID) {
+      sent_event = gst_event_copy (event);
+      gst_event_set_seqnum (sent_event, stream->segment_seqnum[1]);
     } else {
       sent_event = gst_event_ref (event);
     }
 
     res &= gst_element_send_event (stream->udpsrc[1], sent_event);
   } else if (stream->channelpad[1]) {
-    gst_event_ref (event);
+    GstEvent *sent_event;
+
+    sent_event = gst_event_copy (event);
+    gst_event_set_seqnum (sent_event, src->seek_seqnum);
+
     if (GST_PAD_IS_SRC (stream->channelpad[1]))
-      res &= gst_pad_push_event (stream->channelpad[1], event);
+      res &= gst_pad_push_event (stream->channelpad[1], sent_event);
     else
-      res &= gst_pad_send_event (stream->channelpad[1], event);
+      res &= gst_pad_send_event (stream->channelpad[1], sent_event);
   }
 
 done:
@@ -6054,11 +6205,11 @@ interrupt:
   }
 connect_error:
   {
-    gchar *str = gst_rtsp_strresult (res);
     GstFlowReturn ret;
 
     src->conninfo.connected = FALSE;
     if (res != GST_RTSP_EINTR) {
+      gchar *str = gst_rtsp_strresult (res);
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
       gst_rtspsrc_post_error_message (src, GST_RTSPSRC_ERROR_CONNECTION_FAIL,
           "Could not connect to server.");
@@ -6089,11 +6240,11 @@ receive_error:
   }
 handle_request_failed:
   {
-    gchar *str = gst_rtsp_strresult (res);
     GstFlowReturn ret;
 
     gst_rtsp_message_unset (&message);
     if (res != GST_RTSP_EINTR) {
+      gchar *str = gst_rtsp_strresult (res);
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
       gst_rtspsrc_post_error_message (src,
           GST_RTSPSRC_ERROR_SERVICE_UNAVAILABLE,
@@ -6521,21 +6672,21 @@ gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method)
  *
  * At the moment, for Basic auth, we just do a minimal check and don't
  * even parse out the realm */
-static void
+static gboolean
 gst_rtspsrc_parse_auth_hdr (GstRTSPMessage * response,
     GstRTSPAuthMethod * methods, GstRTSPConnection * conn, gboolean * stale)
 {
   GstRTSPAuthCredential **credentials, **credential;
 
-  g_return_if_fail (response != NULL);
-  g_return_if_fail (methods != NULL);
-  g_return_if_fail (stale != NULL);
+  g_return_val_if_fail (response != NULL, FALSE);
+  g_return_val_if_fail (methods != NULL, FALSE);
+  g_return_val_if_fail (stale != NULL, FALSE);
 
   credentials =
       gst_rtsp_message_parse_auth_credentials (response,
       GST_RTSP_HDR_WWW_AUTHENTICATE);
   if (!credentials)
-    return;
+    return FALSE;
 
   credential = credentials;
   while (*credential) {
@@ -6563,6 +6714,8 @@ gst_rtspsrc_parse_auth_hdr (GstRTSPMessage * response,
   }
 
   gst_rtsp_auth_credentials_free (credentials);
+
+  return TRUE;
 }
 
 /**
@@ -6591,10 +6744,14 @@ gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response)
   GstRTSPConnection *conn;
   gboolean stale = FALSE;
 
+  g_return_val_if_fail (response != NULL, FALSE);
+
   conn = src->conninfo.connection;
 
-  /* Identify the available auth methods and see if any are supported */
-  gst_rtspsrc_parse_auth_hdr (response, &avail_methods, conn, &stale);
+  /* Identify the available auth methods and see if any are supported. If no
+   * headers were found, propagate the HTTP error. */
+  if (!gst_rtspsrc_parse_auth_hdr (response, &avail_methods, conn, &stale))
+    goto propagate_error;
 
   if (avail_methods == GST_RTSP_AUTH_NONE)
     goto no_auth_available;
@@ -6626,9 +6783,10 @@ gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response)
    * already, request a username and passwd from the application via some kind
    * of credentials request message */
 
-  /* If we don't have a username and passwd at this point, bail out. */
+  /* If we don't have a username and passwd at this point, bail out and
+   * propagate the normal NOT_AUTHORIZED error. */
   if (user == NULL || pass == NULL)
-    goto no_user_pass;
+    goto propagate_error;
 
   /* Try to configure for each available authentication method, strongest to
    * weakest */
@@ -6666,10 +6824,11 @@ no_auth_available:
 #endif
     return FALSE;
   }
-no_user_pass:
+
+propagate_error:
   {
     /* We don't fire an error message, we just return FALSE and let the
-     * normal NOT_AUTHORIZED error be propagated */
+     * normal error be propagated */
     return FALSE;
   }
 }
@@ -7640,6 +7799,7 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
     GstRTSPConnInfo *conninfo;
     gchar *transports;
     gint retry = 0;
+    gboolean tried_non_compliant_url = FALSE;
     guint mask = 0;
     gboolean selected;
     GstCaps *caps;
@@ -7832,6 +7992,47 @@ gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async)
           continue;
         else
           goto retry;
+      case GST_RTSP_STS_BAD_REQUEST:
+      case GST_RTSP_STS_NOT_FOUND:
+        /* There are various non-compliant servers that don't require control
+         * URLs that are not resolved correctly but instead are just appended.
+         * See e.g.
+         *   https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/922
+         *   https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1447
+         */
+        if (!tried_non_compliant_url && stream->control_url
+            && !g_str_has_prefix (stream->control_url, "rtsp://")) {
+          const gchar *base;
+
+          gst_rtsp_message_unset (&request);
+          gst_rtsp_message_unset (&response);
+          gst_rtspsrc_stream_free_udp (stream);
+
+          g_free (stream->conninfo.location);
+          base = get_aggregate_control (src);
+
+          /* Make sure to not accumulate too many `/` */
+          if ((g_str_has_suffix (base, "/")
+                  && !g_str_has_suffix (stream->control_url, "/"))
+              || (!g_str_has_suffix (base, "/")
+                  && g_str_has_suffix (stream->control_url, "/"))
+              )
+            stream->conninfo.location =
+                g_strconcat (base, stream->control_url, NULL);
+          else if (g_str_has_suffix (base, "/")
+              && g_str_has_suffix (stream->control_url, "/"))
+            stream->conninfo.location =
+                g_strconcat (base, stream->control_url + 1, NULL);
+          else
+            stream->conninfo.location =
+                g_strconcat (base, "/", stream->control_url, NULL);
+
+          tried_non_compliant_url = TRUE;
+
+          goto retry;
+        }
+
+        /* fall through */
       default:
         /* cleanup of leftover transport and move to the next stream */
         gst_rtspsrc_stream_free_udp (stream);
@@ -9722,6 +9923,7 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition)
         goto start_failed;
       break;
     case GST_STATE_CHANGE_READY_TO_PAUSED:
+      rtspsrc->seek_seqnum = gst_util_seqnum_next ();
       /* init some state */
       rtspsrc->cur_protocols = rtspsrc->protocols;
       /* first attempt, don't ignore timeouts */
@@ -9835,12 +10037,12 @@ gst_rtspsrc_send_event (GstElement * element, GstEvent * event)
   if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
     if (rtspsrc->state >= GST_RTSP_STATE_READY) {
       res = gst_rtspsrc_perform_seek (rtspsrc, event);
-      gst_event_unref (event);
     } else {
       /* Store for later use */
       res = TRUE;
-      rtspsrc->initial_seek = event;
+      gst_event_replace (&rtspsrc->initial_seek, event);
     }
+    gst_event_unref (event);
   } else if (GST_EVENT_IS_DOWNSTREAM (event)) {
     res = gst_rtspsrc_push_event (rtspsrc, event);
   } else {