summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Waters <matthew@centricular.com>2018-11-28 17:23:31 +1100
committerMatthew Waters <matthew@centricular.com>2019-05-30 21:33:09 +1000
commit177aa22bcd9db2059e714221221a8e56c9533990 (patch)
tree910320c37222ad651787e12495bfd86521fa7a8d
parent015cb75f6683a09de35bf023bfa37e3bca0d8d46 (diff)
downloadgstreamer-plugins-bad-177aa22bcd9db2059e714221221a8e56c9533990.tar.gz
webrtc: Initial support for stream addition/removal
Limitations: - No transport changes at all (ICE, DTLS) - Codec changes are untested and probably don't work - Stream removal doesn't remove transports (i.e. non-bundled transports will stay around until webrtcbin is shutdown) - Unified Plan SDP only. No Plan-B support.
-rw-r--r--.gitignore1
-rw-r--r--ext/webrtc/gstwebrtcbin.c909
-rw-r--r--ext/webrtc/gstwebrtcbin.h2
-rw-r--r--ext/webrtc/transportstream.c38
-rw-r--r--ext/webrtc/transportstream.h7
-rw-r--r--ext/webrtc/utils.c60
-rw-r--r--ext/webrtc/utils.h4
-rw-r--r--ext/webrtc/webrtcsdp.c25
-rw-r--r--ext/webrtc/webrtcsdp.h9
-rw-r--r--ext/webrtc/webrtctransceiver.c11
-rw-r--r--ext/webrtc/webrtctransceiver.h2
-rw-r--r--gst-libs/gst/webrtc/rtptransceiver.c2
-rw-r--r--tests/check/elements/webrtcbin.c632
-rw-r--r--tests/examples/webrtc/Makefile.am15
-rw-r--r--tests/examples/webrtc/meson.build2
-rw-r--r--tests/examples/webrtc/webrtcrenego.c289
16 files changed, 1625 insertions, 383 deletions
diff --git a/.gitignore b/.gitignore
index 7fa8dbcb9..539e47030 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ gst*orc.h
/tests/examples/webrtc/webrtc
/tests/examples/webrtc/webrtcbidirectional
/tests/examples/webrtc/webrtcswap
+/tests/examples/webrtc/webrtcrenego
/tests/examples/webrtc/webrtctransceiver
Build
diff --git a/ext/webrtc/gstwebrtcbin.c b/ext/webrtc/gstwebrtcbin.c
index c6d2ae33d..665220d9d 100644
--- a/ext/webrtc/gstwebrtcbin.c
+++ b/ext/webrtc/gstwebrtcbin.c
@@ -245,18 +245,28 @@ static gboolean
gst_webrtcbin_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (pad);
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (parent);
+ gboolean check_negotiation = FALSE;
if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
GstCaps *caps;
- gboolean do_update;
gst_event_parse_caps (event, &caps);
- do_update = (!wpad->received_caps
+ check_negotiation = (!wpad->received_caps
|| gst_caps_is_equal (wpad->received_caps, caps));
gst_caps_replace (&wpad->received_caps, caps);
- if (do_update)
- _update_need_negotiation (GST_WEBRTC_BIN (parent));
+ GST_DEBUG_OBJECT (parent,
+ "On %" GST_PTR_FORMAT " checking negotiation? %u, caps %"
+ GST_PTR_FORMAT, pad, check_negotiation, caps);
+ } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
+ check_negotiation = TRUE;
+ }
+
+ if (check_negotiation) {
+ PC_LOCK (webrtc);
+ _update_need_negotiation (webrtc);
+ PC_UNLOCK (webrtc);
}
return gst_pad_event_default (pad, parent, event);
@@ -1164,10 +1174,14 @@ _all_sinks_have_caps (GstWebRTCBin * webrtc)
GST_OBJECT_LOCK (webrtc);
l = GST_ELEMENT (webrtc)->pads;
for (; l; l = g_list_next (l)) {
+ GstWebRTCBinPad *wpad;
+
if (!GST_IS_WEBRTC_BIN_PAD (l->data))
continue;
- if (GST_PAD_DIRECTION (l->data) == GST_PAD_SINK
- && !GST_WEBRTC_BIN_PAD (l->data)->received_caps) {
+
+ wpad = GST_WEBRTC_BIN_PAD (l->data);
+ if (GST_PAD_DIRECTION (l->data) == GST_PAD_SINK && !wpad->received_caps
+ && (!wpad->trans || !wpad->trans->stopped)) {
goto done;
}
}
@@ -1207,10 +1221,6 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
* FIXME */
/* FIXME: emit when input caps/format changes? */
- /* If connection has created any RTCDataChannel's, and no m= section has
- * been negotiated yet for data, return "true".
- * FIXME */
-
if (!webrtc->current_local_description) {
GST_LOG_OBJECT (webrtc, "no local description set");
return TRUE;
@@ -1221,6 +1231,18 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
return TRUE;
}
+ /* If connection has created any RTCDataChannel's, and no m= section has
+ * been negotiated yet for data, return "true". */
+ if (webrtc->priv->data_channels->len > 0) {
+ if (_message_get_datachannel_index (webrtc->current_local_description->
+ sdp) >= G_MAXUINT) {
+ GST_LOG_OBJECT (webrtc,
+ "no data channel media section and have %u " "transports",
+ webrtc->priv->data_channels->len);
+ return TRUE;
+ }
+ }
+
for (i = 0; i < webrtc->priv->transceivers->len; i++) {
GstWebRTCRTPTransceiver *trans;
@@ -1240,8 +1262,8 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
GstWebRTCRTPTransceiverDirection local_dir, remote_dir;
if (trans->mline == -1 || trans->mid == NULL) {
- GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT,
- i, trans);
+ GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT
+ " mid %s", i, trans, trans->mid);
return TRUE;
}
/* internal inconsistency */
@@ -1271,8 +1293,26 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
* nor answer matches t's direction, return "true". */
if (local_dir != trans->direction && remote_dir != trans->direction) {
- GST_LOG_OBJECT (webrtc,
- "transceiver direction doesn't match description");
+ gchar *local_str, *remote_str, *dir_str;
+
+ local_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ local_dir);
+ remote_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ remote_dir);
+ dir_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ trans->direction);
+
+ GST_LOG_OBJECT (webrtc, "transceiver direction (%s) doesn't match "
+ "description (local %s remote %s)", dir_str, local_str,
+ remote_str);
+
+ g_free (dir_str);
+ g_free (local_str);
+ g_free (remote_str);
+
return TRUE;
}
} else if (webrtc->current_local_description->type ==
@@ -1288,8 +1328,30 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
intersect_dir = _intersect_answer_directions (remote_dir, local_dir);
if (intersect_dir != trans->direction) {
- GST_LOG_OBJECT (webrtc,
- "transceiver direction doesn't match description");
+ gchar *local_str, *remote_str, *inter_str, *dir_str;
+
+ local_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ local_dir);
+ remote_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ remote_dir);
+ dir_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ trans->direction);
+ inter_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ intersect_dir);
+
+ GST_LOG_OBJECT (webrtc, "transceiver direction (%s) doesn't match "
+ "description intersected direction %s (local %s remote %s)",
+ dir_str, local_str, inter_str, remote_str);
+
+ g_free (dir_str);
+ g_free (local_str);
+ g_free (remote_str);
+ g_free (inter_str);
+
return TRUE;
}
}
@@ -1342,21 +1404,32 @@ _update_need_negotiation (GstWebRTCBin * webrtc)
}
static GstCaps *
-_find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans,
- GstPadDirection direction, guint media_idx)
+_find_codec_preferences (GstWebRTCBin * webrtc,
+ GstWebRTCRTPTransceiver * rtp_trans, GstPadDirection direction,
+ guint media_idx)
{
+ WebRTCTransceiver *trans = (WebRTCTransceiver *) rtp_trans;
GstCaps *ret = NULL;
GST_LOG_OBJECT (webrtc, "retreiving codec preferences from %" GST_PTR_FORMAT,
trans);
- if (trans && trans->codec_preferences) {
+ if (rtp_trans && rtp_trans->codec_preferences) {
GST_LOG_OBJECT (webrtc, "Using codec preferences: %" GST_PTR_FORMAT,
- trans->codec_preferences);
- ret = gst_caps_ref (trans->codec_preferences);
+ rtp_trans->codec_preferences);
+ ret = gst_caps_ref (rtp_trans->codec_preferences);
} else {
- GstWebRTCBinPad *pad = _find_pad_for_mline (webrtc, direction, media_idx);
- if (pad) {
+ GstWebRTCBinPad *pad = NULL;
+
+ /* try to find a pad */
+ if (!trans
+ || !(pad = _find_pad_for_transceiver (webrtc, direction, rtp_trans)))
+ pad = _find_pad_for_mline (webrtc, direction, media_idx);
+
+ if (!pad) {
+ if (trans && trans->last_configured_caps)
+ ret = gst_caps_ref (trans->last_configured_caps);
+ } else {
GstCaps *caps = NULL;
if (pad->received_caps) {
@@ -1369,12 +1442,20 @@ _find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans,
GST_LOG_OBJECT (webrtc, "Using peer query caps: %" GST_PTR_FORMAT,
caps);
}
- if (caps)
+ if (caps) {
+ if (trans)
+ gst_caps_replace (&trans->last_configured_caps, caps);
+
ret = caps;
+ }
+
gst_object_unref (pad);
}
}
+ if (!ret)
+ GST_DEBUG_OBJECT (trans, "Could not find caps for mline %u", media_idx);
+
return ret;
}
@@ -1966,8 +2047,8 @@ _add_fingerprint_to_media (GstWebRTCDTLSTransport * transport,
static gboolean
sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
GstWebRTCRTPTransceiver * trans, GstWebRTCSDPType type, guint media_idx,
- GString * bundled_mids, guint bundle_idx, gboolean bundle_only,
- GArray * reserved_pts)
+ GString * bundled_mids, guint bundle_idx, gchar * bundle_ufrag,
+ gchar * bundle_pwd, GArray * reserved_pts)
{
/* TODO:
* rtp header extensions
@@ -1979,19 +2060,48 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
* dtls fingerprints
* multiple dtls fingerprints https://tools.ietf.org/html/draft-ietf-mmusic-4572-update-05
*/
- gchar *direction, *sdp_mid;
+ GstSDPMessage *last_offer = _get_latest_offer (webrtc);
+ gchar *direction, *sdp_mid, *ufrag, *pwd;
+ gboolean bundle_only;
GstCaps *caps;
int i;
- /* "An m= section is generated for each RtpTransceiver that has been added
- * to the Bin, excluding any stopped RtpTransceivers." */
- if (trans->stopped)
- return FALSE;
if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
|| trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE)
return FALSE;
- gst_sdp_media_set_port_info (media, bundle_only ? 0 : 9, 0);
+ g_assert (trans->mline == -1 || trans->mline == media_idx);
+
+ bundle_only = bundled_mids && bundle_idx != media_idx
+ && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE;
+
+ /* mandated by JSEP */
+ gst_sdp_media_add_attribute (media, "setup", "actpass");
+
+ /* FIXME: deal with ICE restarts */
+ if (last_offer && trans->mline != -1 && trans->mid) {
+ ufrag = g_strdup (_media_get_ice_ufrag (last_offer, trans->mline));
+ pwd = g_strdup (_media_get_ice_pwd (last_offer, trans->mline));
+ GST_DEBUG_OBJECT (trans, "%u Using previous ice parameters", media_idx);
+ } else {
+ GST_DEBUG_OBJECT (trans,
+ "%u Generating new ice parameters mline %i, mid %s", media_idx,
+ trans->mline, trans->mid);
+ if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
+ _generate_ice_credentials (&ufrag, &pwd);
+ } else {
+ g_assert (bundle_ufrag && bundle_pwd);
+ ufrag = g_strdup (bundle_ufrag);
+ pwd = g_strdup (bundle_pwd);
+ }
+ }
+
+ gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
+ gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
+ g_free (ufrag);
+ g_free (pwd);
+
+ gst_sdp_media_set_port_info (media, bundle_only || trans->stopped ? 0 : 9, 0);
gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
@@ -2018,9 +2128,6 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
caps =
_add_supported_attributes_to_caps (webrtc, WEBRTC_TRANSCEIVER (trans),
caps);
- } else if (type == GST_WEBRTC_SDP_TYPE_ANSWER) {
- caps = _find_codec_preferences (webrtc, trans, GST_PAD_SRC, media_idx);
- /* FIXME: add rtcp-fb paramaters */
} else {
g_assert_not_reached ();
}
@@ -2080,10 +2187,18 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
_media_add_ssrcs (media, caps, webrtc, WEBRTC_TRANSCEIVER (trans));
/* Some identifier; we also add the media name to it so it's identifiable */
- sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
- webrtc->priv->media_counter++);
- gst_sdp_media_add_attribute (media, "mid", sdp_mid);
- g_free (sdp_mid);
+ if (trans->mid) {
+ gst_sdp_media_add_attribute (media, "mid", trans->mid);
+ } else {
+ sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
+ webrtc->priv->media_counter++);
+ gst_sdp_media_add_attribute (media, "mid", sdp_mid);
+ g_free (sdp_mid);
+ }
+
+ /* TODO:
+ * - add a=candidate lines for gathered candidates
+ */
if (trans->sender) {
if (!trans->sender->transport) {
@@ -2099,6 +2214,13 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
_add_fingerprint_to_media (trans->sender->transport, media);
}
+ if (bundled_mids) {
+ const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+ g_assert (mid);
+ g_string_append_printf (bundled_mids, " %s", mid);
+ }
+
gst_caps_unref (caps);
return TRUE;
@@ -2132,25 +2254,122 @@ gather_reserved_pts (GstWebRTCBin * webrtc)
return reserved_pts;
}
+static gboolean
+_add_data_channel_offer (GstWebRTCBin * webrtc, GstSDPMessage * msg,
+ GstSDPMedia * media, GString * bundled_mids, guint bundle_idx,
+ gchar * bundle_ufrag, gchar * bundle_pwd)
+{
+ GstSDPMessage *last_offer = _get_latest_offer (webrtc);
+ gchar *ufrag, *pwd, *sdp_mid;
+ gboolean bundle_only = bundled_mids
+ && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
+ && gst_sdp_message_medias_len (msg) != bundle_idx;
+ guint last_data_index = G_MAXUINT;
+
+ /* add data channel support */
+ if (webrtc->priv->data_channels->len == 0)
+ return FALSE;
+
+ if (last_offer) {
+ last_data_index = _message_get_datachannel_index (last_offer);
+ if (last_data_index < G_MAXUINT) {
+ g_assert (last_data_index < gst_sdp_message_medias_len (last_offer));
+ /* XXX: is this always true when recycling transceivers?
+ * i.e. do we always put the data channel in the same mline */
+ g_assert (last_data_index == gst_sdp_message_medias_len (msg));
+ }
+ }
+
+ /* mandated by JSEP */
+ gst_sdp_media_add_attribute (media, "setup", "actpass");
+
+ /* FIXME: only needed when restarting ICE */
+ if (last_offer && last_data_index < G_MAXUINT) {
+ ufrag = g_strdup (_media_get_ice_ufrag (last_offer, last_data_index));
+ pwd = g_strdup (_media_get_ice_pwd (last_offer, last_data_index));
+ } else {
+ if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
+ _generate_ice_credentials (&ufrag, &pwd);
+ } else {
+ ufrag = g_strdup (bundle_ufrag);
+ pwd = g_strdup (bundle_pwd);
+ }
+ }
+ gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
+ gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
+ g_free (ufrag);
+ g_free (pwd);
+
+ gst_sdp_media_set_media (media, "application");
+ gst_sdp_media_set_port_info (media, bundle_only ? 0 : 9, 0);
+ gst_sdp_media_set_proto (media, "UDP/DTLS/SCTP");
+ gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
+ gst_sdp_media_add_format (media, "webrtc-datachannel");
+
+ if (bundle_idx != gst_sdp_message_medias_len (msg))
+ gst_sdp_media_add_attribute (media, "bundle-only", NULL);
+
+ if (last_offer && last_data_index < G_MAXUINT) {
+ const GstSDPMedia *last_data_media;
+ const gchar *mid;
+
+ last_data_media = gst_sdp_message_get_media (last_offer, last_data_index);
+ mid = gst_sdp_media_get_attribute_val (last_data_media, "mid");
+
+ gst_sdp_media_add_attribute (media, "mid", mid);
+ } else {
+ sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
+ webrtc->priv->media_counter++);
+ gst_sdp_media_add_attribute (media, "mid", sdp_mid);
+ g_free (sdp_mid);
+ }
+
+ if (bundled_mids) {
+ const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+ g_assert (mid);
+ g_string_append_printf (bundled_mids, " %s", mid);
+ }
+
+ /* FIXME: negotiate this properly */
+ gst_sdp_media_add_attribute (media, "sctp-port", "5000");
+
+ _get_or_create_data_channel_transports (webrtc,
+ bundled_mids ? 0 : webrtc->priv->transceivers->len);
+ _add_fingerprint_to_media (webrtc->priv->sctp_transport->transport, media);
+
+ return TRUE;
+}
+
/* TODO: use the options argument */
static GstSDPMessage *
_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
{
GstSDPMessage *ret;
- int i;
GString *bundled_mids = NULL;
gchar *bundle_ufrag = NULL;
gchar *bundle_pwd = NULL;
GArray *reserved_pts = NULL;
+ GstSDPMessage *last_offer = _get_latest_offer (webrtc);
+ GList *seen_transceivers = NULL;
+ guint media_idx = 0;
+ int i;
gst_sdp_message_new (&ret);
gst_sdp_message_set_version (ret, "0");
{
- /* FIXME: session id and version need special handling depending on the state we're in */
- gchar *sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID);
- gst_sdp_message_set_origin (ret, "-", sess_id, "0", "IN", "IP4", "0.0.0.0");
+ gchar *v, *sess_id;
+ v = g_strdup_printf ("%u", webrtc->priv->offer_count++);
+ if (last_offer) {
+ const GstSDPOrigin *origin = gst_sdp_message_get_origin (last_offer);
+ sess_id = g_strdup (origin->sess_id);
+ } else {
+ sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID);
+ }
+ gst_sdp_message_set_origin (ret, "-", sess_id, v, "IN", "IP4", "0.0.0.0");
g_free (sess_id);
+ g_free (v);
}
gst_sdp_message_set_session_name (ret, "-");
gst_sdp_message_add_time (ret, "0", "0", NULL);
@@ -2163,53 +2382,119 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
}
if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE) {
- _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+ GStrv last_bundle = NULL;
+ guint bundle_media_index;
+
reserved_pts = gather_reserved_pts (webrtc);
+ if (last_offer && _parse_bundle (last_offer, &last_bundle) && last_bundle
+ && last_bundle && last_bundle[0]
+ && _get_bundle_index (last_offer, last_bundle, &bundle_media_index)) {
+ bundle_ufrag =
+ g_strdup (_media_get_ice_ufrag (last_offer, bundle_media_index));
+ bundle_pwd =
+ g_strdup (_media_get_ice_pwd (last_offer, bundle_media_index));
+ } else {
+ _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+ }
+
+ g_strfreev (last_bundle);
}
- /* for each rtp transceiver */
+ /* FIXME: recycle transceivers */
+
+ /* Fill up the renegotiated streams first */
+ if (last_offer) {
+ for (i = 0; i < gst_sdp_message_medias_len (last_offer); i++) {
+ GstWebRTCRTPTransceiver *trans = NULL;
+ const GstSDPMedia *last_media;
+
+ last_media = gst_sdp_message_get_media (last_offer, i);
+
+ if (g_strcmp0 (gst_sdp_media_get_media (last_media), "audio") == 0
+ || g_strcmp0 (gst_sdp_media_get_media (last_media), "video") == 0) {
+ const gchar *last_mid;
+ int j;
+ last_mid = gst_sdp_media_get_attribute_val (last_media, "mid");
+
+ for (j = 0; j < webrtc->priv->transceivers->len; j++) {
+ trans =
+ g_array_index (webrtc->priv->transceivers,
+ GstWebRTCRTPTransceiver *, j);
+
+ if (trans->mid && g_strcmp0 (trans->mid, last_mid) == 0) {
+ GstSDPMedia *media;
+
+ g_assert (!g_list_find (seen_transceivers, trans));
+
+ GST_LOG_OBJECT (webrtc, "using previous negotiatied transceiver %"
+ GST_PTR_FORMAT " with mid %s into media index %u", trans,
+ trans->mid, media_idx);
+
+ /* FIXME: deal with format changes */
+ gst_sdp_media_copy (last_media, &media);
+ _media_replace_direction (media, trans->direction);
+
+ if (bundled_mids) {
+ const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+ g_assert (mid);
+ g_string_append_printf (bundled_mids, " %s", mid);
+ }
+
+ gst_sdp_message_add_media (ret, media);
+ media_idx++;
+
+ gst_sdp_media_free (media);
+ seen_transceivers = g_list_prepend (seen_transceivers, trans);
+ break;
+ }
+ }
+ } else if (g_strcmp0 (gst_sdp_media_get_media (last_media),
+ "application") == 0) {
+ GstSDPMedia media = { 0, };
+ gst_sdp_media_init (&media);
+ if (_add_data_channel_offer (webrtc, ret, &media, bundled_mids, 0,
+ bundle_ufrag, bundle_pwd)) {
+ gst_sdp_message_add_media (ret, &media);
+ media_idx++;
+ } else {
+ gst_sdp_media_uninit (&media);
+ }
+ }
+ }
+ }
+
+ /* add any extra streams */
for (i = 0; i < webrtc->priv->transceivers->len; i++) {
GstWebRTCRTPTransceiver *trans;
GstSDPMedia media = { 0, };
- gchar *ufrag, *pwd;
- gboolean bundle_only = bundled_mids
- && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
- && i != 0;
trans =
g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
i);
+ /* don't add transceivers twice */
+ if (g_list_find (seen_transceivers, trans))
+ continue;
+
+ /* don't add stopped transceivers */
+ if (trans->stopped)
+ continue;
+
gst_sdp_media_init (&media);
- /* mandated by JSEP */
- gst_sdp_media_add_attribute (&media, "setup", "actpass");
- /* FIXME: only needed when restarting ICE */
if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
reserved_pts = g_array_new (FALSE, FALSE, sizeof (guint));
- _generate_ice_credentials (&ufrag, &pwd);
- } else {
- ufrag = g_strdup (bundle_ufrag);
- pwd = g_strdup (bundle_pwd);
}
- gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag);
- gst_sdp_media_add_attribute (&media, "ice-pwd", pwd);
- g_free (ufrag);
- g_free (pwd);
-
- g_assert (reserved_pts != NULL);
+ GST_LOG_OBJECT (webrtc, "adding transceiver %" GST_PTR_FORMAT " at media "
+ "index %u", trans, media_idx);
if (sdp_media_from_transceiver (webrtc, &media, trans,
- GST_WEBRTC_SDP_TYPE_OFFER, i, bundled_mids, 0, bundle_only,
- reserved_pts)) {
- if (bundled_mids) {
- const gchar *mid = gst_sdp_media_get_attribute_val (&media, "mid");
-
- g_assert (mid);
- g_string_append_printf (bundled_mids, " %s", mid);
- }
+ GST_WEBRTC_SDP_TYPE_OFFER, media_idx, bundled_mids, 0, bundle_ufrag,
+ bundle_pwd, reserved_pts)) {
gst_sdp_message_add_media (ret, &media);
+ media_idx++;
} else {
gst_sdp_media_uninit (&media);
}
@@ -2217,62 +2502,28 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
g_array_free (reserved_pts, TRUE);
}
+ seen_transceivers = g_list_prepend (seen_transceivers, trans);
}
if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE) {
g_array_free (reserved_pts, TRUE);
}
- /* add data channel support */
- if (webrtc->priv->data_channels->len > 0) {
+ /* add a data channel if exists and not renegotiated */
+ if (_message_get_datachannel_index (ret) == G_MAXUINT) {
GstSDPMedia media = { 0, };
- gchar *ufrag, *pwd, *sdp_mid;
- gboolean bundle_only = bundled_mids
- && webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE
- && webrtc->priv->transceivers->len != 0;
-
gst_sdp_media_init (&media);
- /* mandated by JSEP */
- gst_sdp_media_add_attribute (&media, "setup", "actpass");
-
- /* FIXME: only needed when restarting ICE */
- if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
- _generate_ice_credentials (&ufrag, &pwd);
+ if (_add_data_channel_offer (webrtc, ret, &media, bundled_mids, 0,
+ bundle_ufrag, bundle_pwd)) {
+ gst_sdp_message_add_media (ret, &media);
+ media_idx++;
} else {
- ufrag = g_strdup (bundle_ufrag);
- pwd = g_strdup (bundle_pwd);
+ gst_sdp_media_uninit (&media);
}
- gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag);
- gst_sdp_media_add_attribute (&media, "ice-pwd", pwd);
- g_free (ufrag);
- g_free (pwd);
-
- gst_sdp_media_set_media (&media, "application");
- gst_sdp_media_set_port_info (&media, bundle_only ? 0 : 9, 0);
- gst_sdp_media_set_proto (&media, "UDP/DTLS/SCTP");
- gst_sdp_media_add_connection (&media, "IN", "IP4", "0.0.0.0", 0, 0);
- gst_sdp_media_add_format (&media, "webrtc-datachannel");
-
- if (bundle_only)
- gst_sdp_media_add_attribute (&media, "bundle-only", NULL);
-
- sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (&media),
- webrtc->priv->media_counter++);
- gst_sdp_media_add_attribute (&media, "mid", sdp_mid);
- if (bundled_mids)
- g_string_append_printf (bundled_mids, " %s", sdp_mid);
- g_free (sdp_mid);
-
- /* FIXME: negotiate this properly */
- gst_sdp_media_add_attribute (&media, "sctp-port", "5000");
-
- _get_or_create_data_channel_transports (webrtc,
- bundled_mids ? 0 : webrtc->priv->transceivers->len);
- _add_fingerprint_to_media (webrtc->priv->sctp_transport->transport, &media);
-
- gst_sdp_message_add_media (ret, &media);
}
+ g_assert (media_idx == gst_sdp_message_medias_len (ret));
+
if (bundled_mids) {
gchar *mids = g_string_free (bundled_mids, FALSE);
@@ -2291,6 +2542,8 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
/* XXX: only true for the initial offerer */
g_object_set (webrtc->priv->ice, "controller", TRUE, NULL);
+ g_list_free (seen_transceivers);
+
return ret;
}
@@ -2417,6 +2670,7 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
gchar *bundle_ufrag = NULL;
gchar *bundle_pwd = NULL;
GList *seen_transceivers = NULL;
+ GstSDPMessage *last_answer = _get_latest_answer (webrtc);
if (!webrtc->pending_remote_description) {
GST_ERROR_OBJECT (webrtc,
@@ -2428,6 +2682,9 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
goto out;
if (bundled) {
+ GStrv last_bundle = NULL;
+ guint bundle_media_index;
+
if (!_get_bundle_index (pending_remote->sdp, bundled, &bundle_idx)) {
GST_ERROR_OBJECT (webrtc, "Bundle tag is %s but no media found matching",
bundled[0]);
@@ -2438,18 +2695,28 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
bundled_mids = g_string_new ("BUNDLE");
}
- _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+ if (last_answer && _parse_bundle (last_answer, &last_bundle)
+ && last_bundle && last_bundle[0]
+ && _get_bundle_index (last_answer, last_bundle, &bundle_media_index)) {
+ bundle_ufrag =
+ g_strdup (_media_get_ice_ufrag (last_answer, bundle_media_index));
+ bundle_pwd =
+ g_strdup (_media_get_ice_pwd (last_answer, bundle_media_index));
+ } else {
+ _generate_ice_credentials (&bundle_ufrag, &bundle_pwd);
+ }
+
+ g_strfreev (last_bundle);
}
gst_sdp_message_new (&ret);
- /* FIXME: session id and version need special handling depending on the state we're in */
gst_sdp_message_set_version (ret, "0");
{
const GstSDPOrigin *offer_origin =
gst_sdp_message_get_origin (pending_remote->sdp);
- gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id, "0", "IN",
- "IP4", "0.0.0.0");
+ gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id,
+ offer_origin->sess_version, "IN", "IP4", "0.0.0.0");
}
gst_sdp_message_set_session_name (ret, "-");
@@ -2465,17 +2732,10 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
for (i = 0; i < gst_sdp_message_medias_len (pending_remote->sdp); i++) {
GstSDPMedia *media = NULL;
GstSDPMedia *offer_media;
- GstWebRTCRTPTransceiver *rtp_trans = NULL;
- WebRTCTransceiver *trans = NULL;
- GstWebRTCRTPTransceiverDirection offer_dir, answer_dir;
GstWebRTCDTLSSetup offer_setup, answer_setup;
- GstCaps *offer_caps, *answer_caps = NULL;
- guint j;
- guint k;
- gint target_pt = -1;
- gint original_target_pt = -1;
- guint target_ssrc = 0;
+ guint j, k;
gboolean bundle_only;
+ const gchar *mid;
offer_media =
(GstSDPMedia *) gst_sdp_message_get_media (pending_remote->sdp, i);
@@ -2489,13 +2749,19 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
{
- /* FIXME: only needed when restarting ICE */
gchar *ufrag, *pwd;
- if (!bundled) {
- _generate_ice_credentials (&ufrag, &pwd);
+
+ /* FIXME: deal with ICE restarts */
+ if (last_answer && i < gst_sdp_message_medias_len (last_answer)) {
+ ufrag = g_strdup (_media_get_ice_ufrag (last_answer, i));
+ pwd = g_strdup (_media_get_ice_pwd (last_answer, i));
} else {
- ufrag = g_strdup (bundle_ufrag);
- pwd = g_strdup (bundle_pwd);
+ if (!bundled) {
+ _generate_ice_credentials (&ufrag, &pwd);
+ } else {
+ ufrag = g_strdup (bundle_ufrag);
+ pwd = g_strdup (bundle_pwd);
+ }
}
gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
@@ -2514,6 +2780,10 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
}
}
+ mid = gst_sdp_media_get_attribute_val (media, "mid");
+ /* XXX: not strictly required but a lot of functionality requires a mid */
+ g_assert (mid);
+
/* set the a=setup: attribute */
offer_setup = _get_dtls_setup_from_media (offer_media);
answer_setup = _intersect_dtls_setup (offer_setup);
@@ -2560,8 +2830,6 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
bundled_mids ? bundle_idx : i);
if (bundled_mids) {
- const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
-
g_assert (mid);
g_string_append_printf (bundled_mids, " %s", mid);
}
@@ -2570,69 +2838,97 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
media);
} else if (g_strcmp0 (gst_sdp_media_get_media (offer_media), "audio") == 0
|| g_strcmp0 (gst_sdp_media_get_media (offer_media), "video") == 0) {
- gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
-
- offer_caps = gst_caps_new_empty ();
- for (j = 0; j < gst_sdp_media_formats_len (offer_media); j++) {
- guint pt = atoi (gst_sdp_media_get_format (offer_media, j));
- GstCaps *caps;
+ GstCaps *offer_caps, *answer_caps = NULL;
+ GstWebRTCRTPTransceiver *rtp_trans = NULL;
+ WebRTCTransceiver *trans = NULL;
+ GstWebRTCRTPTransceiverDirection offer_dir, answer_dir;
+ gint target_pt = -1;
+ gint original_target_pt = -1;
+ guint target_ssrc = 0;
- caps = gst_sdp_media_get_caps_from_media (offer_media, pt);
-
- /* gst_sdp_media_get_caps_from_media() produces caps with name
- * "application/x-unknown" which will fail intersection with
- * "application/x-rtp" caps so mangle the returns caps to have the
- * correct name here */
- for (k = 0; k < gst_caps_get_size (caps); k++) {
- GstStructure *s = gst_caps_get_structure (caps, k);
- gst_structure_set_name (s, "application/x-rtp");
- }
-
- gst_caps_append (offer_caps, caps);
- }
-
- for (j = 0; j < webrtc->priv->transceivers->len; j++) {
- GstCaps *trans_caps;
+ gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
+ offer_caps = _rtp_caps_from_media (offer_media);
+
+ if (last_answer && i < gst_sdp_message_medias_len (last_answer)
+ && (rtp_trans =
+ _find_transceiver (webrtc, mid,
+ (FindTransceiverFunc) match_for_mid))) {
+ const GstSDPMedia *last_media =
+ gst_sdp_message_get_media (last_answer, i);
+ const gchar *last_mid =
+ gst_sdp_media_get_attribute_val (last_media, "mid");
+
+ /* FIXME: assumes no shenanigans with recycling transceivers */
+ g_assert (g_strcmp0 (mid, last_mid) == 0);
+
+ if (!answer_caps
+ && (rtp_trans->direction ==
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV
+ || rtp_trans->direction ==
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY))
+ answer_caps =
+ _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, i);
+ if (!answer_caps
+ && (rtp_trans->direction ==
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV
+ || rtp_trans->direction ==
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY))
+ answer_caps =
+ _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SRC, i);
+ if (!answer_caps)
+ answer_caps = _rtp_caps_from_media (last_media);
+
+ /* XXX: In theory we're meant to use the sendrecv formats for the
+ * inactive direction however we don't know what that may be and would
+ * require asking outside what it expects to possibly send later */
+
+ GST_LOG_OBJECT (webrtc, "Found existing previously negotiated "
+ "transceiver %" GST_PTR_FORMAT " from mid %s for mline %u "
+ "using caps %" GST_PTR_FORMAT, rtp_trans, mid, i, answer_caps);
+ } else {
+ for (j = 0; j < webrtc->priv->transceivers->len; j++) {
+ GstCaps *trans_caps;
- rtp_trans =
- g_array_index (webrtc->priv->transceivers,
- GstWebRTCRTPTransceiver *, j);
+ rtp_trans =
+ g_array_index (webrtc->priv->transceivers,
+ GstWebRTCRTPTransceiver *, j);
- if (g_list_find (seen_transceivers, rtp_trans)) {
- /* Don't double allocate a transceiver to multiple mlines */
- rtp_trans = NULL;
- continue;
- }
+ if (g_list_find (seen_transceivers, rtp_trans)) {
+ /* Don't double allocate a transceiver to multiple mlines */
+ rtp_trans = NULL;
+ continue;
+ }
- trans_caps =
- _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, j);
-
- GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT
- " and %" GST_PTR_FORMAT, offer_caps, trans_caps);
-
- /* FIXME: technically this is a little overreaching as some fields we
- * we can deal with not having and/or we may have unrecognized fields
- * that we cannot actually support */
- if (trans_caps) {
- answer_caps = gst_caps_intersect (offer_caps, trans_caps);
- if (answer_caps && !gst_caps_is_empty (answer_caps)) {
- GST_LOG_OBJECT (webrtc,
- "found compatible transceiver %" GST_PTR_FORMAT
- " for offer media %u", rtp_trans, i);
- if (trans_caps)
- gst_caps_unref (trans_caps);
- break;
- } else {
- if (answer_caps) {
- gst_caps_unref (answer_caps);
- answer_caps = NULL;
+ trans_caps =
+ _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, j);
+
+ GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT
+ " and %" GST_PTR_FORMAT, offer_caps, trans_caps);
+
+ /* FIXME: technically this is a little overreaching as some fields we
+ * we can deal with not having and/or we may have unrecognized fields
+ * that we cannot actually support */
+ if (trans_caps) {
+ answer_caps = gst_caps_intersect (offer_caps, trans_caps);
+ if (answer_caps && !gst_caps_is_empty (answer_caps)) {
+ GST_LOG_OBJECT (webrtc,
+ "found compatible transceiver %" GST_PTR_FORMAT
+ " for offer media %u", rtp_trans, i);
+ if (trans_caps)
+ gst_caps_unref (trans_caps);
+ break;
+ } else {
+ if (answer_caps) {
+ gst_caps_unref (answer_caps);
+ answer_caps = NULL;
+ }
+ if (trans_caps)
+ gst_caps_unref (trans_caps);
+ rtp_trans = NULL;
}
- if (trans_caps)
- gst_caps_unref (trans_caps);
+ } else {
rtp_trans = NULL;
}
- } else {
- rtp_trans = NULL;
}
}
@@ -2652,6 +2948,9 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
if (!rtp_trans) {
trans = _create_webrtc_transceiver (webrtc, answer_dir, i);
rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
+
+ GST_LOG_OBJECT (webrtc, "Created new transceiver %" GST_PTR_FORMAT
+ " for mline %u", trans, i);
} else {
trans = WEBRTC_TRANSCEIVER (rtp_trans);
}
@@ -2699,18 +2998,19 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
if (!trans->stream) {
TransportStream *item;
- if (bundled_mids) {
- const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
- item = _get_or_create_transport_stream (webrtc, bundle_idx, FALSE);
-
- g_assert (mid);
- g_string_append_printf (bundled_mids, " %s", mid);
- } else {
- item = _get_or_create_transport_stream (webrtc, i, FALSE);
- }
+ item =
+ _get_or_create_transport_stream (webrtc,
+ bundled_mids ? bundle_idx : i, FALSE);
webrtc_transceiver_set_transport (trans, item);
}
+ if (bundled_mids) {
+ const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
+
+ g_assert (mid);
+ g_string_append_printf (bundled_mids, " %s", mid);
+ }
+
/* set the a=fingerprint: for this transport */
_add_fingerprint_to_media (trans->stream->transport, media);
@@ -2750,8 +3050,7 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
g_object_set (webrtc->priv->ice, "controller", FALSE, NULL);
out:
- if (bundled)
- g_strfreev (bundled);
+ g_strfreev (bundled);
g_list_free (seen_transceivers);
@@ -2982,7 +3281,14 @@ _connect_output_stream (GstWebRTCBin * webrtc,
*/
gchar *pad_name;
- GST_INFO_OBJECT (webrtc, "linking output stream %u", session_id);
+ if (stream->output_connected) {
+ GST_DEBUG_OBJECT (webrtc, "stream %" GST_PTR_FORMAT " is already "
+ "connected to rtpbin. Not connecting", stream);
+ return;
+ }
+
+ GST_INFO_OBJECT (webrtc, "linking output stream %u %" GST_PTR_FORMAT,
+ session_id, stream);
pad_name = g_strdup_printf ("recv_rtp_sink_%u", session_id);
if (!gst_element_link_pads (GST_ELEMENT (stream->receive_bin),
@@ -2994,6 +3300,8 @@ _connect_output_stream (GstWebRTCBin * webrtc,
/* The webrtcbin src_%u output pads will be created when rtpbin receives
* data on that stream in on_rtpbin_pad_added() */
+
+ stream->output_connected = TRUE;
}
typedef struct
@@ -3037,6 +3345,38 @@ _filter_sdp_fields (GQuark field_id, const GValue * value,
}
static void
+_set_rtx_ptmap_from_stream (GstWebRTCBin * webrtc, TransportStream * stream)
+{
+ gint *rtx_pt;
+ gsize rtx_count;
+
+ rtx_pt = transport_stream_get_all_pt (stream, "RTX", &rtx_count);
+ GST_LOG_OBJECT (stream, "have %" G_GSIZE_FORMAT " rtx payloads", rtx_count);
+ if (rtx_pt) {
+ GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+ gsize i;
+
+ for (i = 0; i < rtx_count; i++) {
+ GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt[i]);
+ const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
+ const gchar *apt = gst_structure_get_string (s, "apt");
+
+ GST_LOG_OBJECT (stream, "setting rtx mapping: %s -> %u", apt, rtx_pt[i]);
+ gst_structure_set (pt_map, apt, G_TYPE_UINT, rtx_pt[i], NULL);
+ }
+
+ GST_DEBUG_OBJECT (stream, "setting payload map on %" GST_PTR_FORMAT " : %"
+ GST_PTR_FORMAT " and %" GST_PTR_FORMAT, stream->rtxreceive,
+ stream->rtxsend, pt_map);
+
+ if (stream->rtxreceive)
+ g_object_set (stream->rtxreceive, "payload-type-map", pt_map, NULL);
+ if (stream->rtxsend)
+ g_object_set (stream->rtxsend, "payload-type-map", pt_map, NULL);
+ }
+}
+
+static void
_update_transport_ptmap_from_media (GstWebRTCBin * webrtc,
TransportStream * stream, const GstSDPMessage * sdp, guint media_idx)
{
@@ -3109,7 +3449,7 @@ static void
_update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
const GstSDPMessage * sdp, guint media_idx,
TransportStream * stream, GstWebRTCRTPTransceiver * rtp_trans,
- GStrv bundled, guint bundle_idx, gboolean * should_connect_bundle_stream)
+ GStrv bundled, guint bundle_idx)
{
WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction;
@@ -3117,6 +3457,7 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
GstWebRTCDTLSSetup new_setup;
gboolean new_rtcp_mux, new_rtcp_rsize;
+ ReceiveState receive_state = 0;
int i;
rtp_trans->mline = media_idx;
@@ -3156,6 +3497,7 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
return;
if (prev_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
+ && new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE
&& prev_dir != new_dir) {
GST_FIXME_OBJECT (webrtc, "implement transceiver direction changes");
return;
@@ -3182,10 +3524,28 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
}
if (new_dir != prev_dir) {
- ReceiveState receive_state = 0;
-
GST_TRACE_OBJECT (webrtc, "transceiver direction change");
+ if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE) {
+ GstWebRTCBinPad *pad;
+
+ pad = _find_pad_for_mline (webrtc, GST_PAD_SRC, media_idx);
+ if (pad) {
+ GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+ if (target) {
+ GstPad *peer = gst_pad_get_peer (target);
+ if (peer) {
+ gst_pad_send_event (peer, gst_event_new_eos ());
+ gst_object_unref (peer);
+ }
+ gst_object_unref (target);
+ }
+ gst_object_unref (pad);
+ }
+
+ /* XXX: send eos event up the sink pad as well? */
+ }
+
if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY ||
new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) {
GstWebRTCBinPad *pad =
@@ -3230,10 +3590,8 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
webrtc_transceiver_set_transport (trans, item);
}
- if (!bundled)
- _connect_output_stream (webrtc, trans->stream, media_idx);
- else
- *should_connect_bundle_stream = TRUE;
+ _connect_output_stream (webrtc, trans->stream,
+ bundled ? bundle_idx : media_idx);
/* delay adding the pad until rtpbin creates the recv output pad
* to ghost to so queries/events travel through the pipeline correctly
* as soon as the pad is added */
@@ -3245,20 +3603,25 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
receive_state = RECEIVE_STATE_DROP;
}
- if (!bundled || bundle_idx == media_idx)
- g_object_set (stream, "dtls-client",
- new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
-
- /* Must be after setting the "dtls-client" so that data is not pushed into
- * the dtlssrtp elements before the ssl direction has been set which will
- * throw SSL errors */
- if (receive_state > 0)
- transport_receive_bin_set_receive_state (stream->receive_bin,
- receive_state);
-
rtp_trans->mline = media_idx;
rtp_trans->current_direction = new_dir;
}
+
+ if (!bundled || bundle_idx == media_idx) {
+ if (stream->rtxsend || stream->rtxreceive) {
+ _set_rtx_ptmap_from_stream (webrtc, stream);
+ }
+
+ g_object_set (stream, "dtls-client",
+ new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
+ }
+
+ /* Must be after setting the "dtls-client" so that data is not pushed into
+ * the dtlssrtp elements before the ssl direction has been set which will
+ * throw SSL errors */
+ if (receive_state > 0)
+ transport_receive_bin_set_receive_state (stream->receive_bin,
+ receive_state);
}
/* must be called with the pc lock held */
@@ -3441,7 +3804,6 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
gboolean ret = FALSE;
GStrv bundled = NULL;
guint bundle_idx = 0;
- gboolean should_connect_bundle_stream = FALSE;
TransportStream *bundle_stream = NULL;
if (!_parse_bundle (sdp->sdp, &bundled))
@@ -3501,7 +3863,7 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
g_strcmp0 (gst_sdp_media_get_media (media), "video") == 0) {
if (trans) {
_update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
- trans, bundled, bundle_idx, &should_connect_bundle_stream);
+ trans, bundled, bundle_idx);
} else {
trans = _find_transceiver (webrtc, NULL,
(FindTransceiverFunc) _find_compatible_unassociated_transceiver);
@@ -3515,7 +3877,7 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
_get_direction_from_media (media), i));
}
_update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
- trans, bundled, bundle_idx, &should_connect_bundle_stream);
+ trans, bundled, bundle_idx);
}
} else if (_message_media_is_datachannel (sdp->sdp, i)) {
_update_data_channel_from_sdp_media (webrtc, sdp->sdp, i, stream);
@@ -3525,16 +3887,11 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
}
}
- if (should_connect_bundle_stream) {
- g_assert (bundle_stream);
- _connect_output_stream (webrtc, bundle_stream, bundle_idx);
- }
-
ret = TRUE;
done:
- if (bundled)
- g_strfreev (bundled);
+ g_strfreev (bundled);
+
return ret;
}
@@ -3826,8 +4183,7 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
}
out:
- if (bundled)
- g_strfreev (bundled);
+ g_strfreev (bundled);
PC_UNLOCK (webrtc);
gst_promise_reply (sd->promise, NULL);
@@ -4243,8 +4599,11 @@ gst_webrtc_bin_create_data_channel (GstWebRTCBin * webrtc, const gchar * label,
_link_data_channel_to_sctp (webrtc, ret);
if (webrtc->priv->sctp_transport &&
webrtc->priv->sctp_transport->association_established
- && !ret->negotiated)
+ && !ret->negotiated) {
gst_webrtc_data_channel_start_negotiation (ret);
+ } else {
+ _update_need_negotiation (webrtc);
+ }
}
PC_UNLOCK (webrtc);
@@ -4268,6 +4627,7 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
TransportStream *stream;
GstWebRTCBinPad *pad;
guint media_idx = 0;
+ gboolean found_ssrc = FALSE;
guint i;
if (sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc,
@@ -4287,10 +4647,15 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
&g_array_index (stream->remote_ssrcmap, SsrcMapItem, i);
if (item->ssrc == ssrc) {
media_idx = item->media_idx;
+ found_ssrc = TRUE;
break;
}
}
+ if (!found_ssrc) {
+ GST_WARNING_OBJECT (webrtc, "Could not find ssrc %u", ssrc);
+ }
+
rtp_trans = _find_transceiver_for_mline (webrtc, media_idx);
if (!rtp_trans)
g_warn_if_reached ();
@@ -4351,7 +4716,8 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
GstWebRTCBin * webrtc)
{
TransportStream *stream;
- GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+ gboolean have_rtx = FALSE;
+ GstStructure *pt_map = NULL;
GstElement *ret = NULL;
GstWebRTCRTPTransceiver *trans;
@@ -4359,41 +4725,28 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
trans = _find_transceiver (webrtc, &session_id,
(FindTransceiverFunc) transceiver_match_for_mline);
- if (stream) {
- guint i;
-
- for (i = 0; i < stream->ptmap->len; i++) {
- PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
- if (!gst_caps_is_empty (item->caps)) {
- GstStructure *s = gst_caps_get_structure (item->caps, 0);
- gint pt;
- const gchar *apt_str = gst_structure_get_string (s, "apt");
-
- if (!apt_str)
- continue;
-
- if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "RTX") &&
- gst_structure_get_int (s, "payload", &pt)) {
- gst_structure_set (pt_map, apt_str, G_TYPE_UINT, pt, NULL);
- }
- }
- }
- }
+ if (stream)
+ have_rtx = transport_stream_get_pt (stream, "RTX") != 0;
GST_LOG_OBJECT (webrtc, "requesting aux sender for stream %" GST_PTR_FORMAT
" with transport %" GST_PTR_FORMAT " and pt map %" GST_PTR_FORMAT, stream,
trans, pt_map);
- if (gst_structure_n_fields (pt_map)) {
+ if (have_rtx) {
GstElement *rtx;
GstPad *pad;
gchar *name;
+ if (stream->rtxsend) {
+ GST_WARNING_OBJECT (webrtc, "rtprtxsend already created! rtpbin bug?!");
+ goto out;
+ }
+
GST_INFO ("creating AUX sender");
ret = gst_bin_new (NULL);
rtx = gst_element_factory_make ("rtprtxsend", NULL);
- g_object_set (rtx, "payload-type-map", pt_map, "max-size-packets", 500,
- NULL);
+ g_object_set (rtx, "max-size-packets", 500, NULL);
+ _set_rtx_ptmap_from_stream (webrtc, stream);
if (WEBRTC_TRANSCEIVER (trans)->local_rtx_ssrc_map)
g_object_set (rtx, "ssrc-map",
@@ -4412,9 +4765,13 @@ on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
gst_element_add_pad (ret, gst_ghost_pad_new (name, pad));
g_free (name);
gst_object_unref (pad);
+
+ stream->rtxsend = gst_object_ref (rtx);
}
- gst_structure_free (pt_map);
+out:
+ if (pt_map)
+ gst_structure_free (pt_map);
return ret;
}
@@ -4437,28 +4794,27 @@ on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
rtx_pt = transport_stream_get_pt (stream, "RTX");
}
- GST_LOG_OBJECT (webrtc, "requesting aux receiver for stream %" GST_PTR_FORMAT
- " with pt red:%u rtx:%u", stream, red_pt, rtx_pt);
+ GST_LOG_OBJECT (webrtc, "requesting aux receiver for stream %" GST_PTR_FORMAT,
+ stream);
if (red_pt || rtx_pt)
ret = gst_bin_new (NULL);
if (rtx_pt) {
- GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt);
- GstElement *rtx = gst_element_factory_make ("rtprtxreceive", NULL);
- GstStructure *pt_map;
- const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
+ if (stream->rtxreceive) {
+ GST_WARNING_OBJECT (webrtc,
+ "rtprtxreceive already created! rtpbin bug?!");
+ goto error;
+ }
- gst_bin_add (GST_BIN (ret), rtx);
+ stream->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
+ _set_rtx_ptmap_from_stream (webrtc, stream);
- pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
- gst_structure_set (pt_map, gst_structure_get_string (s, "apt"), G_TYPE_UINT,
- rtx_pt, NULL);
- g_object_set (rtx, "payload-type-map", pt_map, NULL);
+ gst_bin_add (GST_BIN (ret), stream->rtxreceive);
- sinkpad = gst_element_get_static_pad (rtx, "sink");
+ sinkpad = gst_element_get_static_pad (stream->rtxreceive, "sink");
- prev = rtx;
+ prev = gst_object_ref (stream->rtxreceive);
}
if (red_pt) {
@@ -4496,7 +4852,13 @@ on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
gst_element_add_pad (ret, ghost);
}
+out:
return ret;
+
+error:
+ if (ret)
+ gst_object_unref (ret);
+ goto out;
}
static GstElement *
@@ -4795,7 +5157,9 @@ gst_webrtc_bin_change_state (GstElement * element, GstStateChange transition)
if (!_have_nice_elements (webrtc) || !_have_dtls_elements (webrtc))
return GST_STATE_CHANGE_FAILURE;
_start_thread (webrtc);
+ PC_LOCK (webrtc);
_update_need_negotiation (webrtc);
+ PC_UNLOCK (webrtc);
break;
}
case GST_STATE_CHANGE_READY_TO_PAUSED:
@@ -4896,6 +5260,9 @@ gst_webrtc_bin_release_pad (GstElement * element, GstPad * pad)
webrtc_pad->trans = NULL;
_remove_pad (webrtc, webrtc_pad);
+ PC_LOCK (webrtc);
+ _update_need_negotiation (webrtc);
+ PC_UNLOCK (webrtc);
}
static void
diff --git a/ext/webrtc/gstwebrtcbin.h b/ext/webrtc/gstwebrtcbin.h
index 84158e887..546ae6d5c 100644
--- a/ext/webrtc/gstwebrtcbin.h
+++ b/ext/webrtc/gstwebrtcbin.h
@@ -131,6 +131,8 @@ struct _GstWebRTCBinPrivate
/* count of the number of media streams we've offered for uniqueness */
/* FIXME: overflow? */
guint media_counter;
+ /* the number of times create_offer has been called for the version field */
+ guint offer_count;
GstStructure *stats;
};
diff --git a/ext/webrtc/transportstream.c b/ext/webrtc/transportstream.c
index 01fa2dc91..01261ae1b 100644
--- a/ext/webrtc/transportstream.c
+++ b/ext/webrtc/transportstream.c
@@ -75,6 +75,36 @@ transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name)
return ret;
}
+int *
+transport_stream_get_all_pt (TransportStream * stream,
+ const gchar * encoding_name, gsize * pt_len)
+{
+ guint i;
+ gsize ret_i = 0;
+ gsize ret_size = 8;
+ int *ret = NULL;
+
+ for (i = 0; i < stream->ptmap->len; i++) {
+ PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
+ if (!gst_caps_is_empty (item->caps)) {
+ GstStructure *s = gst_caps_get_structure (item->caps, 0);
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
+ encoding_name)) {
+ if (!ret)
+ ret = g_new0 (int, ret_size);
+ if (ret_i >= ret_size) {
+ ret_size *= 2;
+ ret = g_realloc_n (ret, ret_size, sizeof (int));
+ }
+ ret[ret_i++] = item->pt;
+ }
+ }
+ }
+
+ *pt_len = ret_i;
+ return ret;
+}
+
static void
transport_stream_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
@@ -152,6 +182,14 @@ transport_stream_dispose (GObject * object)
gst_object_unref (stream->rtcp_transport);
stream->rtcp_transport = NULL;
+ if (stream->rtxsend)
+ gst_object_unref (stream->rtxsend);
+ stream->rtxsend = NULL;
+
+ if (stream->rtxreceive)
+ gst_object_unref (stream->rtxreceive);
+ stream->rtxreceive = NULL;
+
GST_OBJECT_PARENT (object) = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
diff --git a/ext/webrtc/transportstream.h b/ext/webrtc/transportstream.h
index 8b90a946a..97d4f215b 100644
--- a/ext/webrtc/transportstream.h
+++ b/ext/webrtc/transportstream.h
@@ -61,6 +61,10 @@ struct _TransportStream
GArray *ptmap; /* array of PtMapItem's */
GArray *remote_ssrcmap; /* array of SsrcMapItem's */
+ gboolean output_connected; /* whether receive bin is connected to rtpbin */
+
+ GstElement *rtxsend;
+ GstElement *rtxreceive;
};
struct _TransportStreamClass
@@ -72,6 +76,9 @@ TransportStream * transport_stream_new (GstWebRTCBin * webrtc,
guint session_id);
int transport_stream_get_pt (TransportStream * stream,
const gchar * encoding_name);
+int * transport_stream_get_all_pt (TransportStream * stream,
+ const gchar * encoding_name,
+ gsize * pt_len);
GstCaps * transport_stream_get_caps_for_pt (TransportStream * stream,
guint pt);
diff --git a/ext/webrtc/utils.c b/ext/webrtc/utils.c
index ac050bf01..f2225ef34 100644
--- a/ext/webrtc/utils.c
+++ b/ext/webrtc/utils.c
@@ -21,6 +21,8 @@
# include "config.h"
#endif
+#include <stdlib.h>
+
#include "utils.h"
#include "gstwebrtcbin.h"
@@ -53,28 +55,48 @@ _find_pad_template (GstElement * element, GstPadDirection direction,
}
GstSDPMessage *
-_get_latest_sdp (GstWebRTCBin * webrtc)
+_get_latest_offer (GstWebRTCBin * webrtc)
{
if (webrtc->current_local_description &&
- webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+ webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
- webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+ webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_remote_description->sdp;
}
+
+ return NULL;
+}
+
+GstSDPMessage *
+_get_latest_answer (GstWebRTCBin * webrtc)
+{
if (webrtc->current_local_description &&
- webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+ webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
- webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+ webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
return webrtc->current_remote_description->sdp;
}
return NULL;
}
+GstSDPMessage *
+_get_latest_sdp (GstWebRTCBin * webrtc)
+{
+ GstSDPMessage *ret = NULL;
+
+ if ((ret = _get_latest_answer (webrtc)))
+ return ret;
+ if ((ret = _get_latest_offer (webrtc)))
+ return ret;
+
+ return NULL;
+}
+
struct pad_block *
_create_pad_block (GstElement * element, GstPad * pad, gulong block_id,
gpointer user_data, GDestroyNotify notify)
@@ -142,3 +164,31 @@ _g_checksum_to_webrtc_string (GChecksumType type)
return NULL;
}
}
+
+GstCaps *
+_rtp_caps_from_media (const GstSDPMedia * media)
+{
+ GstCaps *ret;
+ int i, j;
+
+ ret = gst_caps_new_empty ();
+ for (i = 0; i < gst_sdp_media_formats_len (media); i++) {
+ guint pt = atoi (gst_sdp_media_get_format (media, i));
+ GstCaps *caps;
+
+ caps = gst_sdp_media_get_caps_from_media (media, pt);
+
+ /* gst_sdp_media_get_caps_from_media() produces caps with name
+ * "application/x-unknown" which will fail intersection with
+ * "application/x-rtp" caps so mangle the returns caps to have the
+ * correct name here */
+ for (j = 0; j < gst_caps_get_size (caps); j++) {
+ GstStructure *s = gst_caps_get_structure (caps, j);
+ gst_structure_set_name (s, "application/x-rtp");
+ }
+
+ gst_caps_append (ret, caps);
+ }
+
+ return ret;
+}
diff --git a/ext/webrtc/utils.h b/ext/webrtc/utils.h
index 847264730..ee56d3ef8 100644
--- a/ext/webrtc/utils.h
+++ b/ext/webrtc/utils.h
@@ -47,6 +47,8 @@ GstPadTemplate * _find_pad_template (GstElement * element,
const gchar * name);
GstSDPMessage * _get_latest_sdp (GstWebRTCBin * webrtc);
+GstSDPMessage * _get_latest_offer (GstWebRTCBin * webrtc);
+GstSDPMessage * _get_latest_answer (GstWebRTCBin * webrtc);
GstWebRTCICEStream * _find_ice_stream_for_session (GstWebRTCBin * webrtc,
guint session_id);
@@ -74,6 +76,8 @@ G_GNUC_INTERNAL
gchar * _enum_value_to_string (GType type, guint value);
G_GNUC_INTERNAL
const gchar * _g_checksum_to_webrtc_string (GChecksumType type);
+G_GNUC_INTERNAL
+GstCaps * _rtp_caps_from_media (const GstSDPMedia * media);
G_END_DECLS
diff --git a/ext/webrtc/webrtcsdp.c b/ext/webrtc/webrtcsdp.c
index cad0540b6..56ecc7913 100644
--- a/ext/webrtc/webrtcsdp.c
+++ b/ext/webrtc/webrtcsdp.c
@@ -212,7 +212,7 @@ _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
return TRUE;
}
-static const gchar *
+const gchar *
_media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_ufrag;
@@ -227,7 +227,7 @@ _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
return ice_ufrag;
}
-static const gchar *
+const gchar *
_media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_pwd;
@@ -437,11 +437,13 @@ _media_replace_direction (GstSDPMedia * media,
if (g_strcmp0 (attr->key, "sendonly") == 0
|| g_strcmp0 (attr->key, "sendrecv") == 0
- || g_strcmp0 (attr->key, "recvonly") == 0) {
+ || g_strcmp0 (attr->key, "recvonly") == 0
+ || g_strcmp0 (attr->key, "inactive") == 0) {
GstSDPAttribute new_attr = { 0, };
GST_TRACE ("replace %s with %s", attr->key, dir_str);
gst_sdp_attribute_set (&new_attr, dir_str, "");
gst_sdp_media_replace_attribute (media, i, &new_attr);
+ g_free (dir_str);
return;
}
}
@@ -768,6 +770,21 @@ _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
return TRUE;
}
+guint
+_message_get_datachannel_index (const GstSDPMessage * msg)
+{
+ guint i;
+
+ for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
+ if (_message_media_is_datachannel (msg, i)) {
+ g_assert (i < G_MAXUINT);
+ return i;
+ }
+ }
+
+ return G_MAXUINT;
+}
+
void
_get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
gchar ** ufrag, gchar ** pwd)
@@ -833,6 +850,8 @@ _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
if (!(*bundled)[0]) {
GST_ERROR ("Invalid format for BUNDLE group, expected at least "
"one mid (%s)", group);
+ g_strfreev (*bundled);
+ *bundled = NULL;
goto done;
}
} else {
diff --git a/ext/webrtc/webrtcsdp.h b/ext/webrtc/webrtcsdp.h
index a9a86fce5..7620091fa 100644
--- a/ext/webrtc/webrtcsdp.h
+++ b/ext/webrtc/webrtcsdp.h
@@ -89,6 +89,8 @@ void _get_ice_credentials_from_sdp_media (con
G_GNUC_INTERNAL
gboolean _message_media_is_datachannel (const GstSDPMessage * msg,
guint media_id);
+G_GNUC_INTERNAL
+guint _message_get_datachannel_index (const GstSDPMessage * msg);
G_GNUC_INTERNAL
gboolean _get_bundle_index (GstSDPMessage * sdp,
@@ -98,4 +100,11 @@ G_GNUC_INTERNAL
gboolean _parse_bundle (GstSDPMessage * sdp,
GStrv * bundled);
+G_GNUC_INTERNAL
+const gchar * _media_get_ice_pwd (const GstSDPMessage * msg,
+ guint media_idx);
+G_GNUC_INTERNAL
+const gchar * _media_get_ice_ufrag (const GstSDPMessage * msg,
+ guint media_idx);
+
#endif /* __WEBRTC_UTILS_H__ */
diff --git a/ext/webrtc/webrtctransceiver.c b/ext/webrtc/webrtctransceiver.c
index 64030acdb..b3739311c 100644
--- a/ext/webrtc/webrtctransceiver.c
+++ b/ext/webrtc/webrtctransceiver.c
@@ -25,9 +25,14 @@
#include "utils.h"
#include "webrtctransceiver.h"
+#define GST_CAT_DEFAULT webrtc_transceiver_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
#define webrtc_transceiver_parent_class parent_class
-G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver,
- GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
+G_DEFINE_TYPE_WITH_CODE (WebRTCTransceiver, webrtc_transceiver,
+ GST_TYPE_WEBRTC_RTP_TRANSCEIVER,
+ GST_DEBUG_CATEGORY_INIT (webrtc_transceiver_debug,
+ "webrtctransceiver", 0, "webrtctransceiver"););
#define DEFAULT_FEC_TYPE GST_WEBRTC_FEC_TYPE_NONE
#define DEFAULT_DO_NACK FALSE
@@ -172,6 +177,8 @@ webrtc_transceiver_finalize (GObject * object)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map = NULL;
+ gst_caps_replace (&trans->last_configured_caps, NULL);
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
diff --git a/ext/webrtc/webrtctransceiver.h b/ext/webrtc/webrtctransceiver.h
index f1e338f66..c03730415 100644
--- a/ext/webrtc/webrtctransceiver.h
+++ b/ext/webrtc/webrtctransceiver.h
@@ -44,6 +44,8 @@ struct _WebRTCTransceiver
GstWebRTCFECType fec_type;
guint fec_percentage;
gboolean do_nack;
+
+ GstCaps *last_configured_caps;
};
struct _WebRTCTransceiverClass
diff --git a/gst-libs/gst/webrtc/rtptransceiver.c b/gst-libs/gst/webrtc/rtptransceiver.c
index 332c1bd05..9fa884e04 100644
--- a/gst-libs/gst/webrtc/rtptransceiver.c
+++ b/gst-libs/gst/webrtc/rtptransceiver.c
@@ -39,7 +39,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCRTPTransceiver,
gst_webrtc_rtp_transceiver, GST_TYPE_OBJECT,
GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_transceiver_debug,
- "webrtctransceiver", 0, "webrtctransceiver");
+ "webrtcrtptransceiver", 0, "webrtcrtptransceiver");
);
enum
diff --git a/tests/check/elements/webrtcbin.c b/tests/check/elements/webrtcbin.c
index a9f52d6c8..15636aed8 100644
--- a/tests/check/elements/webrtcbin.c
+++ b/tests/check/elements/webrtcbin.c
@@ -41,6 +41,8 @@
#define TEST_GET_OFFEROR(t) (TEST_IS_OFFER_ELEMENT(t, t->webrtc1) ? (t)->webrtc1 : t->webrtc2)
#define TEST_GET_ANSWERER(t) (TEST_IS_OFFER_ELEMENT(t, t->webrtc1) ? (t)->webrtc2 : t->webrtc1)
+#define TEST_SDP_IS_LOCAL(t, e, d) ((TEST_IS_OFFER_ELEMENT (t, e) ^ ((d)->type == GST_WEBRTC_SDP_TYPE_OFFER)) == 0)
+
typedef enum
{
STATE_NEW,
@@ -626,12 +628,12 @@ _pad_added_fakesink (struct test_webrtc *t, GstElement * element,
}
static void
-_count_num_sdp_media (struct test_webrtc *t, GstElement * element,
- GstWebRTCSessionDescription * desc, gpointer user_data)
+on_negotiation_needed_hit (struct test_webrtc *t, GstElement * element,
+ gpointer user_data)
{
- guint expected = GPOINTER_TO_UINT (user_data);
+ guint *flag = (guint *) user_data;
- fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), expected);
+ *flag = 1;
}
typedef void (*ValidateSDPFunc) (struct test_webrtc * t, GstElement * element,
@@ -645,6 +647,9 @@ struct validate_sdp
struct validate_sdp *next;
};
+#define VAL_SDP_INIT(name,func,data,next) \
+ struct validate_sdp name = { func, data, next }
+
static GstWebRTCSessionDescription *
_check_validate_sdp (struct test_webrtc *t, GstElement * element,
GstPromise * promise, gpointer user_data)
@@ -698,13 +703,20 @@ test_validate_sdp (struct test_webrtc *t, struct validate_sdp *offer,
fail_unless (t->state == STATE_ANSWER_CREATED);
}
+static void
+_count_num_sdp_media (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ guint expected = GPOINTER_TO_UINT (user_data);
+
+ fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), expected);
+}
+
GST_START_TEST (test_sdp_no_media)
{
struct test_webrtc *t = test_webrtc_new ();
- struct validate_sdp offer =
- { _count_num_sdp_media, GUINT_TO_POINTER (0), NULL };
- struct validate_sdp answer =
- { _count_num_sdp_media, GUINT_TO_POINTER (0), NULL };
+ VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (0), NULL);
+ VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (0), NULL);
/* check that a no stream connection creates 0 media sections */
@@ -756,10 +768,8 @@ create_audio_test (void)
GST_START_TEST (test_audio)
{
struct test_webrtc *t = create_audio_test ();
- struct validate_sdp offer =
- { _count_num_sdp_media, GUINT_TO_POINTER (1), NULL };
- struct validate_sdp answer =
- { _count_num_sdp_media, GUINT_TO_POINTER (1), NULL };
+ VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (1), NULL);
+ VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (1), NULL);
/* check that a single stream connection creates the associated number
* of media sections */
@@ -786,10 +796,8 @@ create_audio_video_test (void)
GST_START_TEST (test_audio_video)
{
struct test_webrtc *t = create_audio_video_test ();
- struct validate_sdp offer =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
- struct validate_sdp answer =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
+ VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+ VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
/* check that a dual stream connection creates the associated number
* of media sections */
@@ -850,15 +858,14 @@ GST_START_TEST (test_media_direction)
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
- struct validate_sdp offer_direction =
- { on_sdp_media_direction, expected_offer, NULL };
- struct validate_sdp offer =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), &offer_direction };
- struct validate_sdp answer_direction =
- { on_sdp_media_direction, expected_answer, NULL };
- struct validate_sdp answer =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), &answer_direction };
GstHarness *h;
+ VAL_SDP_INIT (offer_direction, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2),
+ &offer_direction);
+ VAL_SDP_INIT (answer_direction, on_sdp_media_direction, expected_answer,
+ NULL);
+ VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (2),
+ &answer_direction);
/* check the default media directions for transceivers */
@@ -905,9 +912,8 @@ on_sdp_media_payload_types (struct test_webrtc *t, GstElement * element,
GST_START_TEST (test_payload_types)
{
struct test_webrtc *t = create_audio_video_test ();
- struct validate_sdp payloads = { on_sdp_media_payload_types, NULL, NULL };
- struct validate_sdp offer =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), &payloads };
+ VAL_SDP_INIT (payloads, on_sdp_media_payload_types, NULL, NULL);
+ VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2), &payloads);
GstWebRTCRTPTransceiver *trans;
GArray *transceivers;
@@ -956,8 +962,8 @@ GST_START_TEST (test_media_setup)
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "actpass" };
const gchar *expected_answer[] = { "active" };
- struct validate_sdp offer = { on_sdp_media_setup, expected_offer, NULL };
- struct validate_sdp answer = { on_sdp_media_setup, expected_answer, NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_setup, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_setup, expected_answer, NULL);
/* check the default dtls setup negotiation values */
test_validate_sdp (t, &offer, &answer);
@@ -1333,9 +1339,8 @@ GST_START_TEST (test_add_recvonly_transceiver)
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly" };
const gchar *expected_answer[] = { "sendonly" };
- struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
- struct validate_sdp answer =
- { on_sdp_media_direction, expected_answer, NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstCaps *caps;
GstHarness *h;
@@ -1372,9 +1377,8 @@ GST_START_TEST (test_recvonly_sendonly)
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly", "sendonly" };
const gchar *expected_answer[] = { "sendonly", "recvonly" };
- struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
- struct validate_sdp answer =
- { on_sdp_media_direction, expected_answer, NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstCaps *caps;
GstHarness *h;
GArray *transceivers;
@@ -1446,8 +1450,8 @@ GST_START_TEST (test_data_channel_create)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
gchar *label;
t->on_negotiation_needed = NULL;
@@ -1500,8 +1504,8 @@ GST_START_TEST (test_data_channel_remote_notify)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1575,8 +1579,8 @@ GST_START_TEST (test_data_channel_transfer_string)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1657,8 +1661,8 @@ GST_START_TEST (test_data_channel_transfer_data)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1715,8 +1719,8 @@ GST_START_TEST (test_data_channel_create_after_negotiate)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1776,8 +1780,8 @@ GST_START_TEST (test_data_channel_low_threshold)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1849,8 +1853,8 @@ GST_START_TEST (test_data_channel_max_message_size)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@@ -1904,8 +1908,8 @@ GST_START_TEST (test_data_channel_pre_negotiated)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel1 = NULL, *channel2 = NULL;
- struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
- struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
+ VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
+ VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
GstStructure *s;
gint n_ready = 0;
@@ -2028,17 +2032,16 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle)
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle_only[] = { NULL };
- struct validate_sdp count =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
- struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
- struct validate_sdp offer_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (1), &bundle_tag };
- struct validate_sdp answer_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (2), &bundle_tag };
- struct validate_sdp offer =
- { _check_bundle_only_media, &offer_bundle_only, &offer_non_reject };
- struct validate_sdp answer =
- { _check_bundle_only_media, &answer_bundle_only, &answer_non_reject };
+ VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+ VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
+ VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (1), &bundle_tag);
+ VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (2), &bundle_tag);
+ VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
+ &offer_non_reject);
+ VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
+ &answer_non_reject);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@@ -2068,13 +2071,12 @@ GST_START_TEST (test_bundle_audio_video_max_compat_max_bundle)
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *bundle_only[] = { NULL };
- struct validate_sdp count =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
- struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
- struct validate_sdp count_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (2), &bundle_tag };
- struct validate_sdp bundle_sdp =
- { _check_bundle_only_media, &bundle_only, &count_non_reject };
+ VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+ VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
+ VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (2), &bundle_tag);
+ VAL_SDP_INIT (bundle_sdp, _check_bundle_only_media, &bundle_only,
+ &count_non_reject);
/* We set a max-compat policy on the offering webrtcbin,
* this means that all the offered medias should be part
@@ -2106,18 +2108,17 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_none)
const gchar *answer_bundle[] = { NULL };
const gchar *answer_bundle_only[] = { NULL };
- struct validate_sdp count =
- { _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
- struct validate_sdp count_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (1), &count };
- struct validate_sdp offer_bundle_tag =
- { _check_bundle_tag, offer_bundle, &count_non_reject };
- struct validate_sdp answer_bundle_tag =
- { _check_bundle_tag, answer_bundle, &count_non_reject };
- struct validate_sdp offer =
- { _check_bundle_only_media, &offer_bundle_only, &offer_bundle_tag };
- struct validate_sdp answer =
- { _check_bundle_only_media, &answer_bundle_only, &answer_bundle_tag };
+ VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+ VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (1), &count);
+ VAL_SDP_INIT (offer_bundle_tag, _check_bundle_tag, offer_bundle,
+ &count_non_reject);
+ VAL_SDP_INIT (answer_bundle_tag, _check_bundle_tag, answer_bundle,
+ &count_non_reject);
+ VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
+ &offer_bundle_tag);
+ VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
+ &answer_bundle_tag);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@@ -2148,17 +2149,16 @@ GST_START_TEST (test_bundle_audio_video_data)
const gchar *answer_bundle_only[] = { NULL };
GObject *channel = NULL;
- struct validate_sdp count =
- { _count_num_sdp_media, GUINT_TO_POINTER (3), NULL };
- struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
- struct validate_sdp offer_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (1), &bundle_tag };
- struct validate_sdp answer_non_reject =
- { _count_non_rejected_media, GUINT_TO_POINTER (3), &bundle_tag };
- struct validate_sdp offer =
- { _check_bundle_only_media, &offer_bundle_only, &offer_non_reject };
- struct validate_sdp answer =
- { _check_bundle_only_media, &answer_bundle_only, &answer_non_reject };
+ VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (3), NULL);
+ VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
+ VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (1), &bundle_tag);
+ VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (3), &bundle_tag);
+ VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
+ &offer_non_reject);
+ VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
+ &answer_non_reject);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@@ -2196,19 +2196,23 @@ GST_START_TEST (test_duplicate_nego)
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
- struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
- struct validate_sdp answer =
- { on_sdp_media_direction, expected_answer, NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstHarness *h;
+ guint negotiation_flag = 0;
/* check that negotiating twice succeeds */
+ t->on_negotiation_needed = on_negotiation_needed_hit;
+ t->negotiation_data = &negotiation_flag;
+
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
- t->on_negotiation_needed = NULL;
test_validate_sdp (t, &offer, &answer);
+ fail_unless_equals_int (negotiation_flag, 1);
+
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
@@ -2222,9 +2226,8 @@ GST_START_TEST (test_dual_audio)
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv", };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
- struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
- struct validate_sdp answer =
- { on_sdp_media_direction, expected_answer, NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstHarness *h;
GstWebRTCRTPTransceiver *trans;
GArray *transceivers;
@@ -2258,6 +2261,431 @@ GST_START_TEST (test_dual_audio)
test_webrtc_free (t);
}
+GST_END_TEST;
+
+static void
+sdp_increasing_session_version (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ GstWebRTCSessionDescription *previous;
+ const GstSDPOrigin *our_origin, *previous_origin;
+ const gchar *prop;
+ guint64 our_v, previous_v;
+
+ prop =
+ TEST_SDP_IS_LOCAL (t, element,
+ desc) ? "current-local-description" : "current-remote-description";
+ g_object_get (element, prop, &previous, NULL);
+
+ our_origin = gst_sdp_message_get_origin (desc->sdp);
+ previous_origin = gst_sdp_message_get_origin (previous->sdp);
+
+ our_v = g_ascii_strtoull (our_origin->sess_version, NULL, 10);
+ previous_v = g_ascii_strtoull (previous_origin->sess_version, NULL, 10);
+
+ ck_assert_int_lt (previous_v, our_v);
+
+ gst_webrtc_session_description_free (previous);
+}
+
+static void
+sdp_equal_session_id (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ GstWebRTCSessionDescription *previous;
+ const GstSDPOrigin *our_origin, *previous_origin;
+ const gchar *prop;
+
+ prop =
+ TEST_SDP_IS_LOCAL (t, element,
+ desc) ? "current-local-description" : "current-remote-description";
+ g_object_get (element, prop, &previous, NULL);
+
+ our_origin = gst_sdp_message_get_origin (desc->sdp);
+ previous_origin = gst_sdp_message_get_origin (previous->sdp);
+
+ fail_unless_equals_string (previous_origin->sess_id, our_origin->sess_id);
+ gst_webrtc_session_description_free (previous);
+}
+
+static void
+sdp_media_equal_attribute (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, GstWebRTCSessionDescription * previous,
+ const gchar * attr)
+{
+ guint i, n;
+
+ n = MIN (gst_sdp_message_medias_len (previous->sdp),
+ gst_sdp_message_medias_len (desc->sdp));
+
+ for (i = 0; i < n; i++) {
+ const GstSDPMedia *our_media, *other_media;
+ const gchar *our_mid, *other_mid;
+
+ our_media = gst_sdp_message_get_media (desc->sdp, i);
+ other_media = gst_sdp_message_get_media (previous->sdp, i);
+
+ our_mid = gst_sdp_media_get_attribute_val (our_media, attr);
+ other_mid = gst_sdp_media_get_attribute_val (other_media, attr);
+
+ fail_unless_equals_string (our_mid, other_mid);
+ }
+}
+
+static void
+sdp_media_equal_mid (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ GstWebRTCSessionDescription *previous;
+ const gchar *prop;
+
+ prop =
+ TEST_SDP_IS_LOCAL (t, element,
+ desc) ? "current-local-description" : "current-remote-description";
+ g_object_get (element, prop, &previous, NULL);
+
+ sdp_media_equal_attribute (t, element, desc, previous, "mid");
+
+ gst_webrtc_session_description_free (previous);
+}
+
+static void
+sdp_media_equal_ice_params (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ GstWebRTCSessionDescription *previous;
+ const gchar *prop;
+
+ prop =
+ TEST_SDP_IS_LOCAL (t, element,
+ desc) ? "current-local-description" : "current-remote-description";
+ g_object_get (element, prop, &previous, NULL);
+
+ sdp_media_equal_attribute (t, element, desc, previous, "ice-ufrag");
+ sdp_media_equal_attribute (t, element, desc, previous, "ice-pwd");
+
+ gst_webrtc_session_description_free (previous);
+}
+
+static void
+sdp_media_equal_fingerprint (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ GstWebRTCSessionDescription *previous;
+ const gchar *prop;
+
+ prop =
+ TEST_SDP_IS_LOCAL (t, element,
+ desc) ? "current-local-description" : "current-remote-description";
+ g_object_get (element, prop, &previous, NULL);
+
+ sdp_media_equal_attribute (t, element, desc, previous, "fingerprint");
+
+ gst_webrtc_session_description_free (previous);
+}
+
+GST_START_TEST (test_renego_add_stream)
+{
+ struct test_webrtc *t = create_audio_video_test ();
+ const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
+ const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+ VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
+ VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
+ &renego_mid);
+ VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
+ VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
+ &renego_sess_id);
+ VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
+ &renego_sess_ver);
+ GstHarness *h;
+
+ /* negotiate an AV stream and then renegotiate an extra stream */
+ h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
+ add_fake_audio_src_harness (h, 96);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
+ add_fake_audio_src_harness (h, 98);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ offer.next = &renego_fingerprint;
+ answer.next = &renego_fingerprint;
+
+ /* renegotiate! */
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_renego_stream_add_data_channel)
+{
+ struct test_webrtc *t = create_audio_video_test ();
+ const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
+ const gchar *expected_answer[] = { "sendrecv", "recvonly" };
+
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+ VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
+ VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
+ &renego_mid);
+ VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
+ VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
+ &renego_sess_id);
+ VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
+ &renego_sess_ver);
+ GObject *channel;
+ GstHarness *h;
+
+ /* negotiate an AV stream and then renegotiate a data channel */
+ h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
+ add_fake_audio_src_harness (h, 96);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
+ &channel);
+
+ offer.next = &renego_fingerprint;
+ answer.next = &renego_fingerprint;
+
+ /* renegotiate! */
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ g_object_unref (channel);
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_renego_data_channel_add_stream)
+{
+ struct test_webrtc *t = test_webrtc_new ();
+ const gchar *expected_offer[] = { NULL, "sendrecv" };
+ const gchar *expected_answer[] = { NULL, "recvonly" };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+ VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
+ VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
+ &renego_mid);
+ VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
+ VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
+ &renego_sess_id);
+ VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
+ &renego_sess_ver);
+ GObject *channel;
+ GstHarness *h;
+
+ /* negotiate an AV stream and then renegotiate a data channel */
+ t->on_negotiation_needed = NULL;
+ t->on_ice_candidate = NULL;
+ t->on_pad_added = _pad_added_fakesink;
+
+ fail_if (gst_element_set_state (t->webrtc1,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+ fail_if (gst_element_set_state (t->webrtc2,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+ g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
+ &channel);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL);
+ add_fake_audio_src_harness (h, 97);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ offer.next = &renego_fingerprint;
+ answer.next = &renego_fingerprint;
+
+ /* renegotiate! */
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ g_object_unref (channel);
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_bundle_renego_add_stream)
+{
+ struct test_webrtc *t = create_audio_video_test ();
+ const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
+ const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
+ const gchar *bundle[] = { "audio0", "video1", "audio2", NULL };
+ const gchar *offer_bundle_only[] = { "video1", "audio2", NULL };
+ const gchar *answer_bundle_only[] = { NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+
+ VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
+ VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
+ &renego_mid);
+ VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
+ VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
+ &renego_sess_id);
+ VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
+ &renego_sess_ver);
+ VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &renego_fingerprint);
+ VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (1), &bundle_tag);
+ VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (3), &bundle_tag);
+ VAL_SDP_INIT (offer_bundle_only_sdp, _check_bundle_only_media,
+ &offer_bundle_only, &offer_non_reject);
+ VAL_SDP_INIT (answer_bundle_only_sdp, _check_bundle_only_media,
+ &answer_bundle_only, &answer_non_reject);
+ GstHarness *h;
+
+ /* We set a max-bundle policy on the offering webrtcbin,
+ * this means that all the offered medias should be part
+ * of the group:BUNDLE attribute, and they should be marked
+ * as bundle-only
+ */
+ gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
+ "max-bundle");
+ /* We also set a max-bundle policy on the answering webrtcbin,
+ * this means that all the offered medias should be part
+ * of the group:BUNDLE attribute, but need not be marked
+ * as bundle-only.
+ */
+ gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
+ "max-bundle");
+
+ /* negotiate an AV stream and then renegotiate an extra stream */
+ h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
+ add_fake_audio_src_harness (h, 96);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
+ add_fake_audio_src_harness (h, 98);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ offer.next = &offer_bundle_only_sdp;
+ answer.next = &answer_bundle_only_sdp;
+
+ /* renegotiate! */
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_bundle_max_compat_max_bundle_renego_add_stream)
+{
+ struct test_webrtc *t = create_audio_video_test ();
+ const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
+ const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
+ const gchar *bundle[] = { "audio0", "video1", "audio2", NULL };
+ const gchar *bundle_only[] = { NULL };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+
+ VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
+ VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
+ &renego_mid);
+ VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
+ VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
+ &renego_sess_id);
+ VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
+ &renego_sess_ver);
+ VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &renego_fingerprint);
+ VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
+ GUINT_TO_POINTER (3), &bundle_tag);
+ VAL_SDP_INIT (bundle_sdp, _check_bundle_only_media, &bundle_only,
+ &count_non_reject);
+ GstHarness *h;
+
+ /* We set a max-compat policy on the offering webrtcbin,
+ * this means that all the offered medias should be part
+ * of the group:BUNDLE attribute, and they should *not* be marked
+ * as bundle-only
+ */
+ gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
+ "max-compat");
+ /* We set a max-bundle policy on the answering webrtcbin,
+ * this means that all the offered medias should be part
+ * of the group:BUNDLE attribute, but need not be marked
+ * as bundle-only.
+ */
+ gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
+ "max-bundle");
+
+ /* negotiate an AV stream and then renegotiate an extra stream */
+ h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
+ add_fake_audio_src_harness (h, 96);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
+ add_fake_audio_src_harness (h, 98);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ offer.next = &bundle_sdp;
+ answer.next = &bundle_sdp;
+
+ /* renegotiate! */
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_renego_transceiver_set_direction)
+{
+ struct test_webrtc *t = create_audio_test ();
+ const gchar *expected_offer[] = { "sendrecv" };
+ const gchar *expected_answer[] = { "sendrecv" };
+ VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
+ VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
+ GstWebRTCRTPTransceiver *transceiver;
+ GstHarness *h;
+ GstPad *pad;
+
+ /* negotiate an AV stream and then change the transceiver direction */
+ h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
+ add_fake_audio_src_harness (h, 96);
+ t->harnesses = g_list_prepend (t->harnesses, h);
+
+ test_validate_sdp (t, &offer, &answer);
+
+ /* renegotiate an inactive transceiver! */
+ pad = gst_element_get_static_pad (t->webrtc1, "sink_0");
+ g_object_get (pad, "transceiver", &transceiver, NULL);
+ fail_unless (transceiver != NULL);
+ gst_webrtc_rtp_transceiver_set_direction (transceiver,
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE);
+ expected_offer[0] = "inactive";
+ expected_answer[0] = "inactive";
+
+ /* TODO: also validate EOS events from the inactive change */
+
+ test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
+ test_validate_sdp (t, &offer, &answer);
+
+ gst_object_unref (pad);
+ gst_object_unref (transceiver);
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
static Suite *
webrtcbin_suite (void)
{
@@ -2294,6 +2722,10 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_bundle_audio_video_max_compat_max_bundle);
tcase_add_test (tc, test_dual_audio);
tcase_add_test (tc, test_duplicate_nego);
+ tcase_add_test (tc, test_renego_add_stream);
+ tcase_add_test (tc, test_bundle_renego_add_stream);
+ tcase_add_test (tc, test_bundle_max_compat_max_bundle_renego_add_stream);
+ tcase_add_test (tc, test_renego_transceiver_set_direction);
if (sctpenc && sctpdec) {
tcase_add_test (tc, test_data_channel_create);
tcase_add_test (tc, test_data_channel_remote_notify);
@@ -2304,6 +2736,8 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_data_channel_max_message_size);
tcase_add_test (tc, test_data_channel_pre_negotiated);
tcase_add_test (tc, test_bundle_audio_video_data);
+ tcase_add_test (tc, test_renego_stream_add_data_channel);
+ tcase_add_test (tc, test_renego_data_channel_add_stream);
} else {
GST_WARNING ("Some required elements were not found. "
"All datachannel tests are disabled. sctpenc %p, sctpdec %p", sctpenc,
diff --git a/tests/examples/webrtc/Makefile.am b/tests/examples/webrtc/Makefile.am
index 6323263bf..7d8aec641 100644
--- a/tests/examples/webrtc/Makefile.am
+++ b/tests/examples/webrtc/Makefile.am
@@ -1,5 +1,5 @@
-noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver
+noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver webrtcrenego
webrtc_SOURCES = webrtc.c
webrtc_CFLAGS=\
@@ -52,3 +52,16 @@ webrtctransceiver_LDADD=\
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
+
+webrtcrenego_SOURCES = webrtcrenego.c
+webrtcrenego_CFLAGS=\
+ -I$(top_srcdir)/gst-libs \
+ -I$(top_builddir)/gst-libs \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(GST_SDP_CFLAGS)
+webrtcrenego_LDADD=\
+ $(GST_PLUGINS_BASE_LIBS) \
+ $(GST_LIBS) \
+ $(GST_SDP_LIBS) \
+ $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
diff --git a/tests/examples/webrtc/meson.build b/tests/examples/webrtc/meson.build
index 716bcf399..3913a4674 100644
--- a/tests/examples/webrtc/meson.build
+++ b/tests/examples/webrtc/meson.build
@@ -1,4 +1,4 @@
-examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver']
+examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver', 'webrtcrenego']
foreach example : examples
exe_name = example
diff --git a/tests/examples/webrtc/webrtcrenego.c b/tests/examples/webrtc/webrtcrenego.c
new file mode 100644
index 000000000..1305ff8ed
--- /dev/null
+++ b/tests/examples/webrtc/webrtcrenego.c
@@ -0,0 +1,289 @@
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+
+#include <string.h>
+
+static GMainLoop *loop;
+static GstElement *pipe1, *webrtc1, *webrtc2, *extra_src;
+static GstBus *bus1;
+
+#define SEND_SRC(pattern) "videotestsrc is-live=true pattern=" pattern " ! timeoverlay ! queue ! vp8enc ! rtpvp8pay ! queue ! " \
+ "capsfilter caps=application/x-rtp,media=video,payload=96,encoding-name=VP8"
+
+static void
+_element_message (GstElement * parent, GstMessage * msg)
+{
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS:{
+ GstElement *receive, *webrtc;
+ GstPad *pad, *peer;
+
+ g_print ("Got element EOS message from %s parent %s\n",
+ GST_OBJECT_NAME (msg->src), GST_OBJECT_NAME (parent));
+
+ receive = GST_ELEMENT (msg->src);
+
+ pad = gst_element_get_static_pad (receive, "sink");
+ peer = gst_pad_get_peer (pad);
+
+ webrtc = GST_ELEMENT (gst_pad_get_parent (peer));
+ gst_bin_remove (GST_BIN (pipe1), receive);
+
+ gst_pad_unlink (peer, pad);
+ gst_element_release_request_pad (webrtc, peer);
+
+ gst_object_unref (pad);
+ gst_object_unref (peer);
+
+ gst_element_set_state (receive, GST_STATE_NULL);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static gboolean
+_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe)
+{
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_STATE_CHANGED:
+ if (GST_ELEMENT (msg->src) == pipe) {
+ GstState old, new, pending;
+
+ gst_message_parse_state_changed (msg, &old, &new, &pending);
+
+ {
+ gchar *dump_name = g_strconcat ("state_changed-",
+ gst_element_state_get_name (old), "_",
+ gst_element_state_get_name (new), NULL);
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src),
+ GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
+ g_free (dump_name);
+ }
+ }
+ break;
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+ gchar *dbg_info = NULL;
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
+ GST_DEBUG_GRAPH_SHOW_ALL, "error");
+
+ gst_message_parse_error (msg, &err, &dbg_info);
+ g_printerr ("ERROR from element %s: %s\n",
+ GST_OBJECT_NAME (msg->src), err->message);
+ g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+ g_error_free (err);
+ g_free (dbg_info);
+ g_main_loop_quit (loop);
+ break;
+ }
+ case GST_MESSAGE_EOS:{
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
+ GST_DEBUG_GRAPH_SHOW_ALL, "eos");
+ g_print ("EOS received\n");
+ g_main_loop_quit (loop);
+ break;
+ }
+ case GST_MESSAGE_ELEMENT:{
+ const GstStructure *s = gst_message_get_structure (msg);
+ if (g_strcmp0 (gst_structure_get_name (s), "GstBinForwarded") == 0) {
+ GstMessage *sub_msg;
+
+ gst_structure_get (s, "message", GST_TYPE_MESSAGE, &sub_msg, NULL);
+ _element_message (GST_ELEMENT (msg->src), sub_msg);
+ gst_message_unref (sub_msg);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
+{
+ GstElement *out;
+ GstPad *sink;
+
+ if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
+ return;
+
+ out = gst_parse_bin_from_description ("queue ! rtpvp8depay ! vp8dec ! "
+ "videoconvert ! queue ! xvimagesink", TRUE, NULL);
+ gst_bin_add (GST_BIN (pipe), out);
+ gst_element_sync_state_with_parent (out);
+
+ sink = out->sinkpads->data;
+
+ gst_pad_link (new_pad, sink);
+}
+
+static void
+_on_answer_received (GstPromise * promise, gpointer user_data)
+{
+ GstWebRTCSessionDescription *answer = NULL;
+ const GstStructure *reply;
+ gchar *desc;
+
+ g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
+ reply = gst_promise_get_reply (promise);
+ gst_structure_get (reply, "answer",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
+ gst_promise_unref (promise);
+ desc = gst_sdp_message_as_text (answer->sdp);
+ g_print ("Created answer:\n%s\n", desc);
+ g_free (desc);
+
+ /* this is one way to tell webrtcbin that we don't want to be notified when
+ * this task is complete: set a NULL promise */
+ g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
+ /* this is another way to tell webrtcbin that we don't want to be notified
+ * when this task is complete: interrupt the promise */
+ promise = gst_promise_new ();
+ g_signal_emit_by_name (webrtc2, "set-local-description", answer, promise);
+ gst_promise_interrupt (promise);
+ gst_promise_unref (promise);
+
+ gst_webrtc_session_description_free (answer);
+}
+
+static void
+_on_offer_received (GstPromise * promise, gpointer user_data)
+{
+ GstWebRTCSessionDescription *offer = NULL;
+ const GstStructure *reply;
+ gchar *desc;
+
+ g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
+ reply = gst_promise_get_reply (promise);
+ gst_structure_get (reply, "offer",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
+ gst_promise_unref (promise);
+ desc = gst_sdp_message_as_text (offer->sdp);
+ g_print ("Created offer:\n%s\n", desc);
+ g_free (desc);
+
+ g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL);
+ g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL);
+
+ promise = gst_promise_new_with_change_func (_on_answer_received, user_data,
+ NULL);
+ g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise);
+
+ gst_webrtc_session_description_free (offer);
+}
+
+static void
+_on_negotiation_needed (GstElement * element, gpointer user_data)
+{
+ GstPromise *promise;
+
+ promise = gst_promise_new_with_change_func (_on_offer_received, user_data,
+ NULL);
+ g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
+}
+
+static void
+_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
+ GstElement * other)
+{
+ g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
+}
+
+static gboolean
+stream_change (gpointer data)
+{
+ if (!extra_src) {
+ g_print ("Adding extra stream\n");
+ extra_src =
+ gst_parse_bin_from_description (SEND_SRC ("circular"), TRUE, NULL);
+
+ gst_element_set_locked_state (extra_src, TRUE);
+ gst_bin_add (GST_BIN (pipe1), extra_src);
+ gst_element_link (extra_src, webrtc1);
+ gst_element_set_locked_state (extra_src, FALSE);
+ gst_element_sync_state_with_parent (extra_src);
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
+ GST_DEBUG_GRAPH_SHOW_ALL, "add");
+ } else {
+ GstPad *pad, *peer;
+ GstWebRTCRTPTransceiver *transceiver;
+
+ g_print ("Removing extra stream\n");
+ pad = gst_element_get_static_pad (extra_src, "src");
+ peer = gst_pad_get_peer (pad);
+ gst_element_send_event (extra_src, gst_event_new_eos ());
+
+ g_object_get (peer, "transceiver", &transceiver, NULL);
+ gst_webrtc_rtp_transceiver_set_direction (transceiver,
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE);
+
+ gst_element_set_locked_state (extra_src, TRUE);
+ gst_element_set_state (extra_src, GST_STATE_NULL);
+ gst_pad_unlink (pad, peer);
+ gst_element_release_request_pad (webrtc1, peer);
+
+ gst_object_unref (peer);
+ gst_object_unref (pad);
+
+ gst_bin_remove (GST_BIN (pipe1), extra_src);
+ extra_src = NULL;
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
+ GST_DEBUG_GRAPH_SHOW_ALL, "remove");
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ pipe1 = gst_parse_launch (SEND_SRC ("smpte")
+ " ! webrtcbin name=smpte bundle-policy=max-bundle " SEND_SRC ("ball")
+ " ! webrtcbin name=ball bundle-policy=max-bundle", NULL);
+ g_object_set (pipe1, "message-forward", TRUE, NULL);
+ bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1));
+ gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1);
+
+ webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte");
+ g_signal_connect (webrtc1, "on-negotiation-needed",
+ G_CALLBACK (_on_negotiation_needed), NULL);
+ g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added),
+ pipe1);
+ webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball");
+ g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added),
+ pipe1);
+ g_signal_connect (webrtc1, "on-ice-candidate",
+ G_CALLBACK (_on_ice_candidate), webrtc2);
+ g_signal_connect (webrtc2, "on-ice-candidate",
+ G_CALLBACK (_on_ice_candidate), webrtc1);
+
+ g_print ("Starting pipeline\n");
+ gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING);
+
+ g_timeout_add_seconds (5, stream_change, NULL);
+
+ g_main_loop_run (loop);
+
+ gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL);
+ g_print ("Pipeline stopped\n");
+
+ gst_object_unref (webrtc1);
+ gst_object_unref (webrtc2);
+ gst_bus_remove_watch (bus1);
+ gst_object_unref (bus1);
+ gst_object_unref (pipe1);
+
+ gst_deinit ();
+
+ return 0;
+}