Implement support for ULP Forward Error Correction
authorMathieu Duponchelle <mathieu@centricular.com>
Mon, 2 Apr 2018 20:49:35 +0000 (22:49 +0200)
committerMathieu Duponchelle <mathieu@centricular.com>
Thu, 19 Apr 2018 16:25:31 +0000 (18:25 +0200)
In this initial commit, interface is only exposed for RECORD,
further work will be needed in rtspsrc to support this for
PLAY.

https://bugzilla.gnome.org/show_bug.cgi?id=794911

docs/libs/gst-rtsp-server-sections.txt
gst/rtsp-server/rtsp-media.c
gst/rtsp-server/rtsp-sdp.c
gst/rtsp-server/rtsp-stream.c
gst/rtsp-server/rtsp-stream.h
gst/rtsp-sink/gstrtspclientsink.c
gst/rtsp-sink/gstrtspclientsink.h

index 956ac0a..3e43110 100644 (file)
@@ -630,6 +630,13 @@ gst_rtsp_stream_seekable
 GstRTSPStreamTransportFilterFunc
 gst_rtsp_stream_transport_filter
 
+gst_rtsp_stream_set_ulpfec_pt
+gst_rtsp_stream_get_ulpfec_pt
+gst_rtsp_stream_set_ulpfec_percentage
+gst_rtsp_stream_get_ulpfec_percentage
+gst_rtsp_stream_request_ulpfec_decoder
+gst_rtsp_stream_request_ulpfec_encoder
+
 <SUBSECTION Standard>
 GST_RTSP_STREAM_CAST
 GST_RTSP_STREAM_CLASS_CAST
index 3614796..0427b6b 100644 (file)
@@ -1517,6 +1517,7 @@ void
 gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
 {
   GstRTSPMediaPrivate *priv;
+  guint i;
 
   g_return_if_fail (GST_IS_RTSP_MEDIA (media));
 
@@ -1526,8 +1527,19 @@ gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
 
   g_mutex_lock (&priv->lock);
   priv->latency = latency;
-  if (priv->rtpbin)
+  if (priv->rtpbin) {
     g_object_set (priv->rtpbin, "latency", latency, NULL);
+
+    for (i = 0; i < media->priv->streams->len; i++) {
+      GObject *storage = NULL;
+
+      g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage",
+          i, &storage);
+      if (storage)
+        g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND, NULL);
+    }
+  }
+
   g_mutex_unlock (&priv->lock);
 }
 
@@ -3108,6 +3120,38 @@ request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
   return res;
 }
 
+static GstElement *
+request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+{
+  GstRTSPMediaPrivate *priv = media->priv;
+  GstRTSPStream *stream = NULL;
+  guint i;
+  GstElement *res = NULL;
+
+  g_mutex_lock (&priv->lock);
+  for (i = 0; i < priv->streams->len; i++) {
+    stream = g_ptr_array_index (priv->streams, i);
+
+    if (sessid == gst_rtsp_stream_get_index (stream))
+      break;
+
+    stream = NULL;
+  }
+  g_mutex_unlock (&priv->lock);
+
+  if (stream) {
+    res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid);
+  }
+
+  return res;
+}
+
+static void
+new_storage_cb (GstElement * rtpbin, GObject * storage, guint sessid, GstRTSPMedia * media)
+{
+  g_object_set (storage, "size-time", (media->priv->latency + 50) * GST_MSECOND, NULL);
+}
+
 static gboolean
 start_prepare (GstRTSPMedia * media)
 {
@@ -3119,6 +3163,9 @@ start_prepare (GstRTSPMedia * media)
   if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
     goto no_longer_preparing;
 
+  g_signal_connect (priv->rtpbin, "new-storage", G_CALLBACK (new_storage_cb), media);
+  g_signal_connect (priv->rtpbin, "request-fec-decoder", G_CALLBACK (request_fec_decoder), media);
+
   /* link streams we already have, other streams might appear when we have
    * dynamic elements */
   for (i = 0; i < priv->streams->len; i++) {
@@ -3145,7 +3192,7 @@ start_prepare (GstRTSPMedia * media)
 
   if (priv->rtpbin)
     g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission,
-        NULL);
+        "do-lost", TRUE, NULL);
 
   for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
     GstElement *elem = walk->data;
@@ -3806,6 +3853,9 @@ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
       s = gst_caps_get_structure (caps, 0);
       gst_structure_set_name (s, "application/x-rtp");
 
+      if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+        gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);
+
       gst_rtsp_stream_set_pt_map (stream, pt, caps);
       gst_caps_unref (caps);
     }
