From 177aa22bcd9db2059e714221221a8e56c9533990 Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 28 Nov 2018 17:23:31 +1100 Subject: 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. --- .gitignore | 1 + ext/webrtc/gstwebrtcbin.c | 909 ++++++++++++++++++++++++----------- ext/webrtc/gstwebrtcbin.h | 2 + ext/webrtc/transportstream.c | 38 ++ ext/webrtc/transportstream.h | 7 + ext/webrtc/utils.c | 60 ++- ext/webrtc/utils.h | 4 + ext/webrtc/webrtcsdp.c | 25 +- ext/webrtc/webrtcsdp.h | 9 + ext/webrtc/webrtctransceiver.c | 11 +- ext/webrtc/webrtctransceiver.h | 2 + gst-libs/gst/webrtc/rtptransceiver.c | 2 +- tests/check/elements/webrtcbin.c | 632 ++++++++++++++++++++---- tests/examples/webrtc/Makefile.am | 15 +- tests/examples/webrtc/meson.build | 2 +- tests/examples/webrtc/webrtcrenego.c | 289 +++++++++++ 16 files changed, 1625 insertions(+), 383 deletions(-) create mode 100644 tests/examples/webrtc/webrtcrenego.c 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 @@ -3036,6 +3344,38 @@ _filter_sdp_fields (GQuark field_id, const GValue * value, return TRUE; } +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 + #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 +#include +#include + +#include + +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; +} -- cgit v1.2.1