rtsp-stream: Compare IP address strings case insensitive
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-stream.c
index 4554414..c57d13d 100644 (file)
@@ -94,21 +94,18 @@ struct _GstRTSPStreamPrivate
   GstElement *srtpdec;
   GHashTable *keys;
 
-  /* sinks used for sending and receiving RTP and RTCP over ipv4, they share
-   * sockets */
+  /* for UDP unicast */
   GstElement *udpsrc_v4[2];
-  /* UDP sources for UDP multicast transports */
-  GstElement *udpsrc_mcast_v4[2];
-
-  /* sinks used for sending and receiving RTP and RTCP over ipv6, they share
-   * sockets */
   GstElement *udpsrc_v6[2];
-  /* UDP sources for UDP multicast transports */
-  GstElement *udpsrc_mcast_v6[2];
-
   GstElement *udpqueue[2];
   GstElement *udpsink[2];
 
+  /* for UDP multicast */
+  GstElement *mcast_udpsrc_v4[2];
+  GstElement *mcast_udpsrc_v6[2];
+  GstElement *mcast_udpqueue[2];
+  GstElement *mcast_udpsink[2];
+
   /* for TCP transport */
   GstElement *appsrc[2];
   GstClockTime appsrc_base_time[2];
@@ -123,22 +120,16 @@ struct _GstRTSPStreamPrivate
   guint rtx_pt;
   GstClockTime rtx_time;
 
-  /* server ports for sending/receiving over ipv4 */
-  GstRTSPRange server_port_v4;
-  GstRTSPAddress *server_addr_v4;
-  gboolean have_ipv4;
+  /* pool used to manage unicast and multicast addresses */
+  GstRTSPAddressPool *pool;
 
-  /* server ports for sending/receiving over ipv6 */
-  GstRTSPRange server_port_v6;
+  /* unicast server addr/port */
+  GstRTSPAddress *server_addr_v4;
   GstRTSPAddress *server_addr_v6;
-  gboolean have_ipv6;
 
   /* multicast addresses */
-  GstRTSPAddressPool *pool;
   GstRTSPAddress *mcast_addr_v4;
   GstRTSPAddress *mcast_addr_v6;
-  gboolean have_ipv4_mcast;
-  gboolean have_ipv6_mcast;
 
   gchar *multicast_iface;
 
@@ -155,7 +146,6 @@ struct _GstRTSPStreamPrivate
   guint tr_cache_cookie_rtp;
   guint tr_cache_cookie_rtcp;
 
-
   gint dscp_qos;
 
   /* stream blocking */
@@ -595,22 +585,18 @@ gst_rtsp_stream_get_mtu (GstRTSPStream * stream)
 
 /* Update the dscp qos property on the udp sinks */
 static void
-update_dscp_qos (GstRTSPStream * stream)
+update_dscp_qos (GstRTSPStream * stream, GstElement * udpsink[2])
 {
   GstRTSPStreamPrivate *priv;
 
-  g_return_if_fail (GST_IS_RTSP_STREAM (stream));
-
   priv = stream->priv;
 
-  if (priv->udpsink[0]) {
-    g_object_set (G_OBJECT (priv->udpsink[0]), "qos-dscp", priv->dscp_qos,
-        NULL);
+  if (udpsink[0]) {
+    g_object_set (G_OBJECT (udpsink[0]), "qos-dscp", priv->dscp_qos, NULL);
   }
 
-  if (priv->udpsink[1]) {
-    g_object_set (G_OBJECT (priv->udpsink[1]), "qos-dscp", priv->dscp_qos,
-        NULL);
+  if (udpsink[1]) {
+    g_object_set (G_OBJECT (udpsink[1]), "qos-dscp", priv->dscp_qos, NULL);
   }
 }
 
@@ -639,7 +625,7 @@ gst_rtsp_stream_set_dscp_qos (GstRTSPStream * stream, gint dscp_qos)
 
   priv->dscp_qos = dscp_qos;
 
-  update_dscp_qos (stream);
+  update_dscp_qos (stream, priv->udpsink);
 }
 
 /**
@@ -927,21 +913,9 @@ gst_rtsp_stream_get_multicast_iface (GstRTSPStream * stream)
   return result;
 }
 
-/**
- * gst_rtsp_stream_get_multicast_address:
- * @stream: a #GstRTSPStream
- * @family: the #GSocketFamily
- *
- * Get the multicast address of @stream for @family. The original
- * #GstRTSPAddress is cached and copy is returned, so freeing the return value
- * won't release the address from the pool.
- *
- * Returns: (transfer full) (nullable): the #GstRTSPAddress of @stream
- * or %NULL when no address could be allocated. gst_rtsp_address_free()
- * after usage.
- */
-GstRTSPAddress *
-gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
+
+static GstRTSPAddress *
+gst_rtsp_stream_get_multicast_address_locked (GstRTSPStream * stream,
     GSocketFamily family)
 {
   GstRTSPStreamPrivate *priv;
@@ -949,8 +923,6 @@ gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
   GstRTSPAddress **addrp;
   GstRTSPAddressFlags flags;
 
-  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
-
   priv = stream->priv;
 
   if (family == G_SOCKET_FAMILY_IPV6) {
@@ -961,7 +933,6 @@ gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
     addrp = &priv->mcast_addr_v4;
   }
 
-  g_mutex_lock (&priv->lock);
   if (*addrp == NULL) {
     if (priv->pool == NULL)
       goto no_pool;
@@ -971,9 +942,14 @@ gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
     *addrp = gst_rtsp_address_pool_acquire_address (priv->pool, flags, 2);
     if (*addrp == NULL)
       goto no_address;
+
+    /* FIXME: Also reserve the same port with unicast ANY address, since that's
+     * where we are going to bind our socket. Probably loop until we find a port
+     * available in both mcast and unicast pools. Maybe GstRTSPAddressPool
+     * should do it for us when both GST_RTSP_ADDRESS_FLAG_MULTICAST and
+     * GST_RTSP_ADDRESS_FLAG_UNICAST are givent. */
   }
   result = gst_rtsp_address_copy (*addrp);
-  g_mutex_unlock (&priv->lock);
 
   return result;
 
@@ -981,18 +957,44 @@ gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
 no_pool:
   {
     GST_ERROR_OBJECT (stream, "no address pool specified");
-    g_mutex_unlock (&priv->lock);
     return NULL;
   }
 no_address:
   {
     GST_ERROR_OBJECT (stream, "failed to acquire address from pool");
-    g_mutex_unlock (&priv->lock);
     return NULL;
   }
 }
 
 /**
+ * gst_rtsp_stream_get_multicast_address:
+ * @stream: a #GstRTSPStream
+ * @family: the #GSocketFamily
+ *
+ * Get the multicast address of @stream for @family. The original
+ * #GstRTSPAddress is cached and copy is returned, so freeing the return value
+ * won't release the address from the pool.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAddress of @stream
+ * or %NULL when no address could be allocated. gst_rtsp_address_free()
+ * after usage.
+ */
+GstRTSPAddress *
+gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
+    GSocketFamily family)
+{
+  GstRTSPAddress *result;
+
+  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+  g_mutex_lock (&stream->priv->lock);
+  result = gst_rtsp_stream_get_multicast_address_locked (stream, family);
+  g_mutex_unlock (&stream->priv->lock);
+
+  return result;
+}
+
+/**
  * gst_rtsp_stream_reserve_address:
  * @stream: a #GstRTSPStream
  * @address: an address
@@ -1050,8 +1052,11 @@ gst_rtsp_stream_reserve_address (GstRTSPStream * stream,
         port, n_ports, ttl, addrp);
     if (res != GST_RTSP_ADDRESS_POOL_OK)
       goto no_address;
+
+    /* FIXME: Also reserve the same port with unicast ANY address, since that's
+     * where we are going to bind our socket. */
   } else {
-    if (strcmp ((*addrp)->address, address) ||
+    if (g_ascii_strcasecmp ((*addrp)->address, address) ||
         (*addrp)->port != port || (*addrp)->n_ports != n_ports ||
         (*addrp)->ttl != ttl)
       goto different_address;
@@ -1077,8 +1082,9 @@ no_address:
   }
 different_address:
   {
-    GST_ERROR_OBJECT (stream, "address %s is not the same that was already"
-        " reserved", address);
+    GST_ERROR_OBJECT (stream,
+        "address %s is not the same as %s that was already" " reserved",
+        address, (*addrp)->address);
     g_mutex_unlock (&priv->lock);
     return NULL;
   }
@@ -1086,10 +1092,9 @@ different_address:
 
 /* must be called with lock */
 static void
-set_sockets_for_udpsinks (GstRTSPStream * stream, GSocket * rtp_socket,
+set_sockets_for_udpsinks (GstElement * udpsink[2], GSocket * rtp_socket,
     GSocket * rtcp_socket, GSocketFamily family)
 {
-  GstRTSPStreamPrivate *priv = stream->priv;
   const gchar *multisink_socket;
 
   if (family == G_SOCKET_FAMILY_IPV6)
@@ -1097,36 +1102,20 @@ set_sockets_for_udpsinks (GstRTSPStream * stream, GSocket * rtp_socket,
   else
     multisink_socket = "socket";
 
-  g_object_set (G_OBJECT (priv->udpsink[0]), multisink_socket, rtp_socket,
-      NULL);
-  g_object_set (G_OBJECT (priv->udpsink[1]), multisink_socket, rtcp_socket,
-      NULL);
+  g_object_set (G_OBJECT (udpsink[0]), multisink_socket, rtp_socket, NULL);
+  g_object_set (G_OBJECT (udpsink[1]), multisink_socket, rtcp_socket, NULL);
 }
 
-/* must be called with lock */
 static gboolean
-create_and_configure_udpsinks (GstRTSPStream * stream)
+create_and_configure_udpsinks (GstRTSPStream * stream, GstElement * udpsink[2])
 {
   GstRTSPStreamPrivate *priv = stream->priv;
   GstElement *udpsink0, *udpsink1;
 
-  udpsink0 = NULL;
-  udpsink1 = NULL;
-
-  if (priv->udpsink[0])
-    udpsink0 = priv->udpsink[0];
-  else
-    udpsink0 = gst_element_factory_make ("multiudpsink", NULL);
-
-  if (!udpsink0)
-    goto no_udp_protocol;
-
-  if (priv->udpsink[1])
-    udpsink1 = priv->udpsink[1];
-  else
-    udpsink1 = gst_element_factory_make ("multiudpsink", NULL);
+  udpsink0 = gst_element_factory_make ("multiudpsink", NULL);
+  udpsink1 = gst_element_factory_make ("multiudpsink", NULL);
 
-  if (!udpsink1)
+  if (!udpsink0 || !udpsink1)
     goto no_udp_protocol;
 
   /* configure sinks */
@@ -1147,17 +1136,20 @@ create_and_configure_udpsinks (GstRTSPStream * stream)
     g_object_set (G_OBJECT (udpsink0), "async", FALSE, NULL);
   g_object_set (G_OBJECT (udpsink1), "async", FALSE, NULL);
 
-  g_object_set (G_OBJECT (udpsink0), "auto-multicast", FALSE, NULL);
-  g_object_set (G_OBJECT (udpsink1), "auto-multicast", FALSE, NULL);
+  /* join multicast group when adding clients, so we'll start receiving from it.
+   * We cannot rely on the udpsrc to join the group since its socket is always a
+   * local unicast one. */
+  g_object_set (G_OBJECT (udpsink0), "auto-multicast", TRUE, NULL);
+  g_object_set (G_OBJECT (udpsink1), "auto-multicast", TRUE, NULL);
 
   g_object_set (G_OBJECT (udpsink0), "loop", FALSE, NULL);
   g_object_set (G_OBJECT (udpsink1), "loop", FALSE, NULL);
 
-  /* update the dscp qos field in the sinks */
-  update_dscp_qos (stream);
+  udpsink[0] = udpsink0;
+  udpsink[1] = udpsink1;
 
-  priv->udpsink[0] = udpsink0;
-  priv->udpsink[1] = udpsink1;
+  /* update the dscp qos field in the sinks */
+  update_dscp_qos (stream, udpsink);
 
   return TRUE;
 
@@ -1169,54 +1161,9 @@ no_udp_protocol:
 }
 
 /* must be called with lock */
-static void
-play_udpsources_one_family (GstRTSPStream * stream, GstElement * udpsrc_out[2],
-    GSocketFamily family)
-{
-  GstRTSPStreamPrivate *priv;
-  GstPad *pad, *selpad;
-  guint i;
-
-  priv = stream->priv;
-
-  for (i = 0; i < 2; i++) {
-    if (!priv->sinkpad && i == 0) {
-      /* Only connect recv RTP sink if we expect to receive RTP. Connect recv
-       * RTCP sink always */
-      continue;
-    }
-
-    if (priv->srcpad) {
-      /* we set and keep these to playing so that they don't cause NO_PREROLL return
-       * values. This is only relevant for PLAY pipelines */
-      gst_element_set_state (udpsrc_out[i], GST_STATE_PLAYING);
-      gst_element_set_locked_state (udpsrc_out[i], TRUE);
-    }
-
-    /* add udpsrc */
-    gst_bin_add (priv->joined_bin, udpsrc_out[i]);
-
-    /* and link to the funnel */
-    selpad = gst_element_get_request_pad (priv->funnel[i], "sink_%u");
-    pad = gst_element_get_static_pad (udpsrc_out[i], "src");
-    gst_pad_link (pad, selpad);
-    gst_object_unref (pad);
-    gst_object_unref (selpad);
-
-    /* otherwise sync state with parent in case it's running already
-     * at this point */
-    if (!priv->srcpad) {
-      gst_element_sync_state_with_parent (udpsrc_out[i]);
-    }
-  }
-}
-
-/* must be called with lock */
 static gboolean
-create_and_configure_udpsources_one_family (GstElement * udpsrc_out[2],
-    GSocket * rtp_socket, GSocket * rtcp_socket, GSocketFamily family,
-    const gchar * address, gint rtpport, gint rtcpport,
-    const gchar * multicast_iface, GstRTSPLowerTrans transport)
+create_and_configure_udpsources (GstElement * udpsrc_out[2],
+    GSocket * rtp_socket, GSocket * rtcp_socket)
 {
   GstStateChangeReturn ret;
 
@@ -1226,22 +1173,17 @@ create_and_configure_udpsources_one_family (GstElement * udpsrc_out[2],
   if (udpsrc_out[0] == NULL || udpsrc_out[1] == NULL)
     goto error;
 
-  if (transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
-    g_object_set (G_OBJECT (udpsrc_out[0]), "address", address, NULL);
-    g_object_set (G_OBJECT (udpsrc_out[1]), "address", address, NULL);
-    g_object_set (G_OBJECT (udpsrc_out[0]), "port", rtpport, NULL);
-    g_object_set (G_OBJECT (udpsrc_out[1]), "port", rtcpport, NULL);
-    g_object_set (G_OBJECT (udpsrc_out[0]), "multicast-iface", multicast_iface,
-        NULL);
-    g_object_set (G_OBJECT (udpsrc_out[1]), "multicast-iface", multicast_iface,
-        NULL);
-    g_object_set (G_OBJECT (udpsrc_out[0]), "loop", FALSE, NULL);
-    g_object_set (G_OBJECT (udpsrc_out[1]), "loop", FALSE, NULL);
-  }
-
   g_object_set (G_OBJECT (udpsrc_out[0]), "socket", rtp_socket, NULL);
   g_object_set (G_OBJECT (udpsrc_out[1]), "socket", rtcp_socket, NULL);
 
+  /* The udpsrc cannot do the join because its socket is always a local unicast
+   * one. The udpsink sharing the same socket will do it for us. */
+  g_object_set (G_OBJECT (udpsrc_out[0]), "auto-multicast", FALSE, NULL);
+  g_object_set (G_OBJECT (udpsrc_out[1]), "auto-multicast", FALSE, NULL);
+
+  g_object_set (G_OBJECT (udpsrc_out[0]), "loop", FALSE, NULL);
+  g_object_set (G_OBJECT (udpsrc_out[1]), "loop", FALSE, NULL);
+
   ret = gst_element_set_state (udpsrc_out[0], GST_STATE_READY);
   if (ret == GST_STATE_CHANGE_FAILURE)
     goto error;
@@ -1268,9 +1210,8 @@ error:
 
 static gboolean
 alloc_ports_one_family (GstRTSPStream * stream, GSocketFamily family,
-    GstElement * udpsrc_out[2], GstRTSPRange * server_port_out,
-    GstRTSPTransport * ct, GstRTSPAddress ** server_addr_out,
-    gboolean use_client_settings)
+    GstElement * udpsrc_out[2], GstElement * udpsink_out[2],
+    GstRTSPAddress ** server_addr_out, gboolean multicast)
 {
   GstRTSPStreamPrivate *priv = stream->priv;
   GSocket *rtp_socket = NULL;
@@ -1285,12 +1226,15 @@ alloc_ports_one_family (GstRTSPStream * stream, GSocketFamily family,
   GSocketAddress *rtp_sockaddr = NULL;
   GSocketAddress *rtcp_sockaddr = NULL;
   GstRTSPAddressPool *pool;
-  GstRTSPLowerTrans transport;
-  const gchar *multicast_iface = priv->multicast_iface;
+
+  g_assert (!udpsrc_out[0]);
+  g_assert (!udpsrc_out[1]);
+  g_assert ((!udpsink_out[0] && !udpsink_out[1]) ||
+      (udpsink_out[0] && udpsink_out[1]));
+  g_assert (*server_addr_out == NULL);
 
   pool = priv->pool;
   count = 0;
-  transport = ct->lower_transport;
 
   /* Start with random port */
   tmp_rtp = 0;
@@ -1301,9 +1245,6 @@ alloc_ports_one_family (GstRTSPStream * stream, GSocketFamily family,
     goto no_udp_protocol;
   g_socket_set_multicast_loopback (rtcp_socket, FALSE);
 
-  if (*server_addr_out)
-    gst_rtsp_address_free (*server_addr_out);
-
   /* try to allocate 2 UDP ports, the RTP port should be an even
    * number and the RTCP port should be the next (uneven) port */
 again:
@@ -1316,30 +1257,27 @@ again:
     g_socket_set_multicast_loopback (rtp_socket, FALSE);
   }
 
-  if (pool && ((transport == GST_RTSP_LOWER_TRANS_UDP &&
-              gst_rtsp_address_pool_has_unicast_addresses (pool))
-          || transport == GST_RTSP_LOWER_TRANS_UDP_MCAST)) {
-    GstRTSPAddressFlags flags = GST_RTSP_ADDRESS_FLAG_EVEN_PORT;
-
-    if (transport == GST_RTSP_LOWER_TRANS_UDP)
-      flags |= GST_RTSP_ADDRESS_FLAG_UNICAST;
-    else
-      flags |= GST_RTSP_ADDRESS_FLAG_MULTICAST;
+  if ((pool && gst_rtsp_address_pool_has_unicast_addresses (pool)) || multicast) {
+    GstRTSPAddressFlags flags;
 
     if (addr)
       rejected_addresses = g_list_prepend (rejected_addresses, addr);
 
+    if (!pool)
+      goto no_ports;
+
+    flags = GST_RTSP_ADDRESS_FLAG_EVEN_PORT;
+    if (multicast)
+      flags |= GST_RTSP_ADDRESS_FLAG_MULTICAST;
+    else
+      flags |= GST_RTSP_ADDRESS_FLAG_UNICAST;
+
     if (family == G_SOCKET_FAMILY_IPV6)
       flags |= GST_RTSP_ADDRESS_FLAG_IPV6;
     else
       flags |= GST_RTSP_ADDRESS_FLAG_IPV4;
 
-    if (ct->destination && transport == GST_RTSP_LOWER_TRANS_UDP_MCAST
-        && use_client_settings)
-      gst_rtsp_address_pool_reserve_address (pool, ct->destination,
-          ct->port.min, 2, ct->ttl, &addr);
-    else
-      addr = gst_rtsp_address_pool_acquire_address (pool, flags, 2);
+    addr = gst_rtsp_address_pool_acquire_address (pool, flags, 2);
 
     if (addr == NULL)
       goto no_ports;
@@ -1347,15 +1285,10 @@ again:
     tmp_rtp = addr->port;
 
     g_clear_object (&inetaddr);
-    inetaddr = g_inet_address_new_from_string (addr->address);
-
-    /* If we're supposed to bind to a multicast address, instead bind
-     * to ANY and let udpsrc later join the relevant multicast group
-     */
-    if (g_inet_address_get_is_multicast (inetaddr)) {
-      g_object_unref (inetaddr);
+    if (multicast)
       inetaddr = g_inet_address_new_any (family);
-    }
+    else
+      inetaddr = g_inet_address_new_from_string (addr->address);
   } else {
     if (tmp_rtp != 0) {
       tmp_rtp += 2;
@@ -1403,25 +1336,20 @@ again:
   }
   g_object_unref (rtcp_sockaddr);
 
-  if (addr == NULL)
-    addr_str = g_inet_address_to_string (inetaddr);
-  else
-    addr_str = addr->address;
+  if (!addr) {
+    addr = g_slice_new0 (GstRTSPAddress);
+    addr->address = g_inet_address_to_string (inetaddr);
+    addr->port = tmp_rtp;
+    addr->n_ports = 2;
+  }
+
+  addr_str = addr->address;
   g_clear_object (&inetaddr);
 
-  if (!create_and_configure_udpsources_one_family (udpsrc_out, rtp_socket,
-          rtcp_socket, family, addr_str, tmp_rtp, tmp_rtcp, multicast_iface,
-          transport)) {
-    if (addr == NULL)
-      g_free (addr_str);
+  if (!create_and_configure_udpsources (udpsrc_out, rtp_socket, rtcp_socket)) {
     goto no_udp_protocol;
   }
 
-  if (addr == NULL)
-    g_free (addr_str);
-
-  play_udpsources_one_family (stream, udpsrc_out, family);
-
   g_object_get (G_OBJECT (udpsrc_out[0]), "port", &rtpport, NULL);
   g_object_get (G_OBJECT (udpsrc_out[1]), "port", &rtcpport, NULL);
 
@@ -1429,13 +1357,26 @@ again:
   if (rtpport != tmp_rtp || rtcpport != tmp_rtcp)
     goto port_error;
 
-  /* set RTP and RTCP sockets */
-  set_sockets_for_udpsinks (stream, rtp_socket, rtcp_socket, family);
+  /* This function is called twice (for v4 and v6) but we create only one pair
+   * of udpsinks. */
+  if (!udpsink_out[0]
+      && !create_and_configure_udpsinks (stream, udpsink_out))
+    goto no_udp_protocol;
+
+  if (multicast) {
+    g_object_set (G_OBJECT (udpsink_out[0]), "multicast-iface",
+        priv->multicast_iface, NULL);
+    g_object_set (G_OBJECT (udpsink_out[1]), "multicast-iface",
+        priv->multicast_iface, NULL);
 
-  server_port_out->min = rtpport;
-  server_port_out->max = rtcpport;
+    g_signal_emit_by_name (udpsink_out[0], "add", addr_str, rtpport, NULL);
+    g_signal_emit_by_name (udpsink_out[1], "add", addr_str, rtcpport, NULL);
+  }
+
+  set_sockets_for_udpsinks (udpsink_out, rtp_socket, rtcp_socket, family);
 
   *server_addr_out = addr;
+
   g_list_free_full (rejected_addresses, (GDestroyNotify) gst_rtsp_address_free);
 
   g_object_unref (rtp_socket);
@@ -1485,60 +1426,14 @@ cleanup:
  * Allocates RTP and RTCP ports.
  *
  * Returns: %TRUE if the RTP and RTCP sockets have been succeccully allocated.
+ * Deprecated: This function shouldn't have been made public
  */
 gboolean
 gst_rtsp_stream_allocate_udp_sockets (GstRTSPStream * stream,
     GSocketFamily family, GstRTSPTransport * ct, gboolean use_client_settings)
 {
-  GstRTSPStreamPrivate *priv;
-  gboolean result = FALSE;
-  GstRTSPLowerTrans transport = ct->lower_transport;
-
-  g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
-  priv = stream->priv;
-  g_return_val_if_fail (priv->joined_bin != NULL, FALSE);
-
-  g_mutex_lock (&priv->lock);
-
-  if (family == G_SOCKET_FAMILY_IPV4) {
-    if (transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
-      if (priv->have_ipv4_mcast)
-        goto done;
-      priv->have_ipv4_mcast =
-          alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
-          priv->udpsrc_mcast_v4, &priv->server_port_v4, ct,
-          &priv->mcast_addr_v4, use_client_settings);
-    } else {
-      priv->have_ipv4 =
-          alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4, priv->udpsrc_v4,
-          &priv->server_port_v4, ct, &priv->server_addr_v4,
-          use_client_settings);
-    }
-  } else {
-    if (transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
-      if (priv->have_ipv6_mcast)
-        goto done;
-      priv->have_ipv6_mcast =
-          alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
-          priv->udpsrc_mcast_v6, &priv->server_port_v6, ct,
-          &priv->mcast_addr_v6, use_client_settings);
-    } else {
-      if (priv->have_ipv6)
-        goto done;
-      priv->have_ipv6 =
-          alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6, priv->udpsrc_v6,
-          &priv->server_port_v6, ct, &priv->server_addr_v6,
-          use_client_settings);
-    }
-  }
-
-done:
-  result = priv->have_ipv4 || priv->have_ipv4_mcast || priv->have_ipv6 ||
-      priv->have_ipv6_mcast;
-
-  g_mutex_unlock (&priv->lock);
-
-  return result;
+  g_warn_if_reached ();
+  return FALSE;
 }
 
 /**
@@ -1589,6 +1484,33 @@ gst_rtsp_stream_is_client_side (GstRTSPStream * stream)
   return ret;
 }
 
+/* must be called with lock */
+static gboolean
+alloc_ports (GstRTSPStream * stream)
+{
+  GstRTSPStreamPrivate *priv = stream->priv;
+  gboolean ret = TRUE;
+
+  if (priv->protocols & GST_RTSP_LOWER_TRANS_UDP) {
+    ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
+        priv->udpsrc_v4, priv->udpsink, &priv->server_addr_v4, FALSE);
+
+    ret |= alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
+        priv->udpsrc_v6, priv->udpsink, &priv->server_addr_v6, FALSE);
+  }
+
+  /* FIXME: Maybe actually consider the return values? */
+  if (priv->protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+    ret |= alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
+        priv->mcast_udpsrc_v4, priv->mcast_udpsink, &priv->mcast_addr_v4, TRUE);
+
+    ret |= alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
+        priv->mcast_udpsrc_v6, priv->mcast_udpsink, &priv->mcast_addr_v6, TRUE);
+  }
+
+  return ret;
+}
+
 /**
  * gst_rtsp_stream_get_server_port:
  * @stream: a #GstRTSPStream
@@ -1610,11 +1532,17 @@ gst_rtsp_stream_get_server_port (GstRTSPStream * stream,
 
   g_mutex_lock (&priv->lock);
   if (family == G_SOCKET_FAMILY_IPV4) {
-    if (server_port)
-      *server_port = priv->server_port_v4;
+    if (server_port) {
+      server_port->min = priv->server_addr_v4->port;
+      server_port->max =
+          priv->server_addr_v4->port + priv->server_addr_v4->n_ports - 1;
+    }
   } else {
-    if (server_port)
-      *server_port = priv->server_port_v6;
+    if (server_port) {
+      server_port->min = priv->server_addr_v6->port;
+      server_port->max =
+          priv->server_addr_v6->port + priv->server_addr_v6->n_ports - 1;
+    }
   }
   g_mutex_unlock (&priv->lock);
 }
@@ -2463,12 +2391,12 @@ plug_sink (GstBin * bin, GstElement * tee, GstElement * sink,
 }
 
 /* must be called with lock */
-static gboolean
+static void
 create_sender_part (GstRTSPStream * stream, GstBin * bin, GstState state)
 {
   GstRTSPStreamPrivate *priv;
   GstPad *pad;
-  gboolean is_tcp = FALSE, is_udp = FALSE;
+  gboolean is_tcp, is_udp;
   gint i;
 
   priv = stream->priv;
@@ -2477,9 +2405,6 @@ create_sender_part (GstRTSPStream * stream, GstBin * bin, GstState state)
   is_udp = ((priv->protocols & GST_RTSP_LOWER_TRANS_UDP) ||
       (priv->protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST));
 
-  if (is_udp && !create_and_configure_udpsinks (stream))
-    goto no_udp_protocol;
-
   for (i = 0; i < 2; i++) {
     /* For the sender we create this bit of pipeline for both
      * RTP and RTCP. Sync and preroll are enabled on udpsink so
@@ -2515,9 +2440,10 @@ create_sender_part (GstRTSPStream * stream, GstBin * bin, GstState state)
           &sink_cb, stream, NULL);
     }
 
-    if (is_udp && is_tcp) {
-      g_object_set (priv->appsink[i], "async", FALSE, "sync", FALSE, NULL);
-
+    /* If we have udp always use a tee because we could have mcast clients
+     * requesting different ports, in which case we'll have to plug more
+     * udpsinks. */
+    if (is_udp) {
       /* make tee for RTP/RTCP */
       priv->tee[i] = gst_element_factory_make ("tee", NULL);
       gst_bin_add (bin, priv->tee[i]);
@@ -2527,8 +2453,17 @@ create_sender_part (GstRTSPStream * stream, GstBin * bin, GstState state)
       gst_pad_link (priv->send_src[i], pad);
       gst_object_unref (pad);
 
-      plug_sink (bin, priv->tee[i], priv->udpsink[i], &priv->udpqueue[i]);
-      plug_sink (bin, priv->tee[i], priv->appsink[i], &priv->appqueue[i]);
+      if (priv->udpsink[i])
+        plug_sink (bin, priv->tee[i], priv->udpsink[i], &priv->udpqueue[i]);
+
+      if (priv->mcast_udpsink[i])
+        plug_sink (bin, priv->tee[i], priv->mcast_udpsink[i],
+            &priv->mcast_udpqueue[i]);
+
+      if (is_tcp) {
+        g_object_set (priv->appsink[i], "async", FALSE, "sync", FALSE, NULL);
+        plug_sink (bin, priv->tee[i], priv->appsink[i], &priv->appqueue[i]);
+      }
     } else if (is_tcp) {
       /* only appsink needed, link it to the session */
       pad = gst_element_get_static_pad (priv->appsink[i], "sink");
@@ -2540,35 +2475,54 @@ create_sender_part (GstRTSPStream * stream, GstBin * bin, GstState state)
        * sink used for RTCP data, not the RTP data. */
       if (i == 1)
         g_object_set (priv->appsink[i], "async", FALSE, "sync", FALSE, NULL);
-    } else {
-      /* else only udpsink needed, link it to the session */
-      pad = gst_element_get_static_pad (priv->udpsink[i], "sink");
-      gst_pad_link (priv->send_src[i], pad);
-      gst_object_unref (pad);
     }
 
     /* check if we need to set to a special state */
     if (state != GST_STATE_NULL) {
       if (priv->udpsink[i])
         gst_element_set_state (priv->udpsink[i], state);
+      if (priv->mcast_udpsink[i])
+        gst_element_set_state (priv->mcast_udpsink[i], state);
       if (priv->appsink[i])
         gst_element_set_state (priv->appsink[i], state);
       if (priv->appqueue[i])
         gst_element_set_state (priv->appqueue[i], state);
       if (priv->udpqueue[i])
         gst_element_set_state (priv->udpqueue[i], state);
+      if (priv->mcast_udpqueue[i])
+        gst_element_set_state (priv->mcast_udpqueue[i], state);
       if (priv->tee[i])
         gst_element_set_state (priv->tee[i], state);
     }
   }
+}
 
-  return TRUE;
+/* must be called with lock */
+static void
+plug_src (GstRTSPStream * stream, GstBin * bin, GstElement * src,
+    GstElement * funnel)
+{
+  GstRTSPStreamPrivate *priv;
+  GstPad *pad, *selpad;
 
-  /* ERRORS */
-no_udp_protocol:
-  {
-    return FALSE;
+  priv = stream->priv;
+
+  if (priv->srcpad) {
+    /* we set and keep these to playing so that they don't cause NO_PREROLL return
+     * values. This is only relevant for PLAY pipelines */
+    gst_element_set_state (src, GST_STATE_PLAYING);
+    gst_element_set_locked_state (src, TRUE);
   }
+
+  /* add src */
+  gst_bin_add (bin, src);
+
+  /* and link to the funnel */
+  selpad = gst_element_get_request_pad (funnel, "sink_%u");
+  pad = gst_element_get_static_pad (src, "src");
+  gst_pad_link (pad, selpad);
+  gst_object_unref (pad);
+  gst_object_unref (selpad);
 }
 
 /* must be called with lock */
@@ -2576,7 +2530,7 @@ static void
 create_receiver_part (GstRTSPStream * stream, GstBin * bin, GstState state)
 {
   GstRTSPStreamPrivate *priv;
-  GstPad *pad, *selpad;
+  GstPad *pad;
   gboolean is_tcp;
   gint i;
 
@@ -2613,56 +2567,25 @@ create_receiver_part (GstRTSPStream * stream, GstBin * bin, GstState state)
     gst_pad_link (pad, priv->recv_sink[i]);
     gst_object_unref (pad);
 
-    if (priv->udpsrc_v4[i]) {
-      if (priv->srcpad) {
-        /* we set and keep these to playing so that they don't cause NO_PREROLL return
-         * values. This is only relevant for PLAY pipelines */
-        gst_element_set_state (priv->udpsrc_v4[i], GST_STATE_PLAYING);
-        gst_element_set_locked_state (priv->udpsrc_v4[i], TRUE);
-      }
-      /* add udpsrc */
-      gst_bin_add (bin, priv->udpsrc_v4[i]);
+    if (priv->udpsrc_v4[i])
+      plug_src (stream, bin, priv->udpsrc_v4[i], priv->funnel[i]);
 
-      /* and link to the funnel v4 */
-      selpad = gst_element_get_request_pad (priv->funnel[i], "sink_%u");
-      pad = gst_element_get_static_pad (priv->udpsrc_v4[i], "src");
-      gst_pad_link (pad, selpad);
-      gst_object_unref (pad);
-      gst_object_unref (selpad);
-    }
+    if (priv->udpsrc_v6[i])
+      plug_src (stream, bin, priv->udpsrc_v6[i], priv->funnel[i]);
 
-    if (priv->udpsrc_v6[i]) {
-      if (priv->srcpad) {
-        gst_element_set_state (priv->udpsrc_v6[i], GST_STATE_PLAYING);
-        gst_element_set_locked_state (priv->udpsrc_v6[i], TRUE);
-      }
-      gst_bin_add (bin, priv->udpsrc_v6[i]);
+    if (priv->mcast_udpsrc_v4[i])
+      plug_src (stream, bin, priv->mcast_udpsrc_v4[i], priv->funnel[i]);
 
-      /* and link to the funnel v6 */
-      selpad = gst_element_get_request_pad (priv->funnel[i], "sink_%u");
-      pad = gst_element_get_static_pad (priv->udpsrc_v6[i], "src");
-      gst_pad_link (pad, selpad);
-      gst_object_unref (pad);
-      gst_object_unref (selpad);
-    }
+    if (priv->mcast_udpsrc_v6[i])
+      plug_src (stream, bin, priv->mcast_udpsrc_v6[i], priv->funnel[i]);
 
     if (is_tcp) {
       /* make and add appsrc */
       priv->appsrc[i] = gst_element_factory_make ("appsrc", NULL);
       priv->appsrc_base_time[i] = -1;
-      if (priv->srcpad) {
-        gst_element_set_state (priv->appsrc[i], GST_STATE_PLAYING);
-        gst_element_set_locked_state (priv->appsrc[i], TRUE);
-      }
       g_object_set (priv->appsrc[i], "format", GST_FORMAT_TIME, "is-live",
           TRUE, NULL);
-      gst_bin_add (bin, priv->appsrc[i]);
-      /* and link to the funnel */
-      selpad = gst_element_get_request_pad (priv->funnel[i], "sink_%u");
-      pad = gst_element_get_static_pad (priv->appsrc[i], "src");
-      gst_pad_link (pad, selpad);
-      gst_object_unref (pad);
-      gst_object_unref (selpad);
+      plug_src (stream, bin, priv->appsrc[i], priv->funnel[i]);
     }
 
     /* check if we need to set to a special state */
@@ -2672,6 +2595,54 @@ create_receiver_part (GstRTSPStream * stream, GstBin * bin, GstState state)
   }
 }
 
+static gboolean
+check_mcast_part_for_transport (GstRTSPStream * stream,
+    const GstRTSPTransport * tr)
+{
+  GstRTSPStreamPrivate *priv = stream->priv;
+  GInetAddress *inetaddr;
+  GSocketFamily family;
+  GstRTSPAddress *mcast_addr;
+
+  /* Check if it's a ipv4 or ipv6 transport */
+  inetaddr = g_inet_address_new_from_string (tr->destination);
+  family = g_inet_address_get_family (inetaddr);
+  g_object_unref (inetaddr);
+
+  /* Select fields corresponding to the family */
+  if (family == G_SOCKET_FAMILY_IPV4) {
+    mcast_addr = priv->mcast_addr_v4;
+  } else {
+    mcast_addr = priv->mcast_addr_v6;
+  }
+
+  /* We support only one mcast group per family, make sure this transport
+   * matches it. */
+  if (!mcast_addr)
+    goto no_addr;
+
+  if (!g_str_equal (tr->destination, mcast_addr->address) ||
+      tr->port.min != mcast_addr->port ||
+      tr->port.max != mcast_addr->port + mcast_addr->n_ports - 1 ||
+      tr->ttl != mcast_addr->ttl)
+    goto wrong_addr;
+
+  return TRUE;
+
+no_addr:
+  {
+    GST_WARNING_OBJECT (stream, "Adding mcast transport, but no mcast address "
+        "has been reserved");
+    return FALSE;
+  }
+wrong_addr:
+  {
+    GST_WARNING_OBJECT (stream, "Adding mcast transport, but it doesn't match "
+        "the reserved address");
+    return FALSE;
+  }
+}
+
 /**
  * gst_rtsp_stream_join_bin:
  * @stream: a #GstRTSPStream
@@ -2710,6 +2681,9 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
 
   GST_INFO ("stream %p joining bin as session %u", stream, idx);
 
+  if (!alloc_ports (stream))
+    goto no_ports;
+
   if (priv->profiles & GST_RTSP_PROFILE_SAVP
       || priv->profiles & GST_RTSP_PROFILE_SAVPF) {
     /* For SRTP */
@@ -2785,9 +2759,7 @@ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
   g_signal_connect (priv->session, "on-sender-ssrc-active",
       (GCallback) on_sender_ssrc_active, stream);
 
-  if (!create_sender_part (stream, bin, state))
-    goto no_udp_protocol;
-
+  create_sender_part (stream, bin, state);
   create_receiver_part (stream, bin, state);
 
   if (priv->srcpad) {
@@ -2807,71 +2779,17 @@ was_joined:
     g_mutex_unlock (&priv->lock);
     return TRUE;
   }
-link_failed:
+no_ports:
   {
-    GST_WARNING ("failed to link stream %u", idx);
-    gst_object_unref (priv->send_rtp_sink);
-    priv->send_rtp_sink = NULL;
     g_mutex_unlock (&priv->lock);
+    GST_WARNING ("failed to allocate ports %u", idx);
     return FALSE;
   }
-no_udp_protocol:
+link_failed:
   {
-    GST_WARNING ("failed to allocate ports %u", idx);
+    GST_WARNING ("failed to link stream %u", idx);
     gst_object_unref (priv->send_rtp_sink);
     priv->send_rtp_sink = NULL;
-    gst_object_unref (priv->send_src[0]);
-    priv->send_src[0] = NULL;
-    gst_object_unref (priv->send_src[1]);
-    priv->send_src[1] = NULL;
-    gst_object_unref (priv->recv_sink[0]);
-    priv->recv_sink[0] = NULL;
-    gst_object_unref (priv->recv_sink[1]);
-    priv->recv_sink[1] = NULL;
-    if (priv->udpsink[0])
-      gst_element_set_state (priv->udpsink[0], GST_STATE_NULL);
-    if (priv->udpsink[1])
-      gst_element_set_state (priv->udpsink[1], GST_STATE_NULL);
-    if (priv->udpsrc_v4[0]) {
-      gst_element_set_state (priv->udpsrc_v4[0], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_v4[0]);
-      priv->udpsrc_v4[0] = NULL;
-    }
-    if (priv->udpsrc_v4[1]) {
-      gst_element_set_state (priv->udpsrc_v4[1], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_v4[1]);
-      priv->udpsrc_v4[1] = NULL;
-    }
-    if (priv->udpsrc_mcast_v4[0]) {
-      gst_element_set_state (priv->udpsrc_mcast_v4[0], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_mcast_v4[0]);
-      priv->udpsrc_mcast_v4[0] = NULL;
-    }
-    if (priv->udpsrc_mcast_v4[1]) {
-      gst_element_set_state (priv->udpsrc_mcast_v4[1], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_mcast_v4[1]);
-      priv->udpsrc_mcast_v4[1] = NULL;
-    }
-    if (priv->udpsrc_v6[0]) {
-      gst_element_set_state (priv->udpsrc_v6[0], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_v6[0]);
-      priv->udpsrc_v6[0] = NULL;
-    }
-    if (priv->udpsrc_v6[1]) {
-      gst_element_set_state (priv->udpsrc_v6[1], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_v6[1]);
-      priv->udpsrc_v6[1] = NULL;
-    }
-    if (priv->udpsrc_mcast_v6[0]) {
-      gst_element_set_state (priv->udpsrc_mcast_v6[0], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_mcast_v6[0]);
-      priv->udpsrc_mcast_v6[0] = NULL;
-    }
-    if (priv->udpsrc_mcast_v6[1]) {
-      gst_element_set_state (priv->udpsrc_mcast_v6[1], GST_STATE_NULL);
-      gst_object_unref (priv->udpsrc_mcast_v6[1]);
-      priv->udpsrc_mcast_v6[1] = NULL;
-    }
     g_mutex_unlock (&priv->lock);
     return FALSE;
   }
@@ -2945,17 +2863,22 @@ gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
   }
 
   for (i = 0; i < 2; i++) {
+    clear_element (bin, &priv->udpsrc_v4[i]);
+    clear_element (bin, &priv->udpsrc_v6[i]);
+    clear_element (bin, &priv->udpqueue[i]);
     clear_element (bin, &priv->udpsink[i]);
-    clear_element (bin, &priv->appsink[i]);
+
+    clear_element (bin, &priv->mcast_udpsrc_v4[i]);
+    clear_element (bin, &priv->mcast_udpsrc_v6[i]);
+    clear_element (bin, &priv->mcast_udpqueue[i]);
+    clear_element (bin, &priv->mcast_udpsink[i]);
+
+    clear_element (bin, &priv->appsrc[i]);
     clear_element (bin, &priv->appqueue[i]);
-    clear_element (bin, &priv->udpqueue[i]);
+    clear_element (bin, &priv->appsink[i]);
+
     clear_element (bin, &priv->tee[i]);
     clear_element (bin, &priv->funnel[i]);
-    clear_element (bin, &priv->appsrc[i]);
-    clear_element (bin, &priv->udpsrc_v4[i]);
-    clear_element (bin, &priv->udpsrc_v6[i]);
-    clear_element (bin, &priv->udpsrc_mcast_v4[i]);
-    clear_element (bin, &priv->udpsrc_mcast_v6[i]);
 
     if (priv->sinkpad || i == 1) {
       gst_element_release_request_pad (rtpbin, priv->recv_sink[i]);
@@ -2984,6 +2907,19 @@ gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
   if (priv->srtpdec)
     gst_object_unref (priv->srtpdec);
 
+  if (priv->mcast_addr_v4)
+    gst_rtsp_address_free (priv->mcast_addr_v4);
+  priv->mcast_addr_v4 = NULL;
+  if (priv->mcast_addr_v6)
+    gst_rtsp_address_free (priv->mcast_addr_v6);
+  priv->mcast_addr_v6 = NULL;
+  if (priv->server_addr_v4)
+    gst_rtsp_address_free (priv->server_addr_v4);
+  priv->server_addr_v4 = NULL;
+  if (priv->server_addr_v6)
+    gst_rtsp_address_free (priv->server_addr_v6);
+  priv->server_addr_v6 = NULL;
+
   g_clear_object (&priv->joined_bin);
   g_mutex_unlock (&priv->lock);
 
@@ -3341,6 +3277,16 @@ update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
 
   switch (tr->lower_transport) {
     case GST_RTSP_LOWER_TRANS_UDP_MCAST:
+    {
+      if (add) {
+        if (!check_mcast_part_for_transport (stream, tr))
+          goto mcast_error;
+        priv->transports = g_list_prepend (priv->transports, trans);
+      } else {
+        priv->transports = g_list_remove (priv->transports, trans);
+      }
+      break;
+    }
     case GST_RTSP_LOWER_TRANS_UDP:
     {
       gchar *dest;
@@ -3402,6 +3348,10 @@ unknown_transport:
     GST_INFO ("Unknown transport %d", tr->lower_transport);
     return FALSE;
   }
+mcast_error:
+  {
+    return FALSE;
+  }
 }