index ff55c02..3795b42 100644 (file)
@@ -373,40 +373,75 @@ gst_rtsp_sdp_make_media (GstSDPMessage * sdp, GstSDPInfo * info,
 
   update_sdp_from_tags (stream, smedia);
 
-  if ((profile == GST_RTSP_PROFILE_AVPF || profile == GST_RTSP_PROFILE_SAVPF)
-      && (rtx_time = gst_rtsp_stream_get_retransmission_time (stream))) {
-    /* ssrc multiplexed retransmit functionality */
-    guint rtx_pt = gst_rtsp_stream_get_retransmission_pt (stream);
-
-    if (rtx_pt == 0) {
-      g_warning ("failed to find an available dynamic payload type. "
-          "Not adding retransmission");
-    } else {
-      gchar *tmp;
-      GstStructure *s;
-      gint caps_pt, caps_rate;
-
-      s = gst_caps_get_structure (caps, 0);
-      if (s == NULL)
-        goto no_caps_info;
-
-      /* get payload type and clock rate */
-      gst_structure_get_int (s, "payload", &caps_pt);
-      gst_structure_get_int (s, "clock-rate", &caps_rate);
-
-      tmp = g_strdup_printf ("%d", rtx_pt);
-      gst_sdp_media_add_format (smedia, tmp);
-      g_free (tmp);
-
-      tmp = g_strdup_printf ("%d rtx/%d", rtx_pt, caps_rate);
-      gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
-      g_free (tmp);
+  if (profile == GST_RTSP_PROFILE_AVPF || profile == GST_RTSP_PROFILE_SAVPF) {
+    if ((rtx_time = gst_rtsp_stream_get_retransmission_time (stream))) {
+      /* ssrc multiplexed retransmit functionality */
+      guint rtx_pt = gst_rtsp_stream_get_retransmission_pt (stream);
+
+      if (rtx_pt == 0) {
+        g_warning ("failed to find an available dynamic payload type. "
+            "Not adding retransmission");
+      } else {
+        gchar *tmp;
+        GstStructure *s;
+        gint caps_pt, caps_rate;
+
+        s = gst_caps_get_structure (caps, 0);
+        if (s == NULL)
+          goto no_caps_info;
+
+        /* get payload type and clock rate */
+        gst_structure_get_int (s, "payload", &caps_pt);
+        gst_structure_get_int (s, "clock-rate", &caps_rate);
+
+        tmp = g_strdup_printf ("%d", rtx_pt);
+        gst_sdp_media_add_format (smedia, tmp);
+        g_free (tmp);
+
+        tmp = g_strdup_printf ("%d rtx/%d", rtx_pt, caps_rate);
+        gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
+        g_free (tmp);
+
+        tmp =
+            g_strdup_printf ("%d apt=%d;rtx-time=%" G_GINT64_FORMAT, rtx_pt,
+            caps_pt, GST_TIME_AS_MSECONDS (rtx_time));
+        gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
+        g_free (tmp);
+      }
+    }
 
-      tmp =
-          g_strdup_printf ("%d apt=%d;rtx-time=%" G_GINT64_FORMAT, rtx_pt,
-          caps_pt, GST_TIME_AS_MSECONDS (rtx_time));
-      gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
-      g_free (tmp);
+    if (gst_rtsp_stream_get_ulpfec_percentage (stream)) {
+      guint ulpfec_pt = gst_rtsp_stream_get_ulpfec_pt (stream);
+
+      if (ulpfec_pt == 0) {
+        g_warning ("failed to find an available dynamic payload type. "
+            "Not adding ulpfec");
+      } else {
+        gchar *tmp;
+        GstStructure *s;
+        gint caps_pt, caps_rate;
+
+        s = gst_caps_get_structure (caps, 0);
+        if (s == NULL)
+          goto no_caps_info;
+
+        /* get payload type and clock rate */
+        gst_structure_get_int (s, "payload", &caps_pt);
+        gst_structure_get_int (s, "clock-rate", &caps_rate);
+
+        tmp = g_strdup_printf ("%d", ulpfec_pt);
+        gst_sdp_media_add_format (smedia, tmp);
+        g_free (tmp);
+
+        tmp = g_strdup_printf ("%d ulpfec/%d", ulpfec_pt, caps_rate);
+        gst_sdp_media_add_attribute (smedia, "rtpmap", tmp);
+        g_free (tmp);
+
+        tmp =
+            g_strdup_printf ("%d apt=%d", ulpfec_pt, caps_pt);
+        gst_sdp_media_add_attribute (smedia, "fmtp", tmp);
+        g_free (tmp);
+      }
     }
   }
 
index 8b7bff7..40c1224 100644 (file)
@@ -128,6 +128,13 @@ struct _GstRTSPStreamPrivate
   guint rtx_pt;
   GstClockTime rtx_time;
 
+  /* Forward Error Correction with RFC 5109 */
+  GstElement *ulpfec_decoder;
+  GstElement *ulpfec_encoder;
+  guint ulpfec_pt;
+  gboolean ulpfec_enabled;
+  guint ulpfec_percentage;
+
   /* pool used to manage unicast and multicast addresses */
   GstRTSPAddressPool *pool;
 
@@ -301,6 +308,10 @@ gst_rtsp_stream_finalize (GObject * obj)
     g_object_unref (priv->rtxsend);
   if (priv->rtxreceive)
     g_object_unref (priv->rtxreceive);
+  if (priv->ulpfec_encoder)
+    gst_object_unref (priv->ulpfec_encoder);
+  if (priv->ulpfec_decoder)
+    gst_object_unref (priv->ulpfec_decoder);
 
   for (i = 0; i < 2; i++) {
     if (priv->socket_v4[i])
@@ -2362,6 +2373,28 @@ done:
   return;
 }
 
+static void
+retrieve_ulpfec_pt (gpointer key, GstCaps *caps, GstElement *ulpfec_decoder)
+{
+  guint pt = GPOINTER_TO_INT (key);
+  const GstStructure *s = gst_caps_get_structure (caps, 0);
+
+  if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+    g_object_set (ulpfec_decoder, "pt", pt, NULL);
+}
+
+static void
+update_ulpfec_decoder_pt (GstRTSPStream * stream)
+{
+  if (!stream->priv->ulpfec_decoder)
+    goto done;
+
+  g_hash_table_foreach (stream->priv->ptmap, (GHFunc) retrieve_ulpfec_pt, stream->priv->ulpfec_decoder);
+
+done:
+  return;
+}
+
 /**
  * gst_rtsp_stream_request_aux_receiver:
  * @stream: a #GstRTSPStream
@@ -2385,6 +2418,7 @@ gst_rtsp_stream_request_aux_receiver (GstRTSPStream * stream, guint sessid)
   bin = gst_bin_new (NULL);
   stream->priv->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
   update_rtx_receive_pt_map (stream);
+  update_ulpfec_decoder_pt (stream);
   gst_bin_add (GST_BIN (bin), gst_object_ref (stream->priv->rtxreceive));
 
   pad = gst_element_get_static_pad (stream->priv->rtxreceive, "src");
@@ -4982,3 +5016,133 @@ gst_rtsp_stream_handle_keymgmt (GstRTSPStream * stream, const gchar * keymgmt)
   g_strfreev (specs);
   return TRUE;
 }
+
+
+/**
+ * gst_rtsp_stream_get_ulpfec_pt:
+ *
+ * Returns: the payload type used for ULPFEC protection packets
+ *
+ * Since: 1.16
+ */
+guint
+gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream *stream)
+{
+  guint res;
+
+  g_mutex_lock (&stream->priv->lock);
+  res = stream->priv->ulpfec_pt;
+  g_mutex_unlock (&stream->priv->lock);
+
+  return res;
+}
+
+/**
+ * gst_rtsp_stream_set_ulpfec_pt:
+ *
+ * Set the payload type to be used for ULPFEC protection packets
+ *
+ * Since: 1.16
+ */
+void
+gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream * stream, guint pt)
+{
+  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+  g_mutex_lock (&stream->priv->lock);
+  stream->priv->ulpfec_pt = pt;
+  if (stream->priv->ulpfec_encoder) {
+    g_object_set (stream->priv->ulpfec_encoder, "pt", pt, NULL);
+  }
+  g_mutex_unlock (&stream->priv->lock);
+}
+
+/**
+ * gst_rtsp_stream_request_ulpfec_decoder:
+ *
+ * Creating a rtpulpfecdec element
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.16
+ */
+GstElement *
+gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream * stream, GstElement *rtpbin, guint sessid)
+{
+  GObject *internal_storage = NULL;
+
+  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+  stream->priv->ulpfec_decoder = gst_object_ref (gst_element_factory_make ("rtpulpfecdec", NULL));
+
+  g_signal_emit_by_name (G_OBJECT (rtpbin), "get-internal-storage", sessid, &internal_storage);
+  g_object_set (stream->priv->ulpfec_decoder, "storage", internal_storage, NULL);
+  g_object_unref (internal_storage);
+  update_ulpfec_decoder_pt (stream);
+
+  return stream->priv->ulpfec_decoder;
+}
+
+/**
+ * gst_rtsp_stream_request_ulpfec_encoder:
+ *
+ * Creating a rtpulpfecenc element
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.16
+ */
+GstElement *
+gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream * stream, guint sessid)
+{
+  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+  if (!stream->priv->ulpfec_percentage)
+    return NULL;
+
+  stream->priv->ulpfec_encoder = gst_object_ref (gst_element_factory_make ("rtpulpfecenc", NULL));
+
+  g_object_set (stream->priv->ulpfec_encoder, "pt", stream->priv->ulpfec_pt, "percentage", stream->priv->ulpfec_percentage, NULL);
+
+  return stream->priv->ulpfec_encoder;
+}
+
+/**
+ * gst_rtsp_stream_set_ulpfec_percentage:
+ *
+ * Sets the amount of redundancy to apply when creating ULPFEC
+ * protection packets.
+ *
+ * Since: 1.16
+ */
+void
+gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream *stream, guint percentage)
+{
+  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+  g_mutex_lock (&stream->priv->lock);
+  stream->priv->ulpfec_percentage = percentage;
+  if (stream->priv->ulpfec_encoder) {
+    g_object_set (stream->priv->ulpfec_encoder, "percentage", percentage, NULL);
+  }
+  g_mutex_unlock (&stream->priv->lock);
+}
+
+/**
+ * gst_rtsp_stream_get_ulpfec_percentage:
+ *
+ * Returns: the amount of redundancy applied when creating ULPFEC
+ * protection packets.
+ *
+ * Since: 1.16
+ */
+guint
+gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream *stream)
+{
+  guint res;
+
+  g_mutex_lock (&stream->priv->lock);
+  res = stream->priv->ulpfec_percentage;
+  g_mutex_unlock (&stream->priv->lock);
+
+  return res;
+}
index 89839f8..1f527a4 100644 (file)
@@ -307,6 +307,28 @@ gboolean           gst_rtsp_stream_is_receiver (GstRTSPStream * stream);
 GST_RTSP_SERVER_API
 gboolean           gst_rtsp_stream_handle_keymgmt (GstRTSPStream *stream, const gchar *keymgmt);
 
+/* ULP Forward Error Correction (RFC 5109) */
+GST_RTSP_SERVER_API
+gboolean           gst_rtsp_stream_get_ulpfec_enabled (GstRTSPStream *stream);
+
+GST_RTSP_SERVER_API
+void               gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream *stream, guint pt);
+
+GST_RTSP_SERVER_API
+guint              gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream *stream);
+
+GST_RTSP_SERVER_API
+GstElement *       gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream *stream, GstElement *rtpbin, guint sessid);
+
+GST_RTSP_SERVER_API
+GstElement *       gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream *stream, guint sessid);
+
+GST_RTSP_SERVER_API
+void               gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream *stream, guint percentage);
+
+GST_RTSP_SERVER_API
+guint              gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream *stream);
+
 /**
  * GstRTSPStreamTransportFilterFunc:
  * @stream: a #GstRTSPStream object
index f400a85..5d37c56 100644 (file)
@@ -109,14 +109,18 @@ struct _GstRtspClientSinkPad
 {
   GstGhostPad parent;
   GstElement *custom_payloader;
+  guint ulpfec_percentage;
 };
 
 enum
 {
   PROP_PAD_0,
-  PROP_PAD_PAYLOADER
+  PROP_PAD_PAYLOADER,
+  PROP_PAD_ULPFEC_PERCENTAGE
 };
 
+#define DEFAULT_PAD_ULPFEC_PERCENTAGE 0
+
 static GType gst_rtsp_client_sink_pad_get_type (void);
 G_DEFINE_TYPE (GstRtspClientSinkPad, gst_rtsp_client_sink_pad,
     GST_TYPE_GHOST_PAD);
@@ -140,6 +144,11 @@ gst_rtsp_client_sink_pad_set_property (GObject * object, guint prop_id,
       gst_object_ref_sink (pad->custom_payloader);
       GST_OBJECT_UNLOCK (pad);
       break;
+    case PROP_PAD_ULPFEC_PERCENTAGE:
+      GST_OBJECT_LOCK (pad);
+      pad->ulpfec_percentage = g_value_get_uint (value);
+      GST_OBJECT_UNLOCK (pad);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -160,6 +169,11 @@ gst_rtsp_client_sink_pad_get_property (GObject * object, guint prop_id,
       g_value_set_object (value, pad->custom_payloader);
       GST_OBJECT_UNLOCK (pad);
       break;
+    case PROP_PAD_ULPFEC_PERCENTAGE:
+      GST_OBJECT_LOCK (pad);
+      g_value_set_uint (value, pad->ulpfec_percentage);
+      GST_OBJECT_UNLOCK (pad);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -192,6 +206,11 @@ gst_rtsp_client_sink_pad_class_init (GstRtspClientSinkPadClass * klass)
       g_param_spec_object ("payloader", "Payloader",
           "The payloader element to use (NULL = default automatically selected)",
           GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_klass, PROP_PAD_ULPFEC_PERCENTAGE,
+      g_param_spec_uint ("ulpfec-percentage", "ULPFEC percentage",
+          "The percentage of ULP redundancy to apply", 0, 100, DEFAULT_PAD_ULPFEC_PERCENTAGE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -1099,7 +1118,7 @@ gst_rtsp_client_sink_create_stream (GstRTSPClientSink * sink,
     GstRTSPStreamContext * context, GstElement * payloader, GstPad * pad)
 {
   GstRTSPStream *stream = NULL;
-  guint pt, aux_pt;
+  guint pt, aux_pt, ulpfec_pt;
 
   GST_OBJECT_LOCK (sink);
 
@@ -1125,6 +1144,11 @@ gst_rtsp_client_sink_create_stream (GstRTSPClientSink * sink,
     goto no_free_pt;
   sink->next_dyn_pt++;
 
+  ulpfec_pt = sink->next_dyn_pt;
+  if (ulpfec_pt > 127)
+    goto no_free_pt;
+  sink->next_dyn_pt++;
+
   GST_OBJECT_UNLOCK (sink);
 
 
@@ -1143,6 +1167,9 @@ gst_rtsp_client_sink_create_stream (GstRTSPClientSink * sink,
     gst_rtsp_stream_set_mtu (stream, sink->rtp_blocksize);
   gst_rtsp_stream_set_multicast_iface (stream, sink->multi_iface);
 
+  gst_rtsp_stream_set_ulpfec_pt (stream, ulpfec_pt);
+  gst_rtsp_stream_set_ulpfec_percentage (stream, context->ulpfec_percentage);
+
 #if 0
   if (priv->pool)
     gst_rtsp_stream_set_address_pool (stream, priv->pool);
@@ -1229,6 +1256,8 @@ gst_rtsp_client_sink_setup_payloader (GstRTSPClientSink * sink, GstPad * pad,
   gst_object_unref (GST_OBJECT (sinkpad));
   GST_RTSP_STATE_UNLOCK (sink);
 
+  context->ulpfec_percentage = cspad->ulpfec_percentage;
+
   gst_element_sync_state_with_parent (payloader);
 
   gst_object_unref (payloader);
@@ -3502,6 +3531,32 @@ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPClientSink * sink)
   return ret;
 }
 
+static GstElement *
+request_fec_encoder (GstElement * rtpbin, guint sessid, GstRTSPClientSink * sink)
+{
+  GstRTSPStream *stream = NULL;
+  GstElement *ret = NULL;
+  GList *walk;
+
+  GST_RTSP_STATE_LOCK (sink);
+  for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+    GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+    if (sessid == gst_rtsp_stream_get_index (context->stream)) {
+      stream = context->stream;
+      break;
+    }
+  }
+
+  if (stream != NULL) {
+    ret = gst_rtsp_stream_request_ulpfec_encoder (stream, sessid);
+  }
+
+  GST_RTSP_STATE_UNLOCK (sink);
+
+  return ret;
+}
+
 static gboolean
 gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink)
 {
@@ -3568,6 +3623,9 @@ gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink)
           (GCallback) request_aux_sender, sink);
     }
 
+    g_signal_connect (sink->rtpbin, "request-fec-encoder",
+        (GCallback) request_fec_encoder, sink);
+
     if (!gst_rtsp_stream_join_bin (context->stream,
             GST_BIN (sink->internal_bin), sink->rtpbin, GST_STATE_PAUSED)) {
       goto join_bin_failed;
index 9082530..38230fa 100644 (file)
@@ -118,6 +118,8 @@ struct _GstRTSPStreamContext {
   guint8        channel[2];
 
   GstRTSPStreamTransport *stream_transport;
+
+  guint ulpfec_percentage;
 };
 
 /**