From 44ca3de04256422636638bbfd48953dbba087673 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 23 Jan 2020 20:26:18 +0100 Subject: [PATCH 01/69] kmsicecandidate: Don't set VALID when mDNS cannot resolve When mDNS resolution is enabled in the system, reception of Chrome's mDNS ICE candidates works fine. However, in environments where mDNS is not available: * Docker images * Ubuntu systems where the package libnss-mdns is not installed * Systems where /etc/nsswitch.conf doesn't contain mdns4_minimal in the list of host resolution engines the resolution of Chrome's mDNS candidate will fail, as expected, but the candidate itself will be considered valid. Considering it valid means that it is processed, stored in the queue of candidates to be added to libnice, and eventually added. libnice rejects adding an mDNS candidate because it cannot parse it, so it returns an error that ends up unwrapping as a Kurento API Error message, and this will make most applications abort because errors are not expected, even though invalid mDNS candidates is a perfectly valid situation. The solution is to simply reject mDNS candidates in the case their address cannot be resolved. Fixes Kurento/bugtracker#432 --- .../webrtcendpoint/kmsicecandidate.c | 30 +++++++++---------- .../webrtcendpoint/kmsiceniceagent.c | 3 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c index d0587fa96..f5549e3e1 100644 --- a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c +++ b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c @@ -193,30 +193,28 @@ kms_ice_candidate_update_values (KmsIceCandidate * self) // The IP is actually an mDNS address, try to resolve it. // https://datatracker.ietf.org/doc/draft-ietf-rtcweb-mdns-ice-candidates/ - GResolver *resolver = g_resolver_get_default (); GError *err = NULL; + GResolver *resolver = g_resolver_get_default (); GList *addresses = g_resolver_lookup_by_name (resolver, self->priv->ip, NULL, &err); + g_object_unref (resolver); if (err) { GST_DEBUG_OBJECT (self, "Ignore foreign mDNS candidate: %s", - (err->message ? err->message : "(None)")); - g_error_free (err); - err = NULL; - } else { - // Set the resolved address - GInetAddress *address = (GInetAddress *) g_list_nth_data (addresses, 0); - gchar *resolved_ip = g_inet_address_to_string (address); - GST_INFO_OBJECT (self, "mDNS address (%s) resolved: %s", self->priv->ip, - resolved_ip); - kms_ice_candidate_set_address (self, resolved_ip); - g_free (resolved_ip); + (err->message ? err->message : "")); + g_clear_error (&err); + goto end; } - if (addresses) { - g_resolver_free_addresses (addresses); - } - g_object_unref (resolver); + // Set the resolved address + GInetAddress *address = (GInetAddress *) g_list_nth_data (addresses, 0); + gchar *resolved_ip = g_inet_address_to_string (address); + GST_INFO_OBJECT (self, "mDNS address (%s) resolved: %s", self->priv->ip, + resolved_ip); + kms_ice_candidate_set_address (self, resolved_ip); + g_free (resolved_ip); + + g_resolver_free_addresses (addresses); } ret = TRUE; diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index 3b1b10d5b..4490e2570 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -460,7 +460,8 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, g_free (candidate_str); if (nice_cand == NULL) { - GST_WARNING_OBJECT (self, "[AddIceCandidate] libnice error, remote: '%s'", + GST_WARNING_OBJECT (self, + "[AddIceCandidate] libnice parse error, remote: '%s'", kms_ice_candidate_get_candidate (candidate)); return FALSE; } From 5ef161f5132538827c85160d571283b3e3d48638 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 23 Jan 2020 20:29:58 +0100 Subject: [PATCH 02/69] kmsrtpendpoint: Correct parsing of "a=rtcp" attributes The code was first removing the attribute, then adding a new one. This means that the loop logic would step over the next attribute that might come next, because the index variable gets incremented, while the array of attributes has been modified in place. This changes the attribute struct in place, so the loop logic is not altered by the modification. --- src/gst-plugins/rtpendpoint/kmsrtpendpoint.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c index 0e3de8213..200af3541 100644 --- a/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c +++ b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c @@ -757,15 +757,13 @@ kms_rtp_endpoint_configure_media (KmsBaseSdpEndpoint * base_sdp_endpoint, attr_len = gst_sdp_media_attributes_len (media); for (a = 0; a < attr_len; a++) { - const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, a); + GstSDPAttribute *attr = + (GstSDPAttribute *) gst_sdp_media_get_attribute (media, a); if (g_strcmp0 (attr->key, "rtcp") == 0) { - gst_sdp_media_remove_attribute (media, a); - const guint rtcp_port = kms_rtp_base_connection_get_rtcp_port (conn); - gchar *str = g_strdup_printf ("%" G_GUINT32_FORMAT, rtcp_port); - gst_sdp_media_add_attribute (media, "rtcp", str); - g_free (str); + g_free (attr->value); + attr->value = g_strdup_printf ("%" G_GUINT32_FORMAT, rtcp_port); } } From 3b444e020d7848355069423b1676dac8952325cb Mon Sep 17 00:00:00 2001 From: prlanzarin Date: Tue, 10 Mar 2020 17:50:13 +0000 Subject: [PATCH 03/69] Fix stunServerPort default value usage The if-else blocks were wrongly disposed, so when the stunServerPort config wasnt set, but the IP was, a STUN server would not be setup in the NiceAgent interface Also some identation changes according to the git hook suggestions around the code I changed --- .../objects/WebRtcEndpointImpl.cpp | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index 1424c23af..4ccf0b8a3 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -533,23 +533,25 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, } uint stunPort = 0; + if (!getConfigValue (&stunPort, "stunServerPort", - DEFAULT_STUN_PORT)) { + DEFAULT_STUN_PORT) ) { GST_INFO ("STUN port not found in config;" " using default value: %d", DEFAULT_STUN_PORT); + } + + std::string stunAddress; + + if (!getConfigValue (&stunAddress, + "stunServerAddress") ) { + GST_INFO ("STUN server not found in config;" + " remember that NAT traversal requires STUN or TURN"); } else { - std::string stunAddress; - if (!getConfigValue (&stunAddress, - "stunServerAddress")) { - GST_INFO ("STUN server not found in config;" - " remember that NAT traversal requires STUN or TURN"); - } else { - GST_INFO ("Using STUN reflexive server: %s:%d", stunAddress.c_str(), - stunPort); + GST_INFO ("Using STUN reflexive server: %s:%d", stunAddress.c_str(), + stunPort); - g_object_set (G_OBJECT (element), "stun-server-port", stunPort, NULL); - g_object_set (G_OBJECT (element), "stun-server", stunAddress.c_str(), NULL); - } + g_object_set (G_OBJECT (element), "stun-server-port", stunPort, NULL); + g_object_set (G_OBJECT (element), "stun-server", stunAddress.c_str(), NULL); } std::string turnURL; From 3e695df82ae9ffb3914e202aa969fb3bf504ec94 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 17 Mar 2020 17:45:40 +0100 Subject: [PATCH 04/69] webrtcendpoint: fix unsigned printf modifiers (%u) --- .../webrtcendpoint/kmsiceniceagent.c | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index 4490e2570..af2c97b3b 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -80,14 +80,14 @@ kms_ice_nice_agent_new_candidate_full (NiceAgent * agent, const guint stream_id = candidate->stream_id; const guint component_id = candidate->component_id; - gchar *stream_id_str = g_strdup_printf ("%d", stream_id); + gchar *stream_id_str = g_strdup_printf ("%u", stream_id); KmsIceCandidate *kms_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, stream_id_str); g_free (stream_id_str); GST_DEBUG_OBJECT (self, - "[IceCandidateFound] local: '%s', stream_id: %d, component_id: %d", + "[IceCandidateFound] local: '%s', stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (kms_candidate), stream_id, component_id); @@ -102,7 +102,7 @@ kms_ice_nice_agent_new_remote_candidate_full (NiceAgent * agent, KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); const guint stream_id = candidate->stream_id; - gchar *stream_id_str = g_strdup_printf ("%d", stream_id); + gchar *stream_id_str = g_strdup_printf ("%u", stream_id); KmsIceCandidate *kms_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, @@ -126,10 +126,10 @@ kms_ice_nice_agent_gathering_done (NiceAgent * agent, guint stream_id, char buff[33]; char *ret; - GST_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %d", stream_id); + GST_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %u", stream_id); //convert id to char* - g_snprintf (buff, 32, "%d", stream_id); + g_snprintf (buff, 32, "%u", stream_id); ret = g_strdup (buff); @@ -169,13 +169,13 @@ kms_ice_nice_agent_component_state_change (NiceAgent * agent, guint stream_id, char *ret; //convert id to char* - g_snprintf (buff, 32, "%d", stream_id); + g_snprintf (buff, 32, "%u", stream_id); ret = g_strdup (buff); state_ = kms_ice_nice_agent_nice_to_ice_state (state); GST_DEBUG_OBJECT (self, - "[IceComponentStateChanged] state: %s, stream_id: %d, component_id: %d", + "[IceComponentStateChanged] state: %s, stream_id: %u, component_id: %u", nice_component_state_to_string (state), stream_id, component_id); g_signal_emit_by_name (parent, "on-ice-component-state-changed", ret, @@ -194,7 +194,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, gchar *stream_id_str; KmsIceCandidate *local_candidate = NULL, *remote_candidate = NULL; - stream_id_str = g_strdup_printf ("%d", stream_id); + stream_id_str = g_strdup_printf ("%u", stream_id); local_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, lcandidate, stream_id_str); @@ -202,7 +202,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, gchar *cand_str = kms_ice_nice_agent_get_candidate_sdp_string (agent, lcandidate); GST_WARNING_OBJECT (self, - "Invalid local candidate: '%s', stream_id: %d, component_id: %d", + "Invalid local candidate: '%s', stream_id: %u, component_id: %u", cand_str, stream_id, component_id); g_free (cand_str); goto end; @@ -214,7 +214,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, gchar *cand_str = kms_ice_nice_agent_get_candidate_sdp_string (agent, rcandidate); GST_WARNING_OBJECT (self, - "Invalid remote candidate: '%s', stream_id: %d, component_id: %d", + "Invalid remote candidate: '%s', stream_id: %u, component_id: %u", cand_str, stream_id, component_id); g_free (cand_str); goto end; @@ -222,7 +222,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, GST_INFO_OBJECT (self, "[NewCandidatePairSelected] local: '%s', remote: '%s'" - ", stream_id: %d, component_id: %d", + ", stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (local_candidate), kms_ice_candidate_get_candidate (remote_candidate), stream_id, component_id); @@ -312,10 +312,10 @@ kms_ice_nice_agent_add_stream (KmsIceBaseAgent * self, const char *stream_id, return NULL; } - GST_DEBUG_OBJECT (self, "Added data stream, ID: %d, stream_id: %s", + GST_DEBUG_OBJECT (self, "Added data stream, ID: %u, stream_id: %s", id, stream_id); - GST_DEBUG_OBJECT (self, "Set port range: [%d, %d]", min_port, max_port); + GST_DEBUG_OBJECT (self, "Set port range: [%u, %u]", min_port, max_port); for (i = 1; i <= KMS_NICE_N_COMPONENTS; i++) { nice_agent_set_port_range (nice_agent->priv->agent, id, i, min_port, max_port); @@ -328,7 +328,7 @@ kms_ice_nice_agent_add_stream (KmsIceBaseAgent * self, const char *stream_id, // nice_agent->priv->context, kms_ice_nice_agent_recv_cb, self); // } - return g_strdup_printf ("%d", id); + return g_strdup_printf ("%u", id); } static void @@ -337,7 +337,7 @@ kms_ice_nice_agent_remove_stream (KmsIceBaseAgent * self, const char *stream_id) KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Remove data stream, stream_id: %d", id); + GST_DEBUG_OBJECT (self, "Remove data stream, stream_id: %u", id); nice_agent_remove_stream (nice_agent->priv->agent, id); } @@ -349,7 +349,7 @@ kms_ice_nice_agent_set_remote_credentials (KmsIceBaseAgent * self, KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Set remote credentials, stream_id: %d", id); + GST_DEBUG_OBJECT (self, "Set remote credentials, stream_id: %u", id); return nice_agent_set_remote_credentials (nice_agent->priv->agent, id, ufrag, pwd); @@ -362,7 +362,7 @@ kms_ice_nice_agent_get_local_credentials (KmsIceBaseAgent * self, KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Get local credentials, stream_id: %d", id); + GST_DEBUG_OBJECT (self, "Get local credentials, stream_id: %u", id); nice_agent_get_local_credentials (nice_agent->priv->agent, id, ufrag, pwd); } @@ -408,7 +408,7 @@ kms_ice_nice_agent_add_relay_server (KmsIceBaseAgent * self, NiceRelayType type = from_turn_protocol_to_nice_relay (server_info.type); GST_DEBUG_OBJECT (self, "Add relay server," - " IP: %s, port: %d, stream_id: %d", + " IP: %s, port: %u, stream_id: %u", server_info.server_ip, server_info.server_port, id); nice_agent_set_relay_info (nice_agent->priv->agent, @@ -435,7 +435,7 @@ kms_ice_nice_agent_start_gathering_candidates (KmsIceBaseAgent * self, gboolean ok = nice_agent_gather_candidates (nice_agent->priv->agent, id); if (ok) { - GST_DEBUG_OBJECT (self, "[IceGatheringStarted] stream_id: %d", id); + GST_DEBUG_OBJECT (self, "[IceGatheringStarted] stream_id: %s", stream_id); } return ok; @@ -461,7 +461,7 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, if (nice_cand == NULL) { GST_WARNING_OBJECT (self, - "[AddIceCandidate] libnice parse error, remote: '%s'", + "[AddIceCandidate] Error in libnice parsing, remote: '%s'", kms_ice_candidate_get_candidate (candidate)); return FALSE; } @@ -494,7 +494,7 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, candidates = g_slist_append (NULL, nice_cand); GST_DEBUG_OBJECT (self, - "[AddIceCandidate] remote: '%s', stream_id: %d, component_id: %d", + "[AddIceCandidate] remote: '%s', stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (candidate), nice_cand->stream_id, nice_cand->component_id); From d58731dd53e38b74d553d30d66627d984ca6e9c8 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 17 Mar 2020 17:50:27 +0100 Subject: [PATCH 05/69] webrtcendpoint: unify log messages Use the names of the Client API events to group different debug messages with the same prefix. This way, it is easy for a user to identify which log messages correspond to the "action" that is related to the given event. There are 3 levels at which the ICE messages can occur, from internal to external: * libnice agent control (kmsiceniceagent) * WebRTC session handling (kmswebrtcsession) * WebRtcEndpoint implementation (kmswebrtcendpoint) The most internal ones are set to DEBUG level, while the endpoint implementation is the one providing information to the user by default, and is set to work on the INFO level. With this, the default log levels for a Kurento installation contain all details about the ICE candidate gathering, and all messages come from the same source (kmswebrtcendpoint). The only exception is from Peer-Reflexive ICE candidates, which are detected and processed only at the most internal layer (kmsiceniceagent), so these are logged at the INFO level. --- .../webrtcendpoint/kmsiceniceagent.c | 10 ++- .../webrtcendpoint/kmswebrtcendpoint.c | 39 +++++++---- .../webrtcendpoint/kmswebrtcsession.c | 67 +++++++++---------- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index af2c97b3b..1abdaca50 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -108,8 +108,8 @@ kms_ice_nice_agent_new_remote_candidate_full (NiceAgent * agent, kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, stream_id_str); - GST_DEBUG_OBJECT (self, - "[AddIceCandidate] found peer-reflexive remote: '%s'", + GST_INFO_OBJECT (self, + "[AddIceCandidate] Found peer-reflexive remote: '%s'", kms_ice_candidate_get_candidate (kms_candidate)); kms_ice_nice_agent_add_ice_candidate (parent, kms_candidate, stream_id_str); @@ -220,7 +220,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, goto end; } - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "[NewCandidatePairSelected] local: '%s', remote: '%s'" ", stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (local_candidate), @@ -501,9 +501,7 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, if (nice_agent_set_remote_candidates (nice_agent->priv->agent, nice_cand->stream_id, nice_cand->component_id, candidates) < 0) { GST_WARNING_OBJECT (self, - "[AddIceCandidate] libnice error, remote: '%s', stream_id: %d, component_id: %d", - kms_ice_candidate_get_candidate (candidate), - nice_cand->stream_id, nice_cand->component_id); + "[AddIceCandidate] Error in libnice, remote candidate NOT added"); ret = FALSE; } else { ret = TRUE; diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index 1884d4087..33f5b74f2 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -109,6 +109,12 @@ on_ice_candidate (KmsWebrtcSession * sess, KmsIceCandidate * candidate, { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); + GST_INFO_OBJECT (self, + "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d", + kms_ice_candidate_get_candidate (candidate), + kms_ice_candidate_get_stream_id (candidate), + kms_ice_candidate_get_component (candidate)); + g_signal_emit (G_OBJECT (self), kms_webrtc_endpoint_signals[SIGNAL_ON_ICE_CANDIDATE], 0, sdp_sess->id_str, candidate); @@ -119,6 +125,8 @@ on_ice_gathering_done (KmsWebrtcSession * sess, KmsWebrtcEndpoint * self) { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); + GST_INFO_OBJECT (self, "[IceGatheringDone] session: '%s'", sdp_sess->id_str); + g_signal_emit (G_OBJECT (self), kms_webrtc_endpoint_signals[SIGNAL_ON_ICE_GATHERING_DONE], 0, sdp_sess->id_str); @@ -130,6 +138,10 @@ on_ice_component_state_change (KmsWebrtcSession * sess, const gchar * stream_id, { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); + GST_DEBUG_OBJECT (self, + "[IceComponentStateChanged] state: %s, stream_id: %s, component_id: %u", + kms_ice_base_agent_state_to_string (state), stream_id, component_id); + g_signal_emit (G_OBJECT (self), kms_webrtc_endpoint_signals[SIGNAL_ON_ICE_COMPONENT_STATE_CHANGED], 0, sdp_sess->id_str, stream_id, component_id, state); @@ -277,8 +289,8 @@ new_selected_pair_full (KmsWebrtcSession * sess, KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); GST_INFO_OBJECT (self, - "New candidate pair selected, local: '%s', remote: '%s'" - ", stream_id: '%s', component_id: %d", + "[NewCandidatePairSelected] local: '%s', remote: '%s'" + ", stream_id: %s, component_id: %u", kms_ice_candidate_get_candidate (lcandidate), kms_ice_candidate_get_candidate (rcandidate), stream_id, component_id); @@ -424,14 +436,14 @@ kms_webrtc_endpoint_gather_candidates (KmsWebrtcEndpoint * self, KmsWebrtcSession *webrtc_sess; gboolean ret = TRUE; - GST_INFO_OBJECT (self, "Gather candidates for session '%s'", sess_id); - sess = kms_base_sdp_endpoint_get_session (base_sdp_ep, sess_id); if (sess == NULL) { - GST_ERROR_OBJECT (self, "There is not session '%s'", sess_id); + GST_ERROR_OBJECT (self, "[IceGatheringStarted] No session: '%s'", sess_id); return FALSE; } + GST_INFO_OBJECT (self, "[IceGatheringStarted] session: '%s'", sess_id); + webrtc_sess = KMS_WEBRTC_SESSION (sess); g_signal_emit_by_name (webrtc_sess, "gather-candidates", &ret); @@ -447,15 +459,18 @@ kms_webrtc_endpoint_add_ice_candidate (KmsWebrtcEndpoint * self, KmsWebrtcSession *webrtc_sess; gboolean ret; - GST_INFO_OBJECT (self, "Add remote candidate '%s' for session '%s'", - kms_ice_candidate_get_candidate (candidate), sess_id); - sess = kms_base_sdp_endpoint_get_session (base_sdp_ep, sess_id); if (sess == NULL) { - GST_ERROR_OBJECT (self, "There is not session '%s'", sess_id); + GST_ERROR_OBJECT (self, "[AddIceCandidate] No session: '%s'", sess_id); return FALSE; } + // Remote candidates haven't been assigned a stream_id yet, so don't print it + GST_INFO_OBJECT (self, + "[AddIceCandidate] remote: '%s', component_id: %d", + kms_ice_candidate_get_candidate (candidate), + kms_ice_candidate_get_component (candidate)); + webrtc_sess = KMS_WEBRTC_SESSION (sess); g_signal_emit_by_name (webrtc_sess, "add-ice-candidate", candidate, &ret); @@ -587,7 +602,7 @@ kms_webrtc_endpoint_create_data_channel (KmsWebrtcEndpoint * self, sess = kms_base_sdp_endpoint_get_session (base_sdp_ep, sess_id); if (sess == NULL) { - GST_ERROR_OBJECT (self, "There is not session '%s'", sess_id); + GST_ERROR_OBJECT (self, "No session: '%s'", sess_id); return -1; } @@ -608,7 +623,7 @@ kms_webrtc_endpoint_destroy_data_channel (KmsWebrtcEndpoint * self, sess = kms_base_sdp_endpoint_get_session (base_sdp_ep, sess_id); if (sess == NULL) { - GST_ERROR_OBJECT (self, "There is not session '%s'", sess_id); + GST_ERROR_OBJECT (self, "No session: '%s'", sess_id); return; } @@ -627,7 +642,7 @@ kms_webrtc_endpoint_get_data_channel_supported (KmsWebrtcEndpoint * self, sess = kms_base_sdp_endpoint_get_session (base_sdp_ep, sess_id); if (sess == NULL) { - GST_ERROR_OBJECT (self, "There is not session '%s'", sess_id); + GST_ERROR_OBJECT (self, "No session: '%s'", sess_id); return FALSE; } diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 753045151..9f8b37d2d 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -369,8 +369,7 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (!self->gather_started) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, - "Adding remote candidate to libnice agent:" - " ICE Gathering not started yet"); + "[AddIceCandidate] ICE Gathering not started yet"); if (allow_error) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "... (Will add later)"); @@ -384,8 +383,7 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (sdp_sess->local_sdp == NULL) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, - "Adding remote candidate to libnice agent:" - " Local SDP not generated yet"); + "[AddIceCandidate] Local SDP not generated yet"); if (allow_error) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "... (Will add later)"); @@ -401,8 +399,7 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (index >= gst_sdp_message_medias_len (sdp_sess->local_sdp)) { GST_ERROR_OBJECT (self, - "Adding remote candidate to libnice agent:" - " Invalid media index: %u", index); + "[AddIceCandidate] Invalid media index: %u", index); return FALSE; } @@ -410,15 +407,13 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (media == NULL) { GST_ERROR_OBJECT (self, - "Adding remote candidate to libnice agent:" - " No media with index: %u", index); + "[AddIceCandidate] No media with index: %u", index); return FALSE; } if (gst_sdp_media_get_port (media) == 0) { GST_DEBUG_OBJECT (self, - "Adding remote candidate to libnice agent:" - " Unwanted media (port = 0): %s, index: %u", + "[AddIceCandidate] Unwanted media (port = 0): %s, index: %u", gst_sdp_media_get_media (media), index); return TRUE; } @@ -428,8 +423,7 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (handler == NULL) { GST_ERROR_OBJECT (self, - "Adding remote candidate to libnice agent:" - " No handler for media: %s, index: %u", + "[AddIceCandidate] No handler for media: %s, index: %u", gst_sdp_media_get_media (media), index); return FALSE; } @@ -439,21 +433,19 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (stream_id == NULL) { GST_ERROR_OBJECT (self, - "Adding remote candidate to libnice agent:" - " No stream_id, index: %u", index); + "[AddIceCandidate] No stream_id, index: %u", index); return FALSE; } if (!kms_ice_base_agent_add_ice_candidate (self->agent, candidate, stream_id)) { GST_ERROR_OBJECT (self, - "Adding remote candidate to libnice agent:" - " Parsing failed, stream_id: '%s'", stream_id); + "[AddIceCandidate] Parsing failed, stream_id: '%s'", stream_id); return FALSE; } GST_DEBUG_OBJECT (self, - "Added remote candidate to libnice agent, stream_id: '%s'", stream_id); + "[AddIceCandidate] Added successfully, stream_id: '%s'", stream_id); return TRUE; } @@ -572,17 +564,23 @@ kms_webrtc_session_sdp_msg_add_ice_candidate (KmsWebrtcSession * self, static void kms_webrtc_session_new_candidate (KmsIceBaseAgent * agent, - KmsIceCandidate * cand, KmsWebrtcSession * self) + KmsIceCandidate * candidate, KmsWebrtcSession * self) { + GST_DEBUG_OBJECT (self, + "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d", + kms_ice_candidate_get_candidate (candidate), + kms_ice_candidate_get_stream_id (candidate), + kms_ice_candidate_get_component (candidate)); + if (self->external_address != NULL) { - kms_ice_candidate_set_address (cand, self->external_address); + kms_ice_candidate_set_address (candidate, self->external_address); GST_DEBUG_OBJECT (self, "[IceCandidateFound] Mangled local: '%s'", - kms_ice_candidate_get_candidate (cand)); + kms_ice_candidate_get_candidate (candidate)); } - kms_webrtc_session_sdp_msg_add_ice_candidate (self, cand); + kms_webrtc_session_sdp_msg_add_ice_candidate (self, candidate); } static gboolean @@ -728,7 +726,7 @@ kms_webrtc_session_gathering_done (KmsIceBaseAgent * agent, gchar * stream_id, gpointer key, v; gboolean done = TRUE; - GST_DEBUG_OBJECT (self, "ICE gathering done, stream_id: '%s'", stream_id); + GST_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %s", stream_id); KMS_SDP_SESSION_LOCK (self); @@ -762,8 +760,8 @@ kms_webrtc_session_component_state_change (KmsIceBaseAgent * agent, char *stream_id, guint component_id, IceState state, KmsWebrtcSession * self) { - GST_LOG_OBJECT (self, - "state: %s, stream_id: '%s', component_id: %d", + GST_DEBUG_OBJECT (self, + "[IceComponentStateChanged] state: %s, stream_id: %s, component_id: %u", kms_ice_base_agent_state_to_string (state), stream_id, component_id); g_signal_emit (G_OBJECT (self), @@ -816,8 +814,6 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self) gpointer key, v; gboolean ret = TRUE; - GST_INFO_OBJECT (self, "Gather candidates"); - KMS_SDP_SESSION_LOCK (self); g_hash_table_iter_init (&iter, base_rtp_sess->conns); while (g_hash_table_iter_next (&iter, &key, &v)) { @@ -826,11 +822,12 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self) kms_webrtc_session_set_network_ifs_info (self, conn); kms_webrtc_session_set_stun_server_info (self, conn); kms_webrtc_session_set_relay_info (self, conn); + if (!kms_ice_base_agent_start_gathering_candidates (conn->agent, conn->stream_id)) { GST_ERROR_OBJECT (self, - "Cannot start gathering candidates: Agent failed for connection '%s'", - conn->name); + "[IceGatheringStarted] Agent failed for connection '%s', stream_id: %s", + conn->name, conn->stream_id); ret = FALSE; } } @@ -838,8 +835,7 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self) if (ret) { self->gather_started = TRUE; - GST_DEBUG_OBJECT (self, "Gather candidates: Add stored remote candidates"); - + GST_DEBUG_OBJECT (self, "[IceGatheringStarted] Add stored remote candidates"); // Allow errors: FALSE, because at this point the remote SDP should have been // received already, and the gathering process is started already kms_webrtc_session_agent_add_stored_ice_candidates (self, FALSE); @@ -856,8 +852,11 @@ kms_webrtc_session_add_ice_candidate (KmsWebrtcSession * self, { gboolean ret; - GST_DEBUG_OBJECT (self, "Add remote candidate (%s)", - kms_ice_candidate_get_candidate (candidate)); + GST_DEBUG_OBJECT (self, + "[AddIceCandidate] remote: '%s', stream_id: %s, component_id: %d", + kms_ice_candidate_get_candidate (candidate), + kms_ice_candidate_get_stream_id (candidate), + kms_ice_candidate_get_component (candidate)); KMS_SDP_SESSION_LOCK (self); self->remote_candidates = @@ -1822,8 +1821,8 @@ kms_webrtc_session_new_selected_pair_full (KmsIceBaseAgent * agent, KmsIceCandidate * rcandidate, KmsWebrtcSession * self) { GST_DEBUG_OBJECT (self, - "New candidate pair selected, local: '%s', remote: '%s'" - ", stream_id: '%s', component_id: %d", + "[NewCandidatePairSelected] local: '%s', remote: '%s'" + ", stream_id: %s, component_id: %u", kms_ice_candidate_get_candidate (lcandidate), kms_ice_candidate_get_candidate (rcandidate), stream_id, component_id); From 61435acc1bdfe3e882bf9b37e9219cc82b78b310 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 6 Apr 2020 14:39:32 +0200 Subject: [PATCH 06/69] webrtcendpoint minor: print TURN type in debug log --- .../webrtcendpoint/kmsiceniceagent.c | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index 1abdaca50..bd852f5a7 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -399,6 +399,22 @@ from_turn_protocol_to_nice_relay (TurnProtocol transport) } } +static gchar * +from_turn_protocol_to_string (TurnProtocol transport) +{ + switch (transport) { + default: + GST_WARNING ("Wrong type of relay transport. Using TCP"); + case TURN_PROTOCOL_TCP: + return "tcp"; + case TURN_PROTOCOL_UDP: + return "udp"; + case TURN_PROTOCOL_TLS: + case TURN_PROTOCOL_SSLTCP: + return "tls"; + } +} + static void kms_ice_nice_agent_add_relay_server (KmsIceBaseAgent * self, KmsIceRelayServerInfo server_info) @@ -408,8 +424,9 @@ kms_ice_nice_agent_add_relay_server (KmsIceBaseAgent * self, NiceRelayType type = from_turn_protocol_to_nice_relay (server_info.type); GST_DEBUG_OBJECT (self, "Add relay server," - " IP: %s, port: %u, stream_id: %u", - server_info.server_ip, server_info.server_port, id); + " IP: %s, port: %u, type: %s, stream_id: %u", + server_info.server_ip, server_info.server_port, + from_turn_protocol_to_string (server_info.type), id); nice_agent_set_relay_info (nice_agent->priv->agent, id, From 7421529046894ff5ead8de535d877a0941e49c15 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 22 Apr 2020 19:33:21 +0200 Subject: [PATCH 07/69] kmsicenicecandidate minor: print candidate on add error --- src/gst-plugins/webrtcendpoint/kmsiceniceagent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index bd852f5a7..d5f1c7864 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -518,7 +518,8 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, if (nice_agent_set_remote_candidates (nice_agent->priv->agent, nice_cand->stream_id, nice_cand->component_id, candidates) < 0) { GST_WARNING_OBJECT (self, - "[AddIceCandidate] Error in libnice, remote candidate NOT added"); + "[AddIceCandidate] Error in libnice, adding remote: '%s'", + kms_ice_candidate_get_candidate (candidate)); ret = FALSE; } else { ret = TRUE; From ce2dcfd33cde07710f36d1240e1ec0559f4b6481 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 22 Apr 2020 19:34:42 +0200 Subject: [PATCH 08/69] kmswebrtcsession: add_ice_candidate minor refactor & logging --- .../webrtcendpoint/kmswebrtcsession.c | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 9f8b37d2d..4b942be7b 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -304,6 +304,8 @@ kms_webrtc_session_remote_sdp_add_ice_candidate (KmsWebrtcSession * const GstSDPMedia *media; const GstDebugLevel dbg = (allow_error ? GST_LEVEL_DEBUG : GST_LEVEL_ERROR); + const gchar *candidate_str = kms_ice_candidate_get_candidate (candidate); + if (sdp_sess->remote_sdp == NULL) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "Adding remote candidate to remote SDP:" @@ -323,8 +325,8 @@ kms_webrtc_session_remote_sdp_add_ice_candidate (KmsWebrtcSession * if (index >= gst_sdp_message_medias_len (sdp_sess->remote_sdp)) { GST_ERROR_OBJECT (self, - "Adding remote candidate to remote SDP:" - " Invalid media index: %u", index); + "Adding candidate to remote SDP:" + " Invalid media index: %u, remote: '%s'", index, candidate_str); return; } @@ -332,18 +334,18 @@ kms_webrtc_session_remote_sdp_add_ice_candidate (KmsWebrtcSession * if (media == NULL) { GST_ERROR_OBJECT (self, - "Adding remote candidate to remote SDP:" - " No media with index: %u", index); + "Adding candidate to remote SDP:" + " No media with index: %u, remote: '%s'", index, candidate_str); } else { /* TODO: Candidates should be added using extensions */ sdp_media_add_ice_candidate ((GstSDPMedia *) media, self->agent, candidate); - GST_DEBUG_OBJECT (self, "Added remote candidate to remote SDP"); + GST_DEBUG_OBJECT (self, "Added candidate to remote SDP, remote: '%s'", + candidate_str); } } static void -kms_webrtc_session_remote_sdp_add_stored_ice_candidates (KmsWebrtcSession *self, - gboolean allow_error) +kms_webrtc_session_remote_sdp_add_stored_ice_candidates (KmsWebrtcSession *self) { guint i; guint len = g_slist_length (self->remote_candidates); @@ -351,7 +353,9 @@ kms_webrtc_session_remote_sdp_add_stored_ice_candidates (KmsWebrtcSession *self, for (i = 0; i < len; i++) { KmsIceCandidate *candidate = g_slist_nth_data (self->remote_candidates, i); - kms_webrtc_session_remote_sdp_add_ice_candidate (self, candidate, allow_error); + // allow_error: FALSE, because at this point the remote SDP should + // have been received already + kms_webrtc_session_remote_sdp_add_ice_candidate (self, candidate, FALSE); } } @@ -367,6 +371,8 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, KmsSdpMediaHandler *handler; gchar *stream_id; + const gchar *candidate_str = kms_ice_candidate_get_candidate (candidate); + if (!self->gather_started) { GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "[AddIceCandidate] ICE Gathering not started yet"); @@ -399,7 +405,8 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (index >= gst_sdp_message_medias_len (sdp_sess->local_sdp)) { GST_ERROR_OBJECT (self, - "[AddIceCandidate] Invalid media index: %u", index); + "[AddIceCandidate] Invalid media index: %u, remote: '%s'", index, + candidate_str); return FALSE; } @@ -407,14 +414,15 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (media == NULL) { GST_ERROR_OBJECT (self, - "[AddIceCandidate] No media with index: %u", index); + "[AddIceCandidate] No media with index: %u, remote: '%s'", index, + candidate_str); return FALSE; } if (gst_sdp_media_get_port (media) == 0) { GST_DEBUG_OBJECT (self, - "[AddIceCandidate] Unwanted media (port = 0): %s, index: %u", - gst_sdp_media_get_media (media), index); + "[AddIceCandidate] Unwanted media (port = 0): %s, index: %u, remote: '%s'", + gst_sdp_media_get_media (media), index, candidate_str); return TRUE; } @@ -423,8 +431,8 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (handler == NULL) { GST_ERROR_OBJECT (self, - "[AddIceCandidate] No handler for media: %s, index: %u", - gst_sdp_media_get_media (media), index); + "[AddIceCandidate] No handler for media: %s, index: %u, remote: '%s'", + gst_sdp_media_get_media (media), index, candidate_str); return FALSE; } @@ -433,26 +441,28 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, if (stream_id == NULL) { GST_ERROR_OBJECT (self, - "[AddIceCandidate] No stream_id, index: %u", index); + "[AddIceCandidate] No stream_id, index: %u, remote: '%s'", index, + candidate_str); return FALSE; } if (!kms_ice_base_agent_add_ice_candidate (self->agent, candidate, stream_id)) { GST_ERROR_OBJECT (self, - "[AddIceCandidate] Parsing failed, stream_id: '%s'", stream_id); + "[AddIceCandidate] Parsing failed, stream_id: '%s', remote: '%s'", + stream_id, candidate_str); return FALSE; } GST_DEBUG_OBJECT (self, - "[AddIceCandidate] Added successfully, stream_id: '%s'", stream_id); + "[AddIceCandidate] Added successfully, stream_id: '%s', remote: '%s'", + stream_id, candidate_str); return TRUE; } static void -kms_webrtc_session_agent_add_stored_ice_candidates (KmsWebrtcSession * self, - gboolean allow_error) +kms_webrtc_session_agent_add_stored_ice_candidates (KmsWebrtcSession * self) { guint i; guint len = g_slist_length (self->remote_candidates); @@ -460,8 +470,9 @@ kms_webrtc_session_agent_add_stored_ice_candidates (KmsWebrtcSession * self, for (i = 0; i < len; i++) { KmsIceCandidate *candidate = g_slist_nth_data (self->remote_candidates, i); - if (!kms_webrtc_session_agent_add_ice_candidate (self, candidate, - allow_error)) { + // allow_error: FALSE, because at this point the local SDP should + // have been generated, and the gathering process started already + if (!kms_webrtc_session_agent_add_ice_candidate (self, candidate, FALSE)) { return; } } @@ -836,9 +847,7 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self) self->gather_started = TRUE; GST_DEBUG_OBJECT (self, "[IceGatheringStarted] Add stored remote candidates"); - // Allow errors: FALSE, because at this point the remote SDP should have been - // received already, and the gathering process is started already - kms_webrtc_session_agent_add_stored_ice_candidates (self, FALSE); + kms_webrtc_session_agent_add_stored_ice_candidates (self); } KMS_SDP_SESSION_UNLOCK (self); @@ -1574,16 +1583,11 @@ kms_webrtc_session_start_transport_send (KmsWebrtcSession * self, gst_sdp_media_get_media (neg_media), index); } - // Allow errors: FALSE, because at this point the remote SDP should have been - // received already - kms_webrtc_session_remote_sdp_add_stored_ice_candidates (self, FALSE); + kms_webrtc_session_remote_sdp_add_stored_ice_candidates (self); if (self->gather_started) { GST_DEBUG_OBJECT (self, "Start transport: Add stored remote candidates"); - - // Allow errors: FALSE, because at this point the remote SDP should have been - // received already, and the gathering process is started already - kms_webrtc_session_agent_add_stored_ice_candidates (self, FALSE); + kms_webrtc_session_agent_add_stored_ice_candidates (self); } else { GST_DEBUG_OBJECT (self, "Start transport:" From 2f962a4ad10ecf03c9cf17958c0d5cac921f71aa Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 22 Apr 2020 19:44:10 +0200 Subject: [PATCH 09/69] Bump development version to 6.13.2-0kurento1 --- debian/changelog | 5 +++-- src/server/interface/elements.kmd.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 1e2ea9e5b..6736e7211 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ -kms-elements (6.13.1-0kurento1) UNRELEASED; urgency=medium +kms-elements (6.13.2-0kurento1) UNRELEASED; urgency=medium * UNRELEASED + * UNRELEASED - -- Juan Navarro Thu, 19 Dec 2019 19:17:38 +0100 + -- Juan Navarro Wed, 22 Apr 2020 19:44:10 +0200 kms-elements (6.13.0-0kurento1) testing; urgency=medium diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 12f786741..1638c4f71 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.13.1-dev", + "version": "6.13.2-dev", "kurentoVersion": "^6.7.0", "code": { "kmd": { From a4c9f35b53e2af3ff5b06edb8c1ce338907e9503 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 27 Apr 2020 21:36:14 +0200 Subject: [PATCH 10/69] webrtcendpoint: fix leaked sockets from libnice breaking change nice_agent_remove_stream(), called from kms_ice_nice_agent_remove_stream(), was silently converted from synchronous to asynchronous in libnice 0.1.16. I know that Semantic Versioning tells us that 0.X.Y should be considered as unstable API, but come on, that change wasn't *nice* of libnice ;-) To solve the issue, we need to run a last iteration of its GMainLoop context in order to allow it release all resources and object references. Otherwise, libnice takes a reference to the NiceAgent object in nice_agent_remove_stream(), and it never gets to release it, because the unref is done in the callback function on_stream_refreshes_pruned(). Thanks to @omgold for finding the root cause of the issue and proposing a solution! https://github.com/Kurento/bugtracker/issues/460#issuecomment-619529496 A bug report has been filled in libnice as a result of this commit: https://gitlab.freedesktop.org/libnice/libnice/issues/110 Fixes https://github.com/Kurento/bugtracker/issues/253 Fixes https://github.com/Kurento/bugtracker/issues/460 --- src/gst-plugins/webrtcendpoint/kmsiceniceagent.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index d5f1c7864..d6f34df2c 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -274,6 +274,12 @@ kms_ice_nice_agent_finalize (GObject * object) GST_DEBUG_OBJECT (self, "finalize"); + // nice_agent_remove_stream(), called from kms_ice_nice_agent_remove_stream(), + // is an asynchronous function. Run a last iteration of its GMainLoop context + // in order to allow it run and release all resources and object references. + g_main_context_wakeup (self->priv->context); + g_main_context_iteration (self->priv->context, FALSE); + g_clear_object (&self->priv->agent); g_slist_free_full (self->priv->remote_candidates, g_object_unref); From af03fc05aaa1b5db79227479bef3bdb604b03822 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 22 May 2020 12:14:48 +0200 Subject: [PATCH 11/69] Reduce default log levels for WebRTC-related modules Defaulting to INFO for some basic operations ends up logging thousands of lines that don't really convey anything useful in a typical deployment, and can make it harder to find other more interesting messages. --- .../webrtcendpoint/kmsiceniceagent.c | 32 ++++++++-------- .../webrtcendpoint/kmswebrtcendpoint.c | 20 +++++----- .../webrtcendpoint/kmswebrtcsession.c | 38 +++++++++---------- .../objects/WebRtcEndpointImpl.cpp | 8 ++-- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index d6f34df2c..c1f5f72e0 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -86,7 +86,7 @@ kms_ice_nice_agent_new_candidate_full (NiceAgent * agent, stream_id_str); g_free (stream_id_str); - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceCandidateFound] local: '%s', stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (kms_candidate), stream_id, component_id); @@ -108,7 +108,7 @@ kms_ice_nice_agent_new_remote_candidate_full (NiceAgent * agent, kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, stream_id_str); - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "[AddIceCandidate] Found peer-reflexive remote: '%s'", kms_ice_candidate_get_candidate (kms_candidate)); @@ -126,7 +126,7 @@ kms_ice_nice_agent_gathering_done (NiceAgent * agent, guint stream_id, char buff[33]; char *ret; - GST_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %u", stream_id); + GST_LOG_OBJECT (self, "[IceGatheringDone] stream_id: %u", stream_id); //convert id to char* g_snprintf (buff, 32, "%u", stream_id); @@ -174,7 +174,7 @@ kms_ice_nice_agent_component_state_change (NiceAgent * agent, guint stream_id, ret = g_strdup (buff); state_ = kms_ice_nice_agent_nice_to_ice_state (state); - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceComponentStateChanged] state: %s, stream_id: %u, component_id: %u", nice_component_state_to_string (state), stream_id, component_id); @@ -220,7 +220,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, goto end; } - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[NewCandidatePairSelected] local: '%s', remote: '%s'" ", stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (local_candidate), @@ -272,7 +272,7 @@ kms_ice_nice_agent_finalize (GObject * object) { KmsIceNiceAgent *self = KMS_ICE_NICE_AGENT (object); - GST_DEBUG_OBJECT (self, "finalize"); + GST_LOG_OBJECT (self, "finalize"); // nice_agent_remove_stream(), called from kms_ice_nice_agent_remove_stream(), // is an asynchronous function. Run a last iteration of its GMainLoop context @@ -318,10 +318,10 @@ kms_ice_nice_agent_add_stream (KmsIceBaseAgent * self, const char *stream_id, return NULL; } - GST_DEBUG_OBJECT (self, "Added data stream, ID: %u, stream_id: %s", + GST_LOG_OBJECT (self, "Added data stream, ID: %u, stream_id: %s", id, stream_id); - GST_DEBUG_OBJECT (self, "Set port range: [%u, %u]", min_port, max_port); + GST_LOG_OBJECT (self, "Set port range: [%u, %u]", min_port, max_port); for (i = 1; i <= KMS_NICE_N_COMPONENTS; i++) { nice_agent_set_port_range (nice_agent->priv->agent, id, i, min_port, max_port); @@ -343,7 +343,7 @@ kms_ice_nice_agent_remove_stream (KmsIceBaseAgent * self, const char *stream_id) KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Remove data stream, stream_id: %u", id); + GST_LOG_OBJECT (self, "Remove data stream, stream_id: %u", id); nice_agent_remove_stream (nice_agent->priv->agent, id); } @@ -355,7 +355,7 @@ kms_ice_nice_agent_set_remote_credentials (KmsIceBaseAgent * self, KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Set remote credentials, stream_id: %u", id); + GST_LOG_OBJECT (self, "Set remote credentials, stream_id: %u", id); return nice_agent_set_remote_credentials (nice_agent->priv->agent, id, ufrag, pwd); @@ -368,7 +368,7 @@ kms_ice_nice_agent_get_local_credentials (KmsIceBaseAgent * self, KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self); guint id = atoi (stream_id); - GST_DEBUG_OBJECT (self, "Get local credentials, stream_id: %u", id); + GST_LOG_OBJECT (self, "Get local credentials, stream_id: %u", id); nice_agent_get_local_credentials (nice_agent->priv->agent, id, ufrag, pwd); } @@ -377,14 +377,14 @@ static void kms_ice_nice_agent_set_remote_description (KmsIceBaseAgent * self, const char *remote_description) { - GST_LOG_OBJECT (self, "Nothing to do in set_remote_description"); + GST_TRACE_OBJECT (self, "Nothing to do in set_remote_description"); } static void kms_ice_nice_agent_set_local_description (KmsIceBaseAgent * self, const char *local_description) { - GST_LOG_OBJECT (self, "Nothing to do in set_local_description"); + GST_TRACE_OBJECT (self, "Nothing to do in set_local_description"); } static NiceRelayType @@ -458,7 +458,7 @@ kms_ice_nice_agent_start_gathering_candidates (KmsIceBaseAgent * self, gboolean ok = nice_agent_gather_candidates (nice_agent->priv->agent, id); if (ok) { - GST_DEBUG_OBJECT (self, "[IceGatheringStarted] stream_id: %s", stream_id); + GST_LOG_OBJECT (self, "[IceGatheringStarted] stream_id: %s", stream_id); } return ok; @@ -516,7 +516,7 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, nice_cand->stream_id = id; candidates = g_slist_append (NULL, nice_cand); - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[AddIceCandidate] remote: '%s', stream_id: %u, component_id: %u", kms_ice_candidate_get_candidate (candidate), nice_cand->stream_id, nice_cand->component_id); @@ -647,7 +647,7 @@ kms_ice_nice_agent_get_controlling_mode (KmsIceBaseAgent * self) static void kms_ice_nice_agent_run_agent (KmsIceBaseAgent * self) { - GST_LOG_OBJECT (self, "Nothing to do in run_agent"); + GST_TRACE_OBJECT (self, "Nothing to do in run_agent"); } NiceAgent * diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index 33f5b74f2..0f8a0fa3b 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -109,7 +109,7 @@ on_ice_candidate (KmsWebrtcSession * sess, KmsIceCandidate * candidate, { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d", kms_ice_candidate_get_candidate (candidate), kms_ice_candidate_get_stream_id (candidate), @@ -125,7 +125,7 @@ on_ice_gathering_done (KmsWebrtcSession * sess, KmsWebrtcEndpoint * self) { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); - GST_INFO_OBJECT (self, "[IceGatheringDone] session: '%s'", sdp_sess->id_str); + GST_DEBUG_OBJECT (self, "[IceGatheringDone] session: '%s'", sdp_sess->id_str); g_signal_emit (G_OBJECT (self), kms_webrtc_endpoint_signals[SIGNAL_ON_ICE_GATHERING_DONE], 0, @@ -138,7 +138,7 @@ on_ice_component_state_change (KmsWebrtcSession * sess, const gchar * stream_id, { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceComponentStateChanged] state: %s, stream_id: %s, component_id: %u", kms_ice_base_agent_state_to_string (state), stream_id, component_id); @@ -201,7 +201,7 @@ kms_webrtc_endpoint_add_data_sink_pad (KmsWebrtcEndpoint * self, return FALSE; } - GST_DEBUG_OBJECT (self, "Added pad %" GST_PTR_FORMAT, pad); + GST_TRACE_OBJECT (self, "Added pad %" GST_PTR_FORMAT, pad); return TRUE; } @@ -272,7 +272,7 @@ kms_webrtc_endpoint_remove_pad (KmsWebrtcSession * session, GstPad * pad, return FALSE; } - GST_DEBUG_OBJECT (self, "Remove sink pad %" GST_PTR_FORMAT, pad); + GST_TRACE_OBJECT (self, "Remove sink pad %" GST_PTR_FORMAT, pad); kms_element_remove_sink_by_type_full (KMS_ELEMENT (self), type, description); @@ -288,7 +288,7 @@ new_selected_pair_full (KmsWebrtcSession * sess, { KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "[NewCandidatePairSelected] local: '%s', remote: '%s'" ", stream_id: %s, component_id: %u", kms_ice_candidate_get_candidate (lcandidate), @@ -442,7 +442,7 @@ kms_webrtc_endpoint_gather_candidates (KmsWebrtcEndpoint * self, return FALSE; } - GST_INFO_OBJECT (self, "[IceGatheringStarted] session: '%s'", sess_id); + GST_DEBUG_OBJECT (self, "[IceGatheringStarted] session: '%s'", sess_id); webrtc_sess = KMS_WEBRTC_SESSION (sess); g_signal_emit_by_name (webrtc_sess, "gather-candidates", &ret); @@ -466,7 +466,7 @@ kms_webrtc_endpoint_add_ice_candidate (KmsWebrtcEndpoint * self, } // Remote candidates haven't been assigned a stream_id yet, so don't print it - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "[AddIceCandidate] remote: '%s', component_id: %d", kms_ice_candidate_get_candidate (candidate), kms_ice_candidate_get_component (candidate)); @@ -559,7 +559,7 @@ kms_webrtc_endpoint_dispose (GObject * object) { KmsWebrtcEndpoint *self = KMS_WEBRTC_ENDPOINT (object); - GST_DEBUG_OBJECT (self, "dispose"); + GST_LOG_OBJECT (self, "dispose"); KMS_ELEMENT_LOCK (self); @@ -576,7 +576,7 @@ kms_webrtc_endpoint_finalize (GObject * object) { KmsWebrtcEndpoint *self = KMS_WEBRTC_ENDPOINT (object); - GST_DEBUG_OBJECT (self, "finalize"); + GST_LOG_OBJECT (self, "finalize"); g_free (self->priv->stun_server_ip); g_free (self->priv->turn_url); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 4b942be7b..a780513c9 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -339,7 +339,7 @@ kms_webrtc_session_remote_sdp_add_ice_candidate (KmsWebrtcSession * } else { /* TODO: Candidates should be added using extensions */ sdp_media_add_ice_candidate ((GstSDPMedia *) media, self->agent, candidate); - GST_DEBUG_OBJECT (self, "Added candidate to remote SDP, remote: '%s'", + GST_LOG_OBJECT (self, "Added candidate to remote SDP, remote: '%s'", candidate_str); } } @@ -454,7 +454,7 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, return FALSE; } - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[AddIceCandidate] Added successfully, stream_id: '%s', remote: '%s'", stream_id, candidate_str); @@ -547,7 +547,7 @@ kms_webrtc_session_sdp_msg_add_ice_candidate (KmsWebrtcSession * self, cand); g_object_unref (handler); - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceCandidateFound] Added local candidate to local SDP media: %s, index: %u", gst_sdp_media_get_media (media), index); @@ -577,7 +577,7 @@ static void kms_webrtc_session_new_candidate (KmsIceBaseAgent * agent, KmsIceCandidate * candidate, KmsWebrtcSession * self) { - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d", kms_ice_candidate_get_candidate (candidate), kms_ice_candidate_get_stream_id (candidate), @@ -737,7 +737,7 @@ kms_webrtc_session_gathering_done (KmsIceBaseAgent * agent, gchar * stream_id, gpointer key, v; gboolean done = TRUE; - GST_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %s", stream_id); + GST_LOG_OBJECT (self, "[IceGatheringDone] stream_id: %s", stream_id); KMS_SDP_SESSION_LOCK (self); @@ -771,7 +771,7 @@ kms_webrtc_session_component_state_change (KmsIceBaseAgent * agent, char *stream_id, guint component_id, IceState state, KmsWebrtcSession * self) { - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[IceComponentStateChanged] state: %s, stream_id: %s, component_id: %u", kms_ice_base_agent_state_to_string (state), stream_id, component_id); @@ -861,7 +861,7 @@ kms_webrtc_session_add_ice_candidate (KmsWebrtcSession * self, { gboolean ret; - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[AddIceCandidate] remote: '%s', stream_id: %s, component_id: %d", kms_ice_candidate_get_candidate (candidate), kms_ice_candidate_get_stream_id (candidate), @@ -1006,10 +1006,10 @@ gst_media_add_remote_candidates (KmsWebrtcSession * self, pwd); return; } else { - GST_DEBUG ("Set remote message credentials OK (%s, %s).", ufrag, pwd); + GST_LOG ("Set remote message credentials OK (%s, %s).", ufrag, pwd); } } else { - GST_DEBUG ("Set remote media credentials OK (%s, %s).", ufrag, pwd); + GST_LOG ("Set remote media credentials OK (%s, %s).", ufrag, pwd); } mid = gst_sdp_media_get_attribute_val (media, "mid"); @@ -1073,7 +1073,7 @@ static void kms_webrtc_session_data_session_established_cb (KmsWebRtcDataSessionBin * session, gboolean connected, KmsWebrtcSession * self) { - GST_DEBUG_OBJECT (self, "Data session %" GST_PTR_FORMAT " %s", + GST_LOG_OBJECT (self, "Data session %" GST_PTR_FORMAT " %s", session, (connected) ? "established" : "finished"); g_signal_emit (self, @@ -1314,7 +1314,7 @@ configure_data_session (KmsWebrtcSession * self, const GstSDPMedia * media) GST_ERROR_OBJECT (self, "Data session can not be configured"); return FALSE; } else { - GST_INFO_OBJECT (self, "Data session configured with port %d", port); + GST_DEBUG_OBJECT (self, "Data session configured with port %d", port); } g_object_set (self->data_session, "sctp-local-port", port, @@ -1440,7 +1440,7 @@ kms_webrtc_session_support_sctp_stream (KmsWebrtcSession * self, } kms_webrtc_session_add_data_session (self, neg_media, conn); } else { - GST_DEBUG_OBJECT (self, "SCTP: waiting for DTLS layer to be established"); + GST_LOG_OBJECT (self, "SCTP: waiting for DTLS layer to be established"); } kms_ref_struct_unref (KMS_REF_STRUCT_CAST (data)); @@ -1460,7 +1460,7 @@ kms_webrtc_session_add_connection (KmsWebrtcSession * self, g_object_get (conn, "added", &connected, NULL); if (connected) { - GST_DEBUG_OBJECT (self, "Conn already added"); + GST_LOG_OBJECT (self, "Conn already added"); } else { gboolean active; @@ -1547,7 +1547,7 @@ kms_webrtc_session_start_transport_send (KmsWebrtcSession * self, KmsSdpMediaHandler *handler; if (sdp_utils_media_is_inactive (neg_media)) { - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "Starting transport: Media is inactive: %s, index: %u", gst_sdp_media_get_media (neg_media), index); continue; @@ -1578,7 +1578,7 @@ kms_webrtc_session_start_transport_send (KmsWebrtcSession * self, gst_media_add_remote_candidates (self, index, rem_media, conn, ufrag, pwd); - GST_INFO_OBJECT (self, + GST_DEBUG_OBJECT (self, "Started transport for media: %s, index: %u", gst_sdp_media_get_media (neg_media), index); } @@ -1638,7 +1638,7 @@ kms_webrtc_session_parse_turn_url (KmsWebrtcSession * self) if ((self->turn_url == NULL) || (g_strcmp0 ("", self->turn_url) == 0)) { - GST_INFO_OBJECT (self, "TURN server info cleared"); + GST_DEBUG_OBJECT (self, "TURN server info cleared"); return; } @@ -1680,7 +1680,7 @@ kms_webrtc_session_parse_turn_url (KmsWebrtcSession * self) } else { g_string_append (safe_url, separated_url); } - GST_INFO_OBJECT (self, "TURN server info set: %s", safe_url->str); + GST_DEBUG_OBJECT (self, "TURN server info set: %s", safe_url->str); g_string_free (safe_url, TRUE); } else { GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, @@ -1777,7 +1777,7 @@ kms_webrtc_session_finalize (GObject * object) { KmsWebrtcSession *self = KMS_WEBRTC_SESSION (object); - GST_DEBUG_OBJECT (self, "finalize"); + GST_LOG_OBJECT (self, "finalize"); g_clear_object (&self->agent); g_main_context_unref (self->context); @@ -1824,7 +1824,7 @@ kms_webrtc_session_new_selected_pair_full (KmsIceBaseAgent * agent, KmsIceCandidate * lcandidate, KmsIceCandidate * rcandidate, KmsWebrtcSession * self) { - GST_DEBUG_OBJECT (self, + GST_LOG_OBJECT (self, "[NewCandidatePairSelected] local: '%s', remote: '%s'" ", stream_id: %s, component_id: %u", kms_ice_candidate_get_candidate (lcandidate), diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index 4ccf0b8a3..e1b13a32f 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -517,8 +517,8 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, g_object_set (G_OBJECT (element), PROP_EXTERNAL_ADDRESS, externalAddress.c_str(), NULL); } else { - GST_INFO ("No predefined external IP address found in config;" - " you can set one or default to STUN automatic discovery"); + GST_DEBUG ("No predefined external IP address found in config;" + " you can set one or default to STUN automatic discovery"); } std::string networkInterfaces; @@ -528,8 +528,8 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, g_object_set (G_OBJECT (element), PROP_NETWORK_INTERFACES, networkInterfaces.c_str(), NULL); } else { - GST_INFO ("No predefined network interfaces found in config;" - " you can set one or default to ICE automatic discovery"); + GST_DEBUG ("No predefined network interfaces found in config;" + " you can set one or default to ICE automatic discovery"); } uint stunPort = 0; From c6e312beace6a493b982f2af5f6731c22fa465a4 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 25 May 2020 11:01:45 +0200 Subject: [PATCH 12/69] recorder minor: better message when caps fail match --- src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index 0751d40ca..61475ba8c 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -1638,7 +1638,10 @@ kms_recorder_endpoint_query_accept_caps (KmsElement * element, GstPad * pad, ret = gst_pad_peer_query_accept_caps (srcpad, accept); gst_object_unref (srcpad); } else { - GST_ERROR_OBJECT (self, "Incompatbile caps %" GST_PTR_FORMAT, caps); + GST_ERROR_OBJECT (self, + "Input caps (%" GST_PTR_FORMAT + ") doesn't match profile caps (%" GST_PTR_FORMAT ")", + accept, caps); } end: From cfae66baea658e2c1b86b101bf1414a6b57e762f Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 9 Jun 2020 14:12:22 +0200 Subject: [PATCH 13/69] recorder: review API doc and clarify some paragraphs --- .../elements.RecorderEndpoint.kmd.json | 106 ++++++++++++------ 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index 5f341db86..ff1ac5d80 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -5,9 +5,9 @@ "extends": "UriEndpoint", "doc": "Provides the functionality to store contents.

- The recorder can store in local files or in a network resource. It receives a - media stream from another :rom:cls:`MediaElement` (i.e. the source), and - stores it in the designated location. + The Recorder can store media in local files or in a network resource. It + receives a media stream from another :rom:cls:`MediaElement` (i.e. the + source), and stores it in the designated location.

The following information has to be provided in order to create a @@ -15,54 +15,66 @@

  • - URI of the resource where media will be stored. Following schemas are - supported: + Destination URI, where media will be stored. These formats + are supported:
    • - Files: mounted in the local file system. + File: A file path that exists in the local file system.
      • file:///path/to/file
    • - HTTP: Requires the server to support method PUT + HTTP: Method PUT used against a remote server.
      • http(s)://{server-ip}/path/to/file
      • - http(s)://username:password@{server-ip}/path/to/file + + http(s)://{username}:{password}@{server-ip}/path/to/file +
  • - Relative URIs (with no schema) are supported. They are completed prepending - a default URI defined by property defaultPath. This property is - defined in the configuration file + Relative URIs (with no schema) are supported. They are completed by + prepending a default URI defined by property defaultPath. This + property is defined in the configuration file /etc/kurento/modules/kurento/UriEndpoint.conf.ini, and the default value is file:///var/lib/kurento/
  • - The media profile (:rom:attr:`MediaProfileSpecType`) used to store the file. - This will determine the encoding. See below for more details about media - profile. + The Media Profile (:rom:attr:`MediaProfileSpecType`) used for + storage. This will determine the video and audio encoding. See below for + more details about Media Profile.
  • Optionally, the user can select if the endpoint will stop processing once - the EndOfStream event is detected. + the EndOfStream event is detected.

- RecorderEndpoint requires access to the resource where stream is going to be - recorded. If it's a local file (file://), the system user running - the media server daemon (kurento by default), needs to have write permissions - for that URI. If it's an HTTP server, it must be accessible from the machine - where media server is running, and also have the correct access rights. - Otherwise, the media server won't be able to store any information, and an + + RecorderEndpoint requires access to the resource where stream is going to be + recorded + + . Otherwise, the media server won't be able to store any information, and an :rom:evt:`Error` will be fired. Please note that if you haven't subscribed to that type of event, you can be left wondering why your media is not being saved, while the error message was ignored.

+
    +
  • + To write local files (if you use file://), the user running the + media server (by default, user kurento) needs to have write + permissions for the requested path. +
  • +
  • + To save into an HTTP server, the server must be accessible through the + network, and also have the correct access rights. +
  • +

The media profile is quite an important parameter, as it will determine whether the server needs to perform on-the-fly transcoding of the media. If @@ -94,11 +106,18 @@ a very important decision.

- Recording will start as soon as the user invokes the record method. The - recorder will then store, in the location indicated, the media that the source - is sending to the endpoint's sink. If no media is being received, or no - endpoint has been connected, then the destination will be empty. The recorder - starts storing information into the file as soon as it gets it. + Recording will start as soon as the user invokes the + record method. The recorder will then store, in the location + indicated, the media that the source is sending to the endpoint. If no media + is being received, or no endpoint has been connected, then the destination + will be empty. The recorder starts storing information into the file as soon + as it gets it. +

+

+ Stopping the recording process is done through the + stopAndWait method, which will return only after all the + information was stored correctly. If the file is empty, this means that no + media arrived at the recorder.

When another endpoint is connected to the recorder, by default both AUDIO and @@ -117,17 +136,32 @@ recording is stopped.

- It is recommended to start recording only after media arrives, either to the - endpoint that is the source of the media connected to the recorder, to the - recorder itself, or both. Users may use the MediaFlowIn and MediaFlowOut - events, and synchronize the recording with the moment media comes in. In any - case, nothing will be stored in the file until the first media packets arrive. -

-

- Stopping the recording process is done through the stopAndWait method, which - will return only after all the information was stored correctly. If the file - is empty, this means that no media arrived at the recorder. + + It is recommended to start recording only after media arrives + + . For this, you may use the MediaFlowInStateChange and + MediaFlowOutStateChange + events of your endpoints, and synchronize the recording with the moment media + comes into the Recorder. For example:

+
    +
  1. + When the remote video arrives to KMS, your WebRtcEndpoint will start + generating packets into the Kurento Pipeline, and it will trigger a + MediaFlowOutStateChange event. +
  2. +
  3. + When video packets arrive from the WebRtcEndpoint to the RecorderEndpoint, + the RecorderEndpoint will raise a MediaFlowInStateChange event. +
  4. +
  5. + You should only start recording when RecorderEndpoint has notified a + MediaFlowInStateChange for ALL streams (so, if you record + AUDIO+VIDEO, your application must receive a + MediaFlowInStateChange event for audio, and another + MediaFlowInStateChange event for video). +
  6. +
", "constructor": { From ec32f396573dcc4ef5a3051209bfab20ef84d9ab Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 10 Jun 2020 12:33:29 +0200 Subject: [PATCH 14/69] tests/webrtcendpoint: print session ID and ICE candidate --- tests/check/element/webrtcendpoint.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index 126b9467f..a94350817 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -2304,6 +2304,9 @@ not_enough_ports_on_ice_candidate (GstElement * self, gchar * sess_id, const KmsIceTcpCandidateType tcp_type = kms_ice_candidate_get_candidate_tcp_type (candidate); + GST_DEBUG ("SessionId: '%s', candidate: '%s'", sess_id, + kms_ice_candidate_get_candidate (candidate)); + if (offerer_num == 2 && component == KMS_ICE_COMPONENT_RTCP) { fail_if (proto != KMS_ICE_PROTOCOL_TCP); fail_if (tcp_type != KMS_ICE_TCP_CANDIDATE_TYPE_ACTIVE); From 8fe50d8478933347fce53196b44e9618ed0ddb39 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 15 Jun 2020 17:58:50 +0200 Subject: [PATCH 15/69] tests/webrtcendpoint: use a HashMap to store gathered addresses The previous method of relying on a limited number of ports was working only for IPv4. As soon as IPv6 is used, this was confusing and prone to errors. Also it failed because with IPv6 it was actually finding an UDP candidate for the RTCP component (number 2) of the second WebRtcEndpoint. This method is more robust: just store all seen candidates, and check that they never get to repeat. It does the same as before, but it works better for both IPv4 and IPv6. --- tests/check/element/webrtcendpoint.c | 79 ++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index a94350817..db96dc42b 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -21,6 +21,7 @@ #include +#include #include #define KMS_VIDEO_PREFIX "video_src_" @@ -2294,29 +2295,51 @@ GST_START_TEST (test_port_range) } GST_END_TEST +// ---------------------------------------------------------------------------- + +// not_enough_ports +// ---------------- + +typedef struct { + GHashTable *tcp; + GHashTable *udp; +} GatheringData; + static void -not_enough_ports_on_ice_candidate (GstElement * self, gchar * sess_id, - KmsIceCandidate * candidate, gpointer offerer_num_pt) +not_enough_ports_on_ice_candidate (GstElement *self, gchar *sess_id, + KmsIceCandidate *candidate, GatheringData *gatheringData) { - const guint offerer_num = GPOINTER_TO_UINT (offerer_num_pt); - const KmsIceComponent component = kms_ice_candidate_get_component (candidate); - const KmsIceProtocol proto = kms_ice_candidate_get_protocol (candidate); const KmsIceTcpCandidateType tcp_type = kms_ice_candidate_get_candidate_tcp_type (candidate); + const KmsIceProtocol proto = kms_ice_candidate_get_protocol (candidate); GST_DEBUG ("SessionId: '%s', candidate: '%s'", sess_id, kms_ice_candidate_get_candidate (candidate)); - if (offerer_num == 2 && component == KMS_ICE_COMPONENT_RTCP) { - fail_if (proto != KMS_ICE_PROTOCOL_TCP); - fail_if (tcp_type != KMS_ICE_TCP_CANDIDATE_TYPE_ACTIVE); + if (tcp_type == KMS_ICE_TCP_CANDIDATE_TYPE_ACTIVE) { + return; + } + + // Check that this candidate doesn't contain a repeated address + NiceAddress *address = nice_address_new (); + gboolean ok = nice_address_set_from_string ( + address, kms_ice_candidate_get_address (candidate)); + fail_unless (ok); + nice_address_set_port (address, kms_ice_candidate_get_port (candidate)); + + if (proto == KMS_ICE_PROTOCOL_TCP) { + fail_if (g_hash_table_contains (gatheringData->tcp, address)); + g_hash_table_add (gatheringData->tcp, address); + } else { + fail_if (g_hash_table_contains (gatheringData->udp, address)); + g_hash_table_add (gatheringData->udp, address); } } GST_START_TEST (test_not_enough_ports) { GArray *codecs_array; - gchar *codecs[] = { "VP8/90000", NULL }; + gchar *codecs[] = {"VP8/90000", NULL}; gchar *offerer_sess_id, *second_offerer_sess_id; GstSDPMessage *offer, *second_offer; gchar *sdp_str = NULL; @@ -2324,11 +2347,18 @@ GST_START_TEST (test_not_enough_ports) GstElement *offerer = gst_element_factory_make ("webrtcendpoint", NULL); GstElement *second_offerer = gst_element_factory_make ("webrtcendpoint", NULL); - CandidateRangeData offerer_cand_data; + CandidateRangeData offerer_cand_data; offerer_cand_data.min_port = 55000; offerer_cand_data.max_port = 55002; + GatheringData gatheringData = { + .tcp = g_hash_table_new_full (NULL, (GEqualFunc) &nice_address_equal, + (GDestroyNotify) &nice_address_free, NULL), + .udp = g_hash_table_new_full (NULL, (GEqualFunc) &nice_address_equal, + (GDestroyNotify) &nice_address_free, NULL), + }; + codecs_array = create_codecs_array (codecs); g_object_set (offerer, "num-video-medias", 1, "video-codecs", g_array_ref (codecs_array), "min-port", offerer_cand_data.min_port, @@ -2342,16 +2372,16 @@ GST_START_TEST (test_not_enough_ports) g_signal_emit_by_name (offerer, "create-session", &offerer_sess_id); GST_DEBUG_OBJECT (offerer, "Created session with id '%s'", offerer_sess_id); - g_signal_emit_by_name (second_offerer, "create-session", - &second_offerer_sess_id); - GST_DEBUG_OBJECT (second_offerer, "Created session with id '%s'", - second_offerer_sess_id); + g_signal_emit_by_name ( + second_offerer, "create-session", &second_offerer_sess_id); + GST_DEBUG_OBJECT ( + second_offerer, "Created session with id '%s'", second_offerer_sess_id); g_signal_connect (G_OBJECT (offerer), "on-ice-candidate", - G_CALLBACK (not_enough_ports_on_ice_candidate), GUINT_TO_POINTER (1)); + G_CALLBACK (not_enough_ports_on_ice_candidate), &gatheringData); g_signal_connect (G_OBJECT (second_offerer), "on-ice-candidate", - G_CALLBACK (not_enough_ports_on_ice_candidate), GUINT_TO_POINTER (2)); + G_CALLBACK (not_enough_ports_on_ice_candidate), &gatheringData); /* SDP negotiation */ g_signal_emit_by_name (offerer, "generate-offer", offerer_sess_id, &offer); @@ -2360,11 +2390,11 @@ GST_START_TEST (test_not_enough_ports) g_free (sdp_str); sdp_str = NULL; - g_signal_emit_by_name (second_offerer, "generate-offer", - second_offerer_sess_id, &second_offer); + g_signal_emit_by_name ( + second_offerer, "generate-offer", second_offerer_sess_id, &second_offer); fail_unless (second_offer != NULL); - GST_DEBUG ("Second offer:\n%s", (sdp_str = - gst_sdp_message_as_text (second_offer))); + GST_DEBUG ( + "Second offer:\n%s", (sdp_str = gst_sdp_message_as_text (second_offer))); g_free (sdp_str); sdp_str = NULL; @@ -2391,8 +2421,8 @@ GST_START_TEST (test_not_enough_ports) * TCP-ACT local candidate : [192.168.1.2]:0 for s1/c2 */ - g_signal_emit_by_name (second_offerer, "gather-candidates", - second_offerer_sess_id, &ret); + g_signal_emit_by_name ( + second_offerer, "gather-candidates", second_offerer_sess_id, &ret); #ifdef HAVE_LIBNICE_0_1_14 fail_unless (ret); #else @@ -2406,9 +2436,14 @@ GST_START_TEST (test_not_enough_ports) g_object_unref (second_offerer); g_free (offerer_sess_id); g_free (second_offerer_sess_id); + + g_hash_table_unref (gatheringData.tcp); + g_hash_table_unref (gatheringData.udp); } GST_END_TEST +// ---------------------------------------------------------------------------- + static void on_ice_candidate_check_mid (GstElement * self, gchar * sess_id, KmsIceCandidate * candidate, const gchar * expected_mid) From 143fccb9f29722f7c0a2e189fe01bc2d839b7235 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 15 Jun 2020 18:58:13 +0200 Subject: [PATCH 16/69] tests/webrtcendpoint: re-enable all tests that were commented out We had some WebRTC tests that had been commented out to disable, due to various causes. According to the comments there were some libnice issues that would cause some of these tests to fail. The issues must have been resolved by now, because all these test now pass successfully. Currently Kurento is using libnice 0.1.17. Also, the check about gathering state of libnice was superfluous and unneeded, because that's already controlled from the previous layer, at KmsWebrtcSession. This was also breaking some tests, but now that the condition is removed, the tests pass. --- CMakeLists.txt | 7 - .../webrtcendpoint/kmsiceniceagent.c | 26 +- tests/check/element/webrtcendpoint.c | 236 ++---------------- 3 files changed, 17 insertions(+), 252 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e96db077..842972dea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,12 +77,5 @@ set(CMAKE_INSTALL_GST_PLUGINS_DIR ${CMAKE_INSTALL_LIBDIR}/gstreamer-1.5) enable_testing() -# Used by test "webrtcendpoint" and kmsiceniceagent -# Enabled if the system has libnice >=0.1.14 -if(NOT nice_VERSION VERSION_LESS "0.1.14") - message(STATUS "libnice version >= 0.1.14, enable specific tests") - add_definitions(-DHAVE_LIBNICE_0_1_14) -endif() - add_subdirectory(src) add_subdirectory(tests) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index c1f5f72e0..9eedf4de3 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -489,29 +489,9 @@ kms_ice_nice_agent_add_ice_candidate (KmsIceBaseAgent * self, return FALSE; } - /* FIXME Upstream libnice has a bug which keeps the NiceAgent state as - * 'DISCONNECTED', even when the ICE Gathering has been started. - * This test relies on that state being correctly updated, - * so for now, only our custom version of libnice does the job. - * See: https://lists.freedesktop.org/archives/nice/2017-September/001394.html - */ -/* FIXME it turns out it also fails with libnice 0.1.13! -#ifndef HAVE_LIBNICE_0_1_14 - NiceComponentState state = - nice_agent_get_component_state (nice_agent->priv->agent, - nice_cand->stream_id, nice_cand->component_id); - - // Docs: "You must first call nice_agent_gather_candidates() - // before calling nice_agent_set_remote_candidates()" - if (state == NICE_COMPONENT_STATE_DISCONNECTED - || state == NICE_COMPONENT_STATE_FAILED) { - GST_ERROR_OBJECT (self, - "Cannot add candidates if ICE Gathering isn't started," - " NiceAgent state: %s", nice_component_state_to_string (state)); - return FALSE; - } -#endif // HAVE_LIBNICE_0_1_14 -*/ + // libnice docs say: "You must first call nice_agent_gather_candidates() + // before calling nice_agent_set_remote_candidates()". + // This is enforced one level up, by KmsWebrtcSession. nice_cand->stream_id = id; candidates = g_slist_append (NULL, nice_cand); diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index db96dc42b..28c7108bb 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -1298,193 +1298,6 @@ test_audio_video_sendrecv (const gchar * audio_enc_name, g_free (answerer_sess_id); } -#ifdef HAVE_LIBNICE_0_1_14 -/* -// This function is reduced to the minimum required to test the -// state change bug in libnice. Use it to test whenever a new -// libnice release fixes the issue. -static void -test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, - GstStaticCaps audio_expected_caps, gchar * audio_codec, - const gchar * video_enc_name, GstStaticCaps video_expected_caps, - gchar * video_codec, gboolean bundle) -{ - // TODO These lines are commented out to minimize the test case, - // once libnice bug gets fixed, remove this function - - GArray *audio_codecs_array, *video_codecs_array; - gchar *audio_codecs[] = { audio_codec, NULL }; - gchar *video_codecs[] = { video_codec, NULL }; -// HandOffData *hod; - GMainLoop *loop = g_main_loop_new (NULL, TRUE); - gchar *offerer_sess_id, *answerer_sess_id; - OnIceCandidateData offerer_cand_data, answerer_cand_data; - GstSDPMessage *offer, *answer; - GstElement *pipeline = gst_pipeline_new (NULL); - -// GstElement *videotestsrc_offerer = -// gst_element_factory_make ("videotestsrc", NULL); -// GstElement *videotestsrc_answerer = -// gst_element_factory_make ("videotestsrc", NULL); -// GstElement *video_enc_offerer = -// gst_element_factory_make (video_enc_name, NULL); -// GstElement *video_enc_answerer = -// gst_element_factory_make (video_enc_name, NULL); - - GstElement *offerer = gst_element_factory_make ("webrtcendpoint", NULL); - GstElement *answerer = gst_element_factory_make ("webrtcendpoint", NULL); - -// GstElement *video_fakesink_offerer = -// gst_element_factory_make ("fakesink", NULL); -// GstElement *video_fakesink_answerer = -// gst_element_factory_make ("fakesink", NULL); - - gchar *sdp_str = NULL; - gboolean ret; - gboolean answer_ok; - -// GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); - -// gst_bus_add_signal_watch (bus); -// g_signal_connect (bus, "message", G_CALLBACK (bus_msg), pipeline); - - audio_codecs_array = create_codecs_array (audio_codecs); - video_codecs_array = create_codecs_array (video_codecs); - - g_object_set (offerer, "num-audio-medias", 1, "audio-codecs", - g_array_ref (audio_codecs_array), "num-video-medias", 1, "video-codecs", - g_array_ref (video_codecs_array), "bundle", bundle, - "min-port", 50000, "max-port", 55000, NULL); - - // Answerer only supports video - g_object_set (answerer, "num-audio-medias", 0, "num-video-medias", 1, - "video-codecs", g_array_ref (video_codecs_array), "bundle", bundle, - "min-port", 50000, "max-port", 55000, NULL); - - g_array_unref (audio_codecs_array); - g_array_unref (video_codecs_array); - - // Session creation - g_signal_emit_by_name (offerer, "create-session", &offerer_sess_id); - GST_DEBUG_OBJECT (offerer, "Created session with id '%s'", offerer_sess_id); - g_signal_emit_by_name (answerer, "create-session", &answerer_sess_id); - GST_DEBUG_OBJECT (answerer, "Created session with id '%s'", answerer_sess_id); - - // Trickle ICE management - offerer_cand_data.peer = answerer; - offerer_cand_data.peer_sess_id = answerer_sess_id; - g_signal_connect (G_OBJECT (offerer), "on-ice-candidate", - G_CALLBACK (on_ice_candidate), &offerer_cand_data); - - answerer_cand_data.peer = offerer; - answerer_cand_data.peer_sess_id = offerer_sess_id; - g_signal_connect (G_OBJECT (answerer), "on-ice-candidate", - G_CALLBACK (on_ice_candidate), &answerer_cand_data); - -// hod = g_slice_new0 (HandOffData); -// hod->expected_caps = video_expected_caps; -// hod->loop = loop; - -// g_object_set (G_OBJECT (video_fakesink_offerer), "signal-handoffs", TRUE, -// NULL); -// g_signal_connect (G_OBJECT (video_fakesink_offerer), "handoff", -// G_CALLBACK (receiver_1_fakesink_hand_off), hod); -// g_object_set (G_OBJECT (video_fakesink_answerer), "signal-handoffs", TRUE, -// NULL); -// g_signal_connect (G_OBJECT (video_fakesink_answerer), "handoff", -// G_CALLBACK (receiver_2_fakesink_hand_off), hod); - - // Add elements - gst_bin_add (GST_BIN (pipeline), offerer); -// connect_sink_async (offerer, videotestsrc_offerer, video_enc_offerer, NULL, -// pipeline, SINK_VIDEO_STREAM); - - gst_bin_add (GST_BIN (pipeline), answerer); -// connect_sink_async (answerer, videotestsrc_answerer, video_enc_answerer, NULL, -// pipeline, SINK_VIDEO_STREAM); - - gst_element_set_state (pipeline, GST_STATE_PLAYING); - - // SDP negotiation - mark_point (); - g_signal_emit_by_name (offerer, "generate-offer", offerer_sess_id, &offer); - fail_unless (offer != NULL); - GST_DEBUG_OBJECT (offerer, "Offer:\n%s", (sdp_str = - gst_sdp_message_as_text (offer))); - g_free (sdp_str); - sdp_str = NULL; - - mark_point (); - g_signal_emit_by_name (answerer, "process-offer", answerer_sess_id, offer, - &answer); - fail_unless (answer != NULL); - GST_DEBUG_OBJECT (answerer, "Answer:\n%s", (sdp_str = - gst_sdp_message_as_text (answer))); - g_free (sdp_str); - sdp_str = NULL; - - mark_point (); - g_signal_emit_by_name (offerer, "process-answer", offerer_sess_id, answer, - &answer_ok); - fail_unless (answer_ok); - gst_sdp_message_free (offer); - gst_sdp_message_free (answer); - - GST_DEBUG_OBJECT (offerer, "============ Offerer gather candidates BEGIN"); - g_signal_emit_by_name (offerer, "gather-candidates", offerer_sess_id, &ret); - GST_DEBUG_OBJECT (offerer, "============ Offerer gather candidates END"); - fail_unless (ret); - - GST_DEBUG_OBJECT (answerer, "============ Answerer gather candidates BEGIN"); - g_signal_emit_by_name (answerer, "gather-candidates", answerer_sess_id, &ret); - GST_DEBUG_OBJECT (answerer, "============ Answerer gather candidates END"); - fail_unless (ret); - -// g_signal_connect (offerer, "pad-added", -// G_CALLBACK (connect_sink_on_srcpad_added), NULL); -// g_signal_connect (answerer, "pad-added", -// G_CALLBACK (connect_sink_on_srcpad_added), NULL); - -// fail_unless (kms_element_request_srcpad (offerer, -// KMS_ELEMENT_PAD_TYPE_AUDIO)); -// fail_unless (kms_element_request_srcpad (answerer, -// KMS_ELEMENT_PAD_TYPE_AUDIO)); - -// gst_bin_add_many (GST_BIN (pipeline), video_fakesink_offerer, -// video_fakesink_answerer, NULL); - -// g_object_set_qdata (G_OBJECT (offerer), video_sink_quark (), -// video_fakesink_offerer); -// fail_unless (kms_element_request_srcpad (offerer, -// KMS_ELEMENT_PAD_TYPE_VIDEO)); -// g_object_set_qdata (G_OBJECT (answerer), video_sink_quark (), -// video_fakesink_answerer); -// fail_unless (kms_element_request_srcpad (answerer, -// KMS_ELEMENT_PAD_TYPE_VIDEO)); - -// GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), -// GST_DEBUG_GRAPH_SHOW_ALL, "test_sendrecv_before_entering_loop"); - - mark_point (); - GST_INFO ("============ Main loop BEGIN"); - g_main_loop_run (loop); - GST_INFO ("============ Main loop END"); - mark_point (); - -// GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), -// GST_DEBUG_GRAPH_SHOW_ALL, "test_sendrecv_end"); - - gst_element_set_state (pipeline, GST_STATE_NULL); -// gst_bus_remove_signal_watch (bus); -// g_object_unref (bus); - g_object_unref (pipeline); - g_main_loop_unref (loop); -// g_slice_free (HandOffData, hod); - g_free (offerer_sess_id); - g_free (answerer_sess_id); -} -*/ -#else // HAVE_LIBNICE_0_1_14 static void test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, GstStaticCaps audio_expected_caps, gchar * audio_codec, @@ -1535,7 +1348,7 @@ test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, g_array_ref (video_codecs_array), "bundle", bundle, "min-port", 50000, "max-port", 55000, NULL); - /* Answerer only supports video */ + // Answerer only supports video g_object_set (answerer, "num-audio-medias", 0, "num-video-medias", 1, "video-codecs", g_array_ref (video_codecs_array), "bundle", bundle, "min-port", 50000, "max-port", 55000, NULL); @@ -1543,13 +1356,13 @@ test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, g_array_unref (audio_codecs_array); g_array_unref (video_codecs_array); - /* Session creation */ + // Session creation g_signal_emit_by_name (offerer, "create-session", &offerer_sess_id); GST_DEBUG_OBJECT (offerer, "Created session with id '%s'", offerer_sess_id); g_signal_emit_by_name (answerer, "create-session", &answerer_sess_id); GST_DEBUG_OBJECT (answerer, "Created session with id '%s'", answerer_sess_id); - /* Trickle ICE management */ + // Trickle ICE management offerer_cand_data.peer = answerer; offerer_cand_data.peer_sess_id = answerer_sess_id; g_signal_connect (G_OBJECT (offerer), "on-ice-candidate", @@ -1573,7 +1386,7 @@ test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, g_signal_connect (G_OBJECT (video_fakesink_answerer), "handoff", G_CALLBACK (receiver_2_fakesink_hand_off), hod); - /* Add elements */ + // Add elements gst_bin_add (GST_BIN (pipeline), offerer); connect_sink_async (offerer, videotestsrc_offerer, video_enc_offerer, NULL, pipeline, SINK_VIDEO_STREAM); @@ -1584,7 +1397,7 @@ test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, gst_element_set_state (pipeline, GST_STATE_PLAYING); - /* SDP negotiation */ + // SDP negotiation mark_point (); g_signal_emit_by_name (offerer, "generate-offer", offerer_sess_id, &offer); fail_unless (offer != NULL); @@ -1662,7 +1475,6 @@ test_offerer_audio_video_answerer_video_sendrecv (const gchar * audio_enc_name, g_free (offerer_sess_id); g_free (answerer_sess_id); } -#endif // HAVE_LIBNICE_0_1_14 #define TEST_MESSAGE "Hello world!" @@ -2090,7 +1902,6 @@ GST_START_TEST (test_pcmu_vp8_sendrecv) } GST_END_TEST -#ifndef HAVE_LIBNICE_0_1_14 GST_START_TEST (test_offerer_pcmu_vp8_answerer_vp8_sendrecv) { test_offerer_audio_video_answerer_video_sendrecv ("mulawenc", @@ -2101,7 +1912,6 @@ GST_START_TEST (test_offerer_pcmu_vp8_answerer_vp8_sendrecv) TRUE); } GST_END_TEST -#endif // HAVE_LIBNICE_0_1_14 GST_START_TEST (test_remb_params) { @@ -2423,11 +2233,7 @@ GST_START_TEST (test_not_enough_ports) g_signal_emit_by_name ( second_offerer, "gather-candidates", second_offerer_sess_id, &ret); -#ifdef HAVE_LIBNICE_0_1_14 fail_unless (ret); -#else - fail_if (ret); -#endif gst_sdp_message_free (offer); gst_sdp_message_free (second_offer); @@ -2673,36 +2479,22 @@ webrtcendpoint_test_suite (void) suite_add_tcase (s, tc_chain); - // FIXME fails with libnice 0.1.15 on trusty-dev (segmentation fault) - //tcase_add_test (tc_chain, test_pcmu_sendrecv); - //tcase_add_test (tc_chain, test_vp8_sendrecv_but_sendonly); - //tcase_add_test (tc_chain, test_vp8_sendonly_recvonly); - //tcase_add_test (tc_chain, test_vp8_sendonly_recvonly_rsa); - //tcase_add_test (tc_chain, test_vp8_sendonly_recvonly_ecdsa); - //tcase_add_test (tc_chain, test_vp8_sendrecv); - - /* FIXME Upstream libnice has a bug which keeps the NiceAgent state as - * 'DISCONNECTED', even when the ICE Gathering has been started. - * This test relies on that state being correctly updated, - * so for now, only our custom version of libnice does the job. - * See: https://lists.freedesktop.org/archives/nice/2017-September/001394.html - */ -#ifndef HAVE_LIBNICE_0_1_14 + tcase_add_test (tc_chain, test_pcmu_sendrecv); + tcase_add_test (tc_chain, test_vp8_sendrecv_but_sendonly); + tcase_add_test (tc_chain, test_vp8_sendonly_recvonly); + tcase_add_test (tc_chain, test_vp8_sendonly_recvonly_rsa); + tcase_add_test (tc_chain, test_vp8_sendonly_recvonly_ecdsa); + tcase_add_test (tc_chain, test_vp8_sendrecv); tcase_add_test (tc_chain, test_offerer_pcmu_vp8_answerer_vp8_sendrecv); -#endif // HAVE_LIBNICE_0_1_14 - - // FIXME fails with libnice 0.1.15 on trusty-dev (segmentation fault) - //tcase_add_test (tc_chain, test_pcmu_vp8_sendrecv); - //tcase_add_test (tc_chain, test_pcmu_vp8_sendonly_recvonly); + tcase_add_test (tc_chain, test_pcmu_vp8_sendrecv); + tcase_add_test (tc_chain, test_pcmu_vp8_sendonly_recvonly); tcase_add_test (tc_chain, test_remb_params); - tcase_add_test (tc_chain, test_session_creation); tcase_add_test (tc_chain, test_port_range); tcase_add_test (tc_chain, test_not_enough_ports); - // FIXME fails with libnice 0.1.13 on trusty (timeout) - //tcase_add_test (tc_chain, test_webrtc_data_channel); + tcase_add_test (tc_chain, test_webrtc_data_channel); tcase_add_test (tc_chain, process_mid_no_bundle_offer); tcase_add_test (tc_chain, set_network_interfaces_test); From 0745ce7943aec3f706d62163ca497759966201dd Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 16 Jun 2020 10:04:37 +0200 Subject: [PATCH 17/69] WebRtcEndpoint: rewrite docs for externalAddress, discourage using it Users were assuming that this setting was mandatory. Move it to the end of the settings file and explain better how this is a hack that should be used only under complete understanding of the feature. --- src/server/config/WebRtcEndpoint.conf.ini | 47 +++++++------- .../elements.WebRtcEndpoint.kmd.json | 61 +++++++++---------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index adb1c090a..e0a42d688 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -1,27 +1,3 @@ -;; External (public) IP address of the media server. -;; -;; If you know what will be the external or public IP address of the media server -;; (e.g. because your deployment has an static IP), you can specify it here. -;; Doing so has the advantage of not needing to configure STUN/TURN for the media -;; server. -;; -;; STUN/TURN are needed only when the media server sits behind a NAT and needs to -;; find out its own external IP address. However, if you set a static external IP -;; address with this parameter, then there is no need for the STUN/TURN -;; auto-discovery. -;; -;; The effect of this parameter is that ALL local ICE candidates that are -;; gathered (for WebRTC) will contain the provided external IP address instead of -;; the local one. -;; -;; is an IPv4 or IPv6 address. -;; -;; Examples: -;; externalAddress=10.20.30.40 -;; externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 -;; -;externalAddress=10.20.30.40 - ;; Local network interfaces used for ICE gathering. ;; ;; If you know which network interfaces should be used to perform ICE (for @@ -89,3 +65,26 @@ ;pemCertificate= ;pemCertificateRSA= ;pemCertificateECDSA= + +;; External IP address of the media server. +;; +;; This setting is normally NOT needed. Only use it if you know what you're +;; doing, and understand 100% WHY you want it. For the majority of cases, you +;; should prefer configuring STUN or TURN servers over using this setting. +;; +;; This setting implements a hack that will mangle all local ICE candidates so +;; that their candidate address is replaced with the provided external address, +;; even for candidates of type 'host'. In doing so, this KMS will not need a +;; STUN or TURN server, but remote peers will still be able to contact it. +;; +;; You can try using this setting if KMS is deployed on a publicly accessible +;; server, without NAT, and with a static public IP address. But if it doesn't +;; work for you, just go back to using the STUN or TURN settings above. +;; +;; is an IPv4 or IPv6 address. +;; +;; Examples: +;; externalAddress=10.20.30.40 +;; externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 +;; +;externalAddress=10.20.30.40 diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index cfa1bea78..d59f589f9 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -271,37 +271,6 @@ ", "properties": [ - { - "name": "externalAddress", - "doc": "External (public) IP address of the media server. -

- If you know what will be the external or public IP address of the media server - (e.g. because your deployment has an static IP), you can specify it here. - Doing so has the advantage of not needing to configure STUN/TURN for the media - server. -

-

- STUN/TURN are needed only when the media server sits behind a NAT and needs to - find out its own external IP address. However, if you set a static external IP - address with this parameter, then there is no need for the STUN/TURN - auto-discovery. -

-

- The effect of this parameter is that ALL local ICE candidates that are - gathered (for WebRTC) will contain the provided external IP address instead of - the local one. -

-

- externalAddress is an IPv4 or IPv6 address. -

-

Examples:

-
    -
  • externalAddress=10.70.35.2
  • -
  • externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • -
- ", - "type": "String" - }, { "name": "networkInterfaces", "doc": "Local network interfaces used for ICE gathering. @@ -388,6 +357,36 @@ ", "type": "String" }, + { + "name": "externalAddress", + "doc": "External IP address of the media server. +

+ This setting is normally NOT needed. Only use it if you know what you're + doing, and understand 100% WHY you want it. For the majority of cases, you + should prefer configuring STUN or TURN servers over using this setting. +

+

+ This setting implements a hack that will mangle all local ICE candidates so + that their candidate address is replaced with the provided external address, + even for candidates of type 'host'. In doing so, this KMS will not need a + STUN or TURN server, but remote peers will still be able to contact it. +

+

+ You can try using this setting if KMS is deployed on a publicly accessible + server, without NAT, and with a static public IP address. But if it doesn't + work for you, just go back to using the STUN or TURN settings above. +

+

+ externalAddress is an IPv4 or IPv6 address. +

+

Examples:

+
    +
  • externalAddress=10.70.35.2
  • +
  • externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • +
+ ", + "type": "String" + }, { "name": "ICECandidatePairs", "doc": "the ICE candidate pair (local and remote candidates) used by the ice library for each stream.", From f370d6b31b3206c3d9eedeb70236cf970b0d50fe Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 16 Jun 2020 10:06:04 +0200 Subject: [PATCH 18/69] WebRtcEndpoint: document the 'pemCertificate' settings It is possible to configure an external certificate to be used by DTLS. This is important because some browsers such as Firefox won't allow to establish multiple connections from the same tab to different peers that use different DTLS certificates. --- src/server/config/WebRtcEndpoint.conf.ini | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index e0a42d688..0a1b0768d 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -61,10 +61,24 @@ ;; ;turnURL=user:password@127.0.0.1:3478?transport=udp -;pemCertificate is deprecated. Please use pemCertificateRSA instead -;pemCertificate= -;pemCertificateRSA= -;pemCertificateECDSA= +;; Certificate used for DTLS authentication. +;; +;; If you want KMS to use a specific certificate for DTLS, then provide it here. +;; You can provide both RSA or ECDSA files; the choice between them is done when +;; calling the WebRtcEndpoint constructor. +;; +;; If this setting isn't specified, a different set of self-signed certificates +;; is generated automatically for each WebRtcEndpoint instance. +;; +;; This setting can be helpful, for example, for situations where you have to +;; manage multiple media servers and want to make sure that all of them use the +;; same certificate. Some browsers, such as Firefox, require this in order to +;; allow multiple WebRTC connections from the same tab to different KMS. +;; +;; Absolute path to the concatenated certificate (chain) file(s) + private key, +;; in PEM format. +;pemCertificateRSA=/path/to/cert+key.pem +;pemCertificateECDSA=/path/to/cert+key.pem ;; External IP address of the media server. ;; From d14364fbb2f8daaece8c5f740581e6a1c07e9f97 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 16 Jun 2020 17:09:41 +0200 Subject: [PATCH 19/69] All: use GST_STR_NULL instead of explicit NULL check for strings --- src/gst-plugins/kmsplayerendpoint.c | 6 +++--- src/gst-plugins/webrtcendpoint/kmsicecandidate.c | 2 +- tests/check/element/httpendpoint.c | 10 +++++----- tests/check/element/playerendpoint.c | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gst-plugins/kmsplayerendpoint.c b/src/gst-plugins/kmsplayerendpoint.c index c8ef22ac0..8473891aa 100644 --- a/src/gst-plugins/kmsplayerendpoint.c +++ b/src/gst-plugins/kmsplayerendpoint.c @@ -1334,11 +1334,11 @@ process_bus_message (GstBus * bus, GstMessage * msg, KmsPlayerEndpoint * self) GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, log_level, self, "Error code %d: '%s', element: %s, parent: %s", err_code, - (err_msg ? err_msg : "(None)"), GST_MESSAGE_SRC_NAME (msg), + GST_STR_NULL (err_msg), GST_MESSAGE_SRC_NAME (msg), GST_ELEMENT_NAME (parent)); - GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, log_level, self, - "Debugging info: %s", (dbg_info ? dbg_info : "(None)")); + GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, log_level, self, "Debugging info: %s", + GST_STR_NULL (dbg_info)); gchar *dot_name = g_strdup_printf ("%s_bus_%d", GST_OBJECT_NAME (self), err_code); diff --git a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c index f5549e3e1..02a2c1dd4 100644 --- a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c +++ b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c @@ -201,7 +201,7 @@ kms_ice_candidate_update_values (KmsIceCandidate * self) if (err) { GST_DEBUG_OBJECT (self, "Ignore foreign mDNS candidate: %s", - (err->message ? err->message : "")); + GST_STR_NULL (err->message)); g_clear_error (&err); goto end; } diff --git a/tests/check/element/httpendpoint.c b/tests/check/element/httpendpoint.c index 901a40796..cf41a1b07 100644 --- a/tests/check/element/httpendpoint.c +++ b/tests/check/element/httpendpoint.c @@ -49,9 +49,9 @@ bus_msg_cb (GstBus * bus, GstMessage * msg, gpointer pipeline) gchar *dbg_info = NULL; gst_message_parse_error (msg, &err, &dbg_info); - GST_ERROR ("Pipeline '%s': Bus error %d: %s", - GST_ELEMENT_NAME (pipeline), err->code, err->message); - GST_ERROR ("Debugging info: %s", (dbg_info) ? dbg_info : "None"); + GST_ERROR ("Pipeline '%s': Bus error %d: %s", GST_ELEMENT_NAME (pipeline), + err->code, GST_STR_NULL (err->message)); + GST_ERROR ("Debugging info: %s", GST_STR_NULL (dbg_info)); g_error_free (err); g_free (dbg_info); @@ -68,8 +68,8 @@ bus_msg_cb (GstBus * bus, GstMessage * msg, gpointer pipeline) gst_message_parse_error (msg, &err, &dbg_info); GST_WARNING ("Pipeline '%s': Bus warning %d: %s", - GST_ELEMENT_NAME (pipeline), err->code, err->message); - GST_WARNING ("Debugging info: %s", (dbg_info) ? dbg_info : "None"); + GST_ELEMENT_NAME (pipeline), err->code, GST_STR_NULL (err->message)); + GST_WARNING ("Debugging info: %s", GST_STR_NULL (dbg_info)); g_error_free (err); g_free (dbg_info); diff --git a/tests/check/element/playerendpoint.c b/tests/check/element/playerendpoint.c index e900ddc69..51071a3b0 100644 --- a/tests/check/element/playerendpoint.c +++ b/tests/check/element/playerendpoint.c @@ -101,9 +101,9 @@ bus_msg_cb (GstBus * bus, GstMessage * msg, gpointer pipeline) gchar *dbg_info = NULL; gst_message_parse_error (msg, &err, &dbg_info); - GST_ERROR ("Pipeline '%s': Bus error %d: %s", - GST_ELEMENT_NAME (pipeline), err->code, err->message); - GST_ERROR ("Debugging info: %s", (dbg_info) ? dbg_info : "None"); + GST_ERROR ("Pipeline '%s': Bus error %d: %s", GST_ELEMENT_NAME (pipeline), + err->code, GST_STR_NULL (err->message)); + GST_ERROR ("Debugging info: %s", GST_STR_NULL (dbg_info)); g_error_free (err); g_free (dbg_info); @@ -120,8 +120,8 @@ bus_msg_cb (GstBus * bus, GstMessage * msg, gpointer pipeline) gst_message_parse_error (msg, &err, &dbg_info); GST_WARNING ("Pipeline '%s': Bus warning %d: %s", - GST_ELEMENT_NAME (pipeline), err->code, err->message); - GST_WARNING ("Debugging info: %s", (dbg_info) ? dbg_info : "None"); + GST_ELEMENT_NAME (pipeline), err->code, GST_STR_NULL (err->message)); + GST_WARNING ("Debugging info: %s", GST_STR_NULL (dbg_info)); g_error_free (err); g_free (dbg_info); From a0dba8b229b74b748d5d0d613eb3a0adf825a01e Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 16 Jun 2020 17:48:04 +0200 Subject: [PATCH 20/69] Prepare release 6.14.0-0kurento1 --- debian/changelog | 7 +++---- src/server/interface/elements.kmd.json | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/debian/changelog b/debian/changelog index 6736e7211..daf781857 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,8 @@ -kms-elements (6.13.2-0kurento1) UNRELEASED; urgency=medium +kms-elements (6.14.0-0kurento1) testing; urgency=medium - * UNRELEASED - * UNRELEASED + * Prepare release 6.14.0-0kurento1 - -- Juan Navarro Wed, 22 Apr 2020 19:44:10 +0200 + -- Juan Navarro Tue, 16 Jun 2020 17:48:04 +0200 kms-elements (6.13.0-0kurento1) testing; urgency=medium diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 1638c4f71..5a1368213 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.13.2-dev", + "version": "6.14.0", "kurentoVersion": "^6.7.0", "code": { "kmd": { From 9bdb3cb5cfd84767ee033af36451994d1883b87e Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 18 Jun 2020 11:43:48 +0200 Subject: [PATCH 21/69] Prepare for next development iteration --- debian/changelog | 6 ++++++ src/server/interface/elements.kmd.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index daf781857..5e673f4e3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +kms-elements (6.14.1-0kurento1) UNRELEASED; urgency=medium + + * UNRELEASED + + -- Juan Navarro Thu, 18 Jun 2020 11:43:48 +0200 + kms-elements (6.14.0-0kurento1) testing; urgency=medium * Prepare release 6.14.0-0kurento1 diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 5a1368213..0cb0eb37a 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.14.0", + "version": "6.14.1-dev", "kurentoVersion": "^6.7.0", "code": { "kmd": { From 9230e5c22c1dc6a28940f10e549e372694c43821 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 26 Jun 2020 16:59:21 +0200 Subject: [PATCH 22/69] webrtcendpoint: improve safe TURN logging string Previous code would reuse the turn_url, after it had been parsed by the input regex. It is safer to rely on the parsing step and only handle well delimited fields. Also the code is much clearer this way. --- .../webrtcendpoint/kmswebrtcsession.c | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index a780513c9..ae068c653 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -1656,13 +1656,28 @@ kms_webrtc_session_parse_turn_url (KmsWebrtcSession * self) self->turn_user = g_match_info_fetch_named (match_info, "user"); self->turn_password = g_match_info_fetch_named (match_info, "password"); self->turn_address = g_match_info_fetch_named (match_info, "address"); - port_str = g_match_info_fetch_named (match_info, "port"); + turn_transport = g_match_info_fetch_named (match_info, "transport"); + + // Build a safe string that can be printed out in the log + GString *safe_log = g_string_new (""); + if (self->turn_address) { + g_string_append_c (safe_log, '@'); + g_string_append (safe_log, self->turn_address); + } + if (port_str) { + g_string_append_c (safe_log, ':'); + g_string_append (safe_log, port_str); + } + if (turn_transport) { + g_string_append_c (safe_log, '?'); + g_string_append (safe_log, turn_transport); + } + self->turn_port = g_ascii_strtoll (port_str, NULL, 10); g_free (port_str); - self->turn_transport = TURN_PROTOCOL_UDP; /* default */ - turn_transport = g_match_info_fetch_named (match_info, "transport"); + self->turn_transport = TURN_PROTOCOL_UDP; /* default */ if (turn_transport != NULL) { if (g_strcmp0 ("tcp", turn_transport) == 0) { self->turn_transport = TURN_PROTOCOL_TCP; @@ -1672,16 +1687,8 @@ kms_webrtc_session_parse_turn_url (KmsWebrtcSession * self) g_free (turn_transport); } - GString *safe_url = g_string_new (""); - gchar *separated_url = g_strrstr (self->turn_url, "@"); - if (separated_url == NULL) { - g_string_append_c (safe_url, '@'); - g_string_append (safe_url, self->turn_url); - } else { - g_string_append (safe_url, separated_url); - } - GST_DEBUG_OBJECT (self, "TURN server info set: %s", safe_url->str); - g_string_free (safe_url, TRUE); + GST_DEBUG_OBJECT (self, "TURN server info set: %s", safe_log->str); + g_string_free (safe_log, TRUE); } else { GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, ("URL '%s' not allowed. It must have this format: 'user:password@address:port(?transport=[udp|tcp|tls])'", From 741b5f1c15765c9e010c95afdcca4caca54c150f Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 23 Jul 2020 17:54:54 +0200 Subject: [PATCH 23/69] recorder: change docs HTTP PUT -> POST & improve error messages --- src/gst-plugins/kmsplayerendpoint.c | 4 +- .../recorderendpoint/kmsbasemediamuxer.c | 12 ++-- .../recorderendpoint/kmsrecorderendpoint.c | 65 ++++++++++--------- .../elements.RecorderEndpoint.kmd.json | 12 ++-- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/gst-plugins/kmsplayerendpoint.c b/src/gst-plugins/kmsplayerendpoint.c index 8473891aa..9cc195dc8 100644 --- a/src/gst-plugins/kmsplayerendpoint.c +++ b/src/gst-plugins/kmsplayerendpoint.c @@ -900,12 +900,12 @@ kms_player_endpoint_uridecodebin_pad_removed (GstElement * element, appsink = g_object_steal_qdata (G_OBJECT (pad), appsink_quark ()); appsrc = g_object_steal_qdata (G_OBJECT (pad), appsrc_quark ()); - // remove appsrc before appsink to avoid segment fault + // remove appsrc before appsink to avoid segment fault // caused by invalid appsink in appsrc_query_probe if (appsrc != NULL) { kms_utils_bin_remove (GST_BIN (self), appsrc); } - + if (appsink != NULL) { kms_utils_bin_remove (GST_BIN (self->priv->pipeline), appsink); } diff --git a/src/gst-plugins/recorderendpoint/kmsbasemediamuxer.c b/src/gst-plugins/recorderendpoint/kmsbasemediamuxer.c index a84cc0821..29b918cae 100644 --- a/src/gst-plugins/recorderendpoint/kmsbasemediamuxer.c +++ b/src/gst-plugins/recorderendpoint/kmsbasemediamuxer.c @@ -68,7 +68,7 @@ kms_base_media_muxer_finalize (GObject * object) { KmsBaseMediaMuxer *self = KMS_BASE_MEDIA_MUXER (object); - GST_DEBUG_OBJECT (self, "finalize"); + GST_LOG_OBJECT (self, "finalize"); gst_element_set_state (KMS_BASE_MEDIA_MUXER_GET_PIPELINE (self), GST_STATE_NULL); @@ -134,7 +134,7 @@ kms_base_media_muxer_get_sink_fallback (KmsBaseMediaMuxer * self, || (g_strcmp0 (prot, HTTPS_PROTO) == 0)) { if (kms_is_valid_uri (uri)) { - /* We use souphttpclientsink */ + /* We use the GStreamer CURL plugin */ sink = gst_element_factory_make ("curlhttpsink", NULL); if (sink != NULL) { g_object_set (sink, "blocksize", MEGA_BYTES (1), "qos", FALSE, @@ -182,6 +182,8 @@ kms_base_media_muxer_get_sink (KmsBaseMediaMuxer * self, const gchar * uri) g_clear_error (&err); } + GST_DEBUG_OBJECT (sink, "Muxer sink created for URI '%s'", uri); + /* Try to configure the sink element */ sink_class = G_OBJECT_GET_CLASS (sink); @@ -192,11 +194,9 @@ kms_base_media_muxer_get_sink (KmsBaseMediaMuxer * self, const gchar * uri) /* Work around for filesink elements */ gchar *location = gst_uri_get_location (uri); - GST_DEBUG_OBJECT (sink, "filesink location=%s", location); g_object_set (sink, "location", location, NULL); g_free (location); } else { - GST_DEBUG_OBJECT (sink, "configuring location=%s", uri); g_object_set (sink, "location", uri, NULL); } } @@ -210,7 +210,7 @@ kms_base_media_muxer_get_sink (KmsBaseMediaMuxer * self, const gchar * uri) } invalid_uri: { - GST_ERROR_OBJECT (self, "Invalid URI \"%s\".", uri); + GST_ERROR_OBJECT (self, "Invalid URI: '%s'", uri); g_clear_error (&err); goto end; } @@ -225,7 +225,7 @@ kms_base_media_muxer_get_sink (KmsBaseMediaMuxer * self, const gchar * uri) if (prot == NULL) goto invalid_uri; - GST_ERROR_OBJECT (self, "No URI handler implemented for \"%s\".", prot); + GST_ERROR_OBJECT (self, "No URI handler implemented for '%s'", prot); g_free (prot); } else { diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index 61475ba8c..42c4ecc69 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -1958,52 +1958,59 @@ kms_recorder_endpoint_on_eos_message (gpointer data) } static GstBusSyncReply -bus_sync_signal_handler (GstBus * bus, GstMessage * msg, gpointer data) +bus_sync_signal_handler (GstBus *bus, GstMessage *msg, gpointer data) { KmsRecorderEndpoint *self = KMS_RECORDER_ENDPOINT (data); - if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { - ErrorData *data; - - GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self), - GST_DEBUG_GRAPH_SHOW_ALL, GST_ELEMENT_NAME (self)); + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + case GST_MESSAGE_WARNING: { + ErrorData *data = create_error_data (self, msg); + gst_task_pool_push ( + self->priv->pool, kms_recorder_endpoint_post_error, data, NULL); + + // Generate a DOT file from the element's internal pipeline. + gchar *dot_name = g_strdup_printf ("%s_bus_msg", GST_OBJECT_NAME (self)); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS ( + GST_BIN (self), GST_DEBUG_GRAPH_SHOW_ALL, dot_name); + g_free (dot_name); kms_base_media_muxer_dot_file (self->priv->mux); - GST_ERROR_OBJECT (self, "Message %" GST_PTR_FORMAT, msg); - - data = create_error_data (self, msg); - - GST_ERROR_OBJECT (self, "Error: %" GST_PTR_FORMAT, msg); - - gst_task_pool_push (self->priv->pool, kms_recorder_endpoint_post_error, - data, NULL); - } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { - gst_task_pool_push (self->priv->pool, kms_recorder_endpoint_on_eos_message, - self, NULL); - } else if ((GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STATE_CHANGED) - && (GST_OBJECT_CAST (KMS_BASE_MEDIA_MUXER_GET_PIPELINE (self-> - priv->mux)) == GST_MESSAGE_SRC (msg))) { - GstState new_state, pending; + break; + } + case GST_MESSAGE_EOS: + gst_task_pool_push ( + self->priv->pool, kms_recorder_endpoint_on_eos_message, self, NULL); + break; + case GST_MESSAGE_STATE_CHANGED: + if (GST_OBJECT_CAST (KMS_BASE_MEDIA_MUXER_GET_PIPELINE (self->priv->mux)) + == GST_MESSAGE_SRC (msg)) { + GstState new_state, pending; - gst_message_parse_state_changed (msg, NULL, &new_state, &pending); + gst_message_parse_state_changed (msg, NULL, &new_state, &pending); - if (pending == GST_STATE_VOID_PENDING || (pending == GST_STATE_NULL - && new_state == GST_STATE_READY)) { - switch (new_state) { + if (pending == GST_STATE_VOID_PENDING + || (pending == GST_STATE_NULL && new_state == GST_STATE_READY)) { + switch (new_state) { case GST_STATE_PLAYING: - kms_recorder_endpoint_async_state_changed (self, - KMS_URI_ENDPOINT_STATE_START); + kms_recorder_endpoint_async_state_changed ( + self, KMS_URI_ENDPOINT_STATE_START); break; case GST_STATE_READY: - kms_recorder_endpoint_async_state_changed (self, - KMS_URI_ENDPOINT_STATE_STOP); + kms_recorder_endpoint_async_state_changed ( + self, KMS_URI_ENDPOINT_STATE_STOP); break; default: GST_DEBUG_OBJECT (self, "Not raising event"); break; + } } } + break; + default: + break; } + return GST_BUS_PASS; } diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index ff1ac5d80..8a636bccc 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -3,10 +3,10 @@ { "name": "RecorderEndpoint", "extends": "UriEndpoint", - "doc": "Provides the functionality to store contents. + "doc": "Provides functionality to store media contents.

- The Recorder can store media in local files or in a network resource. It - receives a media stream from another :rom:cls:`MediaElement` (i.e. the + The RecorderEndpoint can store media in local files or in a network resource. + It receives a media stream from another :rom:cls:`MediaElement` (i.e. the source), and stores it in the designated location.

@@ -25,7 +25,9 @@

  • - HTTP: Method PUT used against a remote server. + HTTP: A POST request will be used against a remote server. The server + must support using the chunked encoding mode (HTTP header + Transfer-Encoding: chunked).
    • http(s)://{server-ip}/path/to/file
    • @@ -72,7 +74,7 @@
    • To save into an HTTP server, the server must be accessible through the - network, and also have the correct access rights. + network, and also have the correct access rights to the destination path.

    From f37243c85621c20df687c20dae5cc5bad947f5d8 Mon Sep 17 00:00:00 2001 From: Thomas Mullen <14932492+t-mullen@users.noreply.github.com> Date: Mon, 3 Aug 2020 09:34:29 -0400 Subject: [PATCH 24/69] Fix incorrect cropping in AlphaBlending element (#24) What is the current behavior you want to change? When the relative X/Y is greater than zero but less than the relative width/height, the video is cropped incorrectly. Fixes Kurento/bugtracker#63. ie) Given setPortProperties(0.1, 0.1, 0, 0.5, 0.5, port) results in the video being positioned at 0.1, 0.1 but only a small slice of the video is showing, the rest is cropped. What is the new behavior provided by this change? Removing this cropping produces the expected behaviour with correct relative X, Y, width, height. ie) Given setPortProperties(0.1, 0.1, 0, 0.5, 0.5, port) results in the video being positioned at 0.1, 0.1 with the entire video showing, correctly scaled to 50% of the master port size. How has this been tested? Tested in Ubuntu 18 with both latest KMS source and as a module in KMS 6.14.1 installed through apt. --- src/gst-plugins/kmsalphablending.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/gst-plugins/kmsalphablending.c b/src/gst-plugins/kmsalphablending.c index 434018707..45c1fb6d7 100644 --- a/src/gst-plugins/kmsalphablending.c +++ b/src/gst-plugins/kmsalphablending.c @@ -204,28 +204,14 @@ configure_port (KmsAlphaBlendingData * port_data) int top = 0; int bottom = 0; - if (_relative_x > 0) { - int width_size = - (mixer->priv->output_width - (mixer->priv->output_width - - _relative_x)); - if (_relative_width > width_size) { - right = (_relative_width - width_size); - } - } else { + if (_relative_x <= 0) { left = -1 * _relative_x; if ((_relative_width - left) > mixer->priv->output_width) { right = (_relative_width - left) - mixer->priv->output_width; } } - if (_relative_y > 0) { - int height_size = - (mixer->priv->output_height - (mixer->priv->output_height - - _relative_y)); - if (_relative_height > height_size) { - bottom = (_relative_height - height_size); - } - } else { + if (_relative_y <= 0) { top = -1 * _relative_y; if ((_relative_height - top) > mixer->priv->output_height) { bottom = (_relative_height - top) - mixer->priv->output_height; From 17e0182619f7094f1ca49415db6b58eea56a37dd Mon Sep 17 00:00:00 2001 From: prlanzarin Date: Thu, 10 Sep 2020 21:06:14 +0000 Subject: [PATCH 25/69] Add externalIPv4/externalIPv6 configs and API methods for them This setting follows the same rationale of the externalAddress setting, but the idea here is to make it dual stack friendly by splitting it into IPv4 and IPv6 specific configs and mangling compatible candidates with them For the sake of backwards compatibility, externalAddress has preference over these two. So when it`s set, they will be ignored --- .../webrtcendpoint/kmswebrtcendpoint.c | 44 ++++++++++- .../webrtcendpoint/kmswebrtcsession.c | 47 +++++++++++- .../webrtcendpoint/kmswebrtcsession.h | 2 + src/server/config/WebRtcEndpoint.conf.ini | 28 +++++++ .../objects/WebRtcEndpointImpl.cpp | 74 +++++++++++++++++++ .../objects/WebRtcEndpointImpl.hpp | 6 ++ .../elements.WebRtcEndpoint.kmd.json | 10 +++ 7 files changed, 209 insertions(+), 2 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index 0f8a0fa3b..f6a559040 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -56,6 +56,8 @@ G_DEFINE_TYPE (KmsWebrtcEndpoint, kms_webrtc_endpoint, #define DEFAULT_PEM_CERTIFICATE NULL #define DEFAULT_NETWORK_INTERFACES NULL #define DEFAULT_EXTERNAL_ADDRESS NULL +#define DEFAULT_EXTERNAL_IPV4 NULL +#define DEFAULT_EXTERNAL_IPV6 NULL enum { @@ -66,6 +68,8 @@ enum PROP_PEM_CERTIFICATE, PROP_NETWORK_INTERFACES, PROP_EXTERNAL_ADDRESS, + PROP_EXTERNAL_IPV4, + PROP_EXTERNAL_IPV6, N_PROPERTIES }; @@ -99,6 +103,8 @@ struct _KmsWebrtcEndpointPrivate gchar *pem_certificate; gchar *network_interfaces; gchar *external_address; + gchar *external_ipv4; + gchar *external_ipv6; }; /* Internal session management begin */ @@ -329,13 +335,19 @@ kms_webrtc_endpoint_create_session_internal (KmsBaseSdpEndpoint * base_sdp, webrtc_sess, "network-interfaces", G_BINDING_DEFAULT); g_object_bind_property (self, "external-address", webrtc_sess, "external-address", G_BINDING_DEFAULT); + g_object_bind_property (self, "external-ipv4", + webrtc_sess, "external-ipv4", G_BINDING_DEFAULT); + g_object_bind_property (self, "external-ipv6", + webrtc_sess, "external-ipv6", G_BINDING_DEFAULT); g_object_set (webrtc_sess, "stun-server", self->priv->stun_server_ip, "stun-server-port", self->priv->stun_server_port, "turn-url", self->priv->turn_url, "pem-certificate", self->priv->pem_certificate, "network-interfaces", self->priv->network_interfaces, - "external-address", self->priv->external_address, NULL); + "external-address", self->priv->external_address, + "external-ipv4", self->priv->external_ipv4, + "external-ipv6", self->priv->external_ipv6, NULL); g_signal_connect (webrtc_sess, "on-ice-candidate", G_CALLBACK (on_ice_candidate), self); @@ -511,6 +523,14 @@ kms_webrtc_endpoint_set_property (GObject * object, guint prop_id, g_free (self->priv->external_address); self->priv->external_address = g_value_dup_string (value); break; + case PROP_EXTERNAL_IPV4: + g_free (self->priv->external_ipv4); + self->priv->external_ipv4 = g_value_dup_string (value); + break; + case PROP_EXTERNAL_IPV6: + g_free (self->priv->external_ipv6); + self->priv->external_ipv6 = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -546,6 +566,12 @@ kms_webrtc_endpoint_get_property (GObject * object, guint prop_id, case PROP_EXTERNAL_ADDRESS: g_value_set_string (value, self->priv->external_address); break; + case PROP_EXTERNAL_IPV4: + g_value_set_string (value, self->priv->external_ipv4); + break; + case PROP_EXTERNAL_IPV6: + g_value_set_string (value, self->priv->external_ipv6); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -583,6 +609,8 @@ kms_webrtc_endpoint_finalize (GObject * object) g_free (self->priv->pem_certificate); g_free (self->priv->network_interfaces); g_free (self->priv->external_address); + g_free (self->priv->external_ipv4); + g_free (self->priv->external_ipv6); g_main_context_unref (self->priv->context); @@ -771,6 +799,18 @@ kms_webrtc_endpoint_class_init (KmsWebrtcEndpointClass * klass) "External (public) IP address of the media server", DEFAULT_EXTERNAL_ADDRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXTERNAL_IPV4, + g_param_spec_string ("external-ipv4", + "externalIPv4", + "External (public) IPv4 address of the media server", + DEFAULT_EXTERNAL_IPV4, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_EXTERNAL_IPV6, + g_param_spec_string ("external-ipv6", + "externalIPv6", + "External (public) IPv6 address of the media server", + DEFAULT_EXTERNAL_IPV6, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * KmsWebrtcEndpoint::on-ice-candidate: * @self: the object which received the signal @@ -905,6 +945,8 @@ kms_webrtc_endpoint_init (KmsWebrtcEndpoint * self) self->priv->pem_certificate = DEFAULT_PEM_CERTIFICATE; self->priv->network_interfaces = DEFAULT_NETWORK_INTERFACES; self->priv->external_address = DEFAULT_EXTERNAL_ADDRESS; + self->priv->external_ipv4 = DEFAULT_EXTERNAL_IPV4; + self->priv->external_ipv6 = DEFAULT_EXTERNAL_IPV6; self->priv->loop = kms_loop_new (); g_object_get (self->priv->loop, "context", &self->priv->context, NULL); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index ae068c653..8d7649a17 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -56,6 +56,8 @@ G_DEFINE_TYPE (KmsWebrtcSession, kms_webrtc_session, KMS_TYPE_BASE_RTP_SESSION); #define DEFAULT_PEM_CERTIFICATE NULL #define DEFAULT_NETWORK_INTERFACES NULL #define DEFAULT_EXTERNAL_ADDRESS NULL +#define DEFAULT_EXTERNAL_IPV4 NULL +#define DEFAULT_EXTERNAL_IPV6 NULL #define IP_VERSION_6 6 @@ -90,6 +92,8 @@ enum PROP_PEM_CERTIFICATE, PROP_NETWORK_INTERFACES, PROP_EXTERNAL_ADDRESS, + PROP_EXTERNAL_IPV4, + PROP_EXTERNAL_IPV6, N_PROPERTIES }; @@ -583,12 +587,23 @@ kms_webrtc_session_new_candidate (KmsIceBaseAgent * agent, kms_ice_candidate_get_stream_id (candidate), kms_ice_candidate_get_component (candidate)); + gboolean is_candidate_ipv6 = kms_ice_candidate_get_ip_version (candidate) == IP_VERSION_6; + if (self->external_address != NULL) { kms_ice_candidate_set_address (candidate, self->external_address); - GST_DEBUG_OBJECT (self, "[IceCandidateFound] Mangled local: '%s'", kms_ice_candidate_get_candidate (candidate)); + } else if (self->external_ipv4 != NULL && is_candidate_ipv6 == FALSE) { + kms_ice_candidate_set_address (candidate, self->external_ipv4); + GST_DEBUG_OBJECT (self, + "[IceCandidateFound] Mangled local candidate with IPv4: '%s'", + kms_ice_candidate_get_candidate (candidate)); + } else if (self->external_ipv6 != NULL && is_candidate_ipv6 == TRUE) { + kms_ice_candidate_set_address (candidate, self->external_ipv6); + GST_DEBUG_OBJECT (self, + "[IceCandidateFound] Mangled local candidate with IPv6: '%s'", + kms_ice_candidate_get_candidate (candidate)); } kms_webrtc_session_sdp_msg_add_ice_candidate (self, candidate); @@ -1733,6 +1748,14 @@ kms_webrtc_session_set_property (GObject * object, guint prop_id, g_free (self->external_address); self->external_address = g_value_dup_string (value); break; + case PROP_EXTERNAL_IPV4: + g_free (self->external_ipv4); + self->external_ipv4 = g_value_dup_string (value); + break; + case PROP_EXTERNAL_IPV6: + g_free (self->external_ipv6); + self->external_ipv6 = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1771,6 +1794,12 @@ kms_webrtc_session_get_property (GObject * object, guint prop_id, case PROP_EXTERNAL_ADDRESS: g_value_set_string (value, self->external_address); break; + case PROP_EXTERNAL_IPV4: + g_value_set_string (value, self->external_ipv4); + break; + case PROP_EXTERNAL_IPV6: + g_value_set_string (value, self->external_ipv6); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1798,6 +1827,8 @@ kms_webrtc_session_finalize (GObject * object) g_free (self->pem_certificate); g_free (self->network_interfaces); g_free (self->external_address); + g_free (self->external_ipv4); + g_free (self->external_ipv6); if (self->destroy_data != NULL && self->cb_data != NULL) { self->destroy_data (self->cb_data); @@ -1907,6 +1938,8 @@ kms_webrtc_session_init (KmsWebrtcSession * self) self->pem_certificate = DEFAULT_PEM_CERTIFICATE; self->network_interfaces = DEFAULT_NETWORK_INTERFACES; self->external_address = DEFAULT_EXTERNAL_ADDRESS; + self->external_ipv4= DEFAULT_EXTERNAL_IPV4; + self->external_ipv6 = DEFAULT_EXTERNAL_IPV6; self->gather_started = FALSE; self->data_channels = g_hash_table_new_full (g_direct_hash, @@ -2012,6 +2045,18 @@ kms_webrtc_session_class_init (KmsWebrtcSessionClass * klass) "External (public) IP address of the media server", DEFAULT_EXTERNAL_ADDRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_EXTERNAL_IPV4, + g_param_spec_string ("external-ipv4", + "externalIPv4", + "External (public) IPv4 address of the media server", + DEFAULT_EXTERNAL_IPV4, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_EXTERNAL_IPV6, + g_param_spec_string ("external-ipv6", + "externalIPv6", + "External (public) IPv6 address of the media server", + DEFAULT_EXTERNAL_IPV6, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DATA_CHANNEL_SUPPORTED, g_param_spec_boolean ("data-channel-supported", "Data channel supported", diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h index 294c2b1b6..7575f860b 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h @@ -72,6 +72,8 @@ struct _KmsWebrtcSession gchar *pem_certificate; gchar *network_interfaces; gchar *external_address; + gchar *external_ipv4; + gchar *external_ipv6; guint16 min_port; guint16 max_port; diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index 0a1b0768d..7c974984e 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -102,3 +102,31 @@ ;; externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 ;; ;externalAddress=10.20.30.40 + +;; External IPv4 address of the media server. +;; +;; This setting follows the same rationale of the externalAddress setting, but +;; the idea here is to couple it with the externalIPv6 setting to achieve dual +;; stack IPv4+IPv6 candidate mangling. +;; +;; Right now, externalAddress only allows a single IP (which has to be either +;; IPv4 or IPv6), so it's not dual stack friendly. This option will mangle +;; IPv4 candidates with the specified one. +;; +;; For the sake of backwards compatibility, externalAddress has preference over +;; this: when externalAddress is set, this option will be ignored. +;; +;; is an IPv4 address. +;; +; externalIPv4=10.20.30.40 + +;; External IPv6 address of the media server. +;; +;; Refer to the detailing of externalIPv4 which should explain this as well. +;; +;; For the sake of backwards compatibility, externalAddress has preference over +;; this: when externalAddress is set, this option will be ignored. +;; +;; is an IPv6 address. +;; +; externalIPv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334 diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index e1b13a32f..7cd29fa57 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -53,9 +53,13 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define DEFAULT_PATH "/etc/kurento" #define PARAM_EXTERNAL_ADDRESS "externalAddress" +#define PARAM_EXTERNAL_IPV4 "externalIPv4" +#define PARAM_EXTERNAL_IPV6 "externalIPv6" #define PARAM_NETWORK_INTERFACES "networkInterfaces" #define PROP_EXTERNAL_ADDRESS "external-address" +#define PROP_EXTERNAL_IPV4 "external-ipv4" +#define PROP_EXTERNAL_IPV6 "external-ipv6" #define PROP_NETWORK_INTERFACES "network-interfaces" namespace kurento @@ -510,6 +514,28 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, //set properties + std::string externalIPv4; + if (getConfigValue (&externalIPv4, + PARAM_EXTERNAL_IPV4)) { + GST_INFO ("Predefined external IPv4 address: %s", externalIPv4.c_str()); + g_object_set (G_OBJECT (element), PROP_EXTERNAL_IPV4, + externalIPv4.c_str(), NULL); + } else { + GST_DEBUG ("No predefined external IPv4 address found in config;" + " you can set one or default to STUN automatic discovery"); + } + + std::string externalIPv6; + if (getConfigValue (&externalIPv6, + PARAM_EXTERNAL_IPV6)) { + GST_INFO ("Predefined external IPv6 address: %s", externalIPv6.c_str()); + g_object_set (G_OBJECT (element), PROP_EXTERNAL_IPV6, + externalIPv6.c_str(), NULL); + } else { + GST_DEBUG ("No predefined external IPv6 address found in config;" + " you can set one or default to STUN automatic discovery"); + } + std::string externalAddress; if (getConfigValue (&externalAddress, PARAM_EXTERNAL_ADDRESS)) { @@ -624,6 +650,54 @@ WebRtcEndpointImpl::~WebRtcEndpointImpl() } } +std::string +WebRtcEndpointImpl::getExternalIPv4 () +{ + std::string externalIPv4; + gchar *ret; + + g_object_get (G_OBJECT (element), PROP_EXTERNAL_IPV4, &ret, NULL); + + if (ret != nullptr) { + externalIPv4 = std::string (ret); + g_free (ret); + } + + return externalIPv4; +} + +void +WebRtcEndpointImpl::setExternalIPv4 (const std::string &externalIPv4) +{ + GST_INFO ("Set external IPv4 address: %s", externalIPv4.c_str()); + g_object_set (G_OBJECT (element), PROP_EXTERNAL_IPV4, + externalIPv4.c_str(), NULL); +} + +std::string +WebRtcEndpointImpl::getExternalIPv6 () +{ + std::string externalIPv6; + gchar *ret; + + g_object_get (G_OBJECT (element), PROP_EXTERNAL_IPV6, &ret, NULL); + + if (ret != nullptr) { + externalIPv6 = std::string (ret); + g_free (ret); + } + + return externalIPv6; +} + +void +WebRtcEndpointImpl::setExternalIPv6 (const std::string &externalIPv6) +{ + GST_INFO ("Set external IPv6 address: %s", externalIPv6.c_str()); + g_object_set (G_OBJECT (element), PROP_EXTERNAL_IPV6, + externalIPv6.c_str(), NULL); +} + std::string WebRtcEndpointImpl::getExternalAddress () { diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.hpp b/src/server/implementation/objects/WebRtcEndpointImpl.hpp index d651d1725..b3f0586b7 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.hpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.hpp @@ -46,6 +46,12 @@ class WebRtcEndpointImpl : public BaseRtpEndpointImpl, ~WebRtcEndpointImpl () override; + std::string getExternalIPv6 () override; + void setExternalIPv6 (const std::string &externalIPv6) override; + + std::string getExternalIPv4() override; + void setExternalIPv4 (const std::string &externalIPv4) override; + std::string getExternalAddress () override; void setExternalAddress (const std::string &externalAddress) override; diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index d59f589f9..be017c8a2 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -357,6 +357,16 @@ ", "type": "String" }, + { + "name": "externalIPv4", + "doc": "External IPv4 of the media server", + "type": "String" + }, + { + "name": "externalIPv6", + "doc": "External IPv6 of the media server", + "type": "String" + }, { "name": "externalAddress", "doc": "External IP address of the media server. From 6c34c1ecda6996f2cab977d3c21e727f0c748587 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 11 Sep 2020 14:40:15 +0200 Subject: [PATCH 26/69] webrtcendpoint: tests for externalIPv4, externalIPv6 --- tests/check/element/webrtcendpoint.c | 134 +++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 7 deletions(-) diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index 28c7108bb..d4d4dbc81 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -2357,9 +2358,12 @@ static void on_ice_candidate_check_ip (GstElement * self, gchar * sess_id, KmsIceCandidate * candidate, const gchar * expected_ip) { - gchar *candidate_ip = kms_ice_candidate_get_address (candidate); - assert_equals_string (candidate_ip, expected_ip); - g_free (candidate_ip); + if (kms_ice_candidate_get_ip_version (candidate) + == kms_utils_get_ip_version (expected_ip)) { + gchar *candidate_ip = kms_ice_candidate_get_address (candidate); + assert_equals_string (candidate_ip, expected_ip); + g_free (candidate_ip); + } } /** @@ -2417,7 +2421,7 @@ GST_START_TEST (set_network_interfaces_test) GST_END_TEST /** - * Test setting local network interface to limit ICE candidate gathering. + * Test mangling of ICE candidates to set a custom IP address. */ GST_START_TEST (set_external_address_test) { @@ -2430,9 +2434,10 @@ GST_START_TEST (set_external_address_test) GstSDPMessage *offer = NULL, *answer = NULL; gboolean ret; - // Check that candidates only include the localhost IP - g_object_set (webrtcendpoint, "external-address", "10.20.30.40", NULL); + g_object_set (webrtcendpoint, "external-address", "198.51.100.1", NULL); + // No "ice-ufrag" and "ice-pwd" will cause a warning message "Cannot set + // remote media credentials", which can be ignored. static const gchar *offer_str = "v=0\r\n" "o=mozilla...THIS_IS_SDPARTA-43.0 4115481872190049086 0 IN IP4 0.0.0.0\r\n" "a=ice-options:trickle\r\n" @@ -2453,7 +2458,7 @@ GST_START_TEST (set_external_address_test) g_array_unref (video_codecs_array); g_signal_connect (G_OBJECT (webrtcendpoint), "on-ice-candidate", - G_CALLBACK (on_ice_candidate_check_ip), "10.20.30.40"); + G_CALLBACK (on_ice_candidate_check_ip), "198.51.100.1"); fail_unless (gst_sdp_message_new (&offer) == GST_SDP_OK); fail_unless (gst_sdp_message_parse_buffer ((const guint8 *) @@ -2468,6 +2473,118 @@ GST_START_TEST (set_external_address_test) } GST_END_TEST +/** + * Test mangling of ICE candidates to set a custom IPv4 address. + */ +GST_START_TEST (set_external_ipv4_test) +{ + GArray *audio_codecs_array, *video_codecs_array; + gchar *audio_codecs[] = {"opus/48000/1", NULL}; + gchar *video_codecs[] = {"VP8/90000", NULL}; + GstElement *webrtcendpoint = + gst_element_factory_make ("webrtcendpoint", NULL); + gchar *sess_id; + GstSDPMessage *offer = NULL, *answer = NULL; + gboolean ret; + + g_object_set (webrtcendpoint, "external-ipv4", "198.51.100.1", NULL); + + // No "ice-ufrag" and "ice-pwd" will cause a warning message "Cannot set + // remote media credentials", which can be ignored. + static const gchar *offer_str = + "v=0\r\n" + "o=mozilla...THIS_IS_SDPARTA-43.0 4115481872190049086 0 IN IP4 0.0.0.0\r\n" + "a=ice-options:trickle\r\n" + "a=msid-semantic:WMS *\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 120\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=sendrecv\r\n" + "a=mid:sdparta_0\r\n" + "a=rtpmap:120 VP8/90000\r\n"; + + audio_codecs_array = create_codecs_array (audio_codecs); + video_codecs_array = create_codecs_array (video_codecs); + g_object_set (webrtcendpoint, "num-audio-medias", 1, "audio-codecs", + g_array_ref (audio_codecs_array), "num-video-medias", 1, "video-codecs", + g_array_ref (video_codecs_array), NULL); + + g_array_unref (audio_codecs_array); + g_array_unref (video_codecs_array); + + g_signal_connect (G_OBJECT (webrtcendpoint), "on-ice-candidate", + G_CALLBACK (on_ice_candidate_check_ip), "198.51.100.1"); + + fail_unless (gst_sdp_message_new (&offer) == GST_SDP_OK); + fail_unless ( + gst_sdp_message_parse_buffer ((const guint8 *) offer_str, -1, offer) + == GST_SDP_OK); + g_signal_emit_by_name (webrtcendpoint, "create-session", &sess_id); + g_signal_emit_by_name ( + webrtcendpoint, "process-offer", sess_id, offer, &answer); + g_signal_emit_by_name (webrtcendpoint, "gather-candidates", sess_id, &ret); + fail_unless (ret); + g_object_unref (webrtcendpoint); + g_free (sess_id); +} +GST_END_TEST + +/** + * Test mangling of ICE candidates to set a custom IPv6 address. + */ +GST_START_TEST (set_external_ipv6_test) +{ + GArray *audio_codecs_array, *video_codecs_array; + gchar *audio_codecs[] = {"opus/48000/1", NULL}; + gchar *video_codecs[] = {"VP8/90000", NULL}; + GstElement *webrtcendpoint = + gst_element_factory_make ("webrtcendpoint", NULL); + gchar *sess_id; + GstSDPMessage *offer = NULL, *answer = NULL; + gboolean ret; + + g_object_set (webrtcendpoint, "external-ipv6", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", NULL); + + // No "ice-ufrag" and "ice-pwd" will cause a warning message "Cannot set + // remote media credentials", which can be ignored. + static const gchar *offer_str = + "v=0\r\n" + "o=mozilla...THIS_IS_SDPARTA-43.0 4115481872190049086 0 IN IP4 0.0.0.0\r\n" + "a=ice-options:trickle\r\n" + "a=msid-semantic:WMS *\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 120\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=sendrecv\r\n" + "a=mid:sdparta_0\r\n" + "a=rtpmap:120 VP8/90000\r\n"; + + audio_codecs_array = create_codecs_array (audio_codecs); + video_codecs_array = create_codecs_array (video_codecs); + g_object_set (webrtcendpoint, "num-audio-medias", 1, "audio-codecs", + g_array_ref (audio_codecs_array), "num-video-medias", 1, "video-codecs", + g_array_ref (video_codecs_array), NULL); + + g_array_unref (audio_codecs_array); + g_array_unref (video_codecs_array); + + g_signal_connect (G_OBJECT (webrtcendpoint), "on-ice-candidate", + G_CALLBACK (on_ice_candidate_check_ip), + "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + + fail_unless (gst_sdp_message_new (&offer) == GST_SDP_OK); + fail_unless ( + gst_sdp_message_parse_buffer ((const guint8 *) offer_str, -1, offer) + == GST_SDP_OK); + g_signal_emit_by_name (webrtcendpoint, "create-session", &sess_id); + g_signal_emit_by_name ( + webrtcendpoint, "process-offer", sess_id, offer, &answer); + g_signal_emit_by_name (webrtcendpoint, "gather-candidates", sess_id, &ret); + fail_unless (ret); + g_object_unref (webrtcendpoint); + g_free (sess_id); +} +GST_END_TEST + /* * End of test cases */ @@ -2498,7 +2615,10 @@ webrtcendpoint_test_suite (void) tcase_add_test (tc_chain, process_mid_no_bundle_offer); tcase_add_test (tc_chain, set_network_interfaces_test); + tcase_add_test (tc_chain, set_external_address_test); + tcase_add_test (tc_chain, set_external_ipv4_test); + tcase_add_test (tc_chain, set_external_ipv6_test); return s; } From 674516f670a06610a554e1a75b9ad84ae7c22a9b Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 11 Sep 2020 14:40:34 +0200 Subject: [PATCH 27/69] webrtcendpoint: rewrite docs for externalAddress, externalIPv4, externalIPv6 Add Javadoc/Jsdoc texts for new parameters externalIPv4, externalIPv6. Edit externalAddress to make all of them homogeneous. --- src/server/config/WebRtcEndpoint.conf.ini | 68 ++++++++---------- .../elements.WebRtcEndpoint.kmd.json | 71 +++++++++++++++---- 2 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index 7c974984e..7ee9cb0eb 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -80,53 +80,45 @@ ;pemCertificateRSA=/path/to/cert+key.pem ;pemCertificateECDSA=/path/to/cert+key.pem -;; External IP address of the media server. -;; -;; This setting is normally NOT needed. Only use it if you know what you're -;; doing, and understand 100% WHY you want it. For the majority of cases, you -;; should prefer configuring STUN or TURN servers over using this setting. +;; External IPv4 and IPv6 addresses of the media server. ;; -;; This setting implements a hack that will mangle all local ICE candidates so -;; that their candidate address is replaced with the provided external address, -;; even for candidates of type 'host'. In doing so, this KMS will not need a -;; STUN or TURN server, but remote peers will still be able to contact it. +;; Forces all local IPv4 and/or IPv6 ICE candidates to have the given address. +;; This is really nothing more than a hack, but it's very effective to force a +;; public IP address when one is known in advance for the media server. In doing +;; so, KMS will not need a STUN or TURN server, but remote peers will still be +;; able to contact it. ;; -;; You can try using this setting if KMS is deployed on a publicly accessible +;; You can try using these settings if KMS is deployed on a publicly accessible ;; server, without NAT, and with a static public IP address. But if it doesn't -;; work for you, just go back to using the STUN or TURN settings above. +;; work for you, just go back to configuring a STUN or TURN server for ICE. ;; -;; is an IPv4 or IPv6 address. +;; Only set this parameter if you know what you're doing, and you understand +;; 100% WHY you need it. For the majority of cases, you should just prefer to +;; configure a STUN or TURN server. ;; -;; Examples: -;; externalAddress=10.20.30.40 -;; externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 +;; is a single IPv4 address. +;; is a single IPv6 address. ;; -;externalAddress=10.20.30.40 +;externalIPv4=198.51.100.1 +;externalIPv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334 -;; External IPv4 address of the media server. -;; -;; This setting follows the same rationale of the externalAddress setting, but -;; the idea here is to couple it with the externalIPv6 setting to achieve dual -;; stack IPv4+IPv6 candidate mangling. -;; -;; Right now, externalAddress only allows a single IP (which has to be either -;; IPv4 or IPv6), so it's not dual stack friendly. This option will mangle -;; IPv4 candidates with the specified one. -;; -;; For the sake of backwards compatibility, externalAddress has preference over -;; this: when externalAddress is set, this option will be ignored. -;; -;; is an IPv4 address. +;; External IP address of the media server. ;; -; externalIPv4=10.20.30.40 - -;; External IPv6 address of the media server. +;; Forces all local IPv4 and IPv6 ICE candidates to have the given address. This +;; is really nothing more than a hack, but it's very effective to force a public +;; IP address when one is known in advance for the media server. In doing so, +;; KMS will not need a STUN or TURN server, but remote peers will still be able +;; to contact it. ;; -;; Refer to the detailing of externalIPv4 which should explain this as well. +;; You can try using this setting if KMS is deployed on a publicly accessible +;; server, without NAT, and with a static public IP address. But if it doesn't +;; work for you, just go back to configuring a STUN or TURN server for ICE. ;; -;; For the sake of backwards compatibility, externalAddress has preference over -;; this: when externalAddress is set, this option will be ignored. +;; Only set this parameter if you know what you're doing, and you understand +;; 100% WHY you need it. For the majority of cases, you should just prefer to +;; configure a STUN or TURN server. ;; -;; is an IPv6 address. +;; is a single IPv4 or IPv6 address. ;; -; externalIPv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334 +;externalAddress=198.51.100.1 +;externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index be017c8a2..5103b804d 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -359,39 +359,82 @@ }, { "name": "externalIPv4", - "doc": "External IPv4 of the media server", + "doc": "External IPv4 address of the media server. +

    + Forces all local IPv4 ICE candidates to have the given address. This is really + nothing more than a hack, but it's very effective to force a public IP address + when one is known in advance for the media server. In doing so, KMS will not + need a STUN or TURN server, but remote peers will still be able to contact it. +

    +

    + You can try using this setting if KMS is deployed on a publicly accessible + server, without NAT, and with a static public IP address. But if it doesn't + work for you, just go back to configuring a STUN or TURN server for ICE. +

    +

    + Only set this parameter if you know what you're doing, and you understand 100% + WHY you need it. For the majority of cases, you should just prefer to + configure a STUN or TURN server. +

    +

    externalIPv4 is a single IPv4 address.

    +

    Example:

    +
      +
    • externalIPv4=198.51.100.1
    • +
    + ", "type": "String" }, { "name": "externalIPv6", - "doc": "External IPv6 of the media server", + "doc": "External IPv6 address of the media server. +

    + Forces all local IPv6 ICE candidates to have the given address. This is really + nothing more than a hack, but it's very effective to force a public IP address + when one is known in advance for the media server. In doing so, KMS will not + need a STUN or TURN server, but remote peers will still be able to contact it. +

    +

    + You can try using this setting if KMS is deployed on a publicly accessible + server, without NAT, and with a static public IP address. But if it doesn't + work for you, just go back to configuring a STUN or TURN server for ICE. +

    +

    + Only set this parameter if you know what you're doing, and you understand 100% + WHY you need it. For the majority of cases, you should just prefer to + configure a STUN or TURN server. +

    +

    externalIPv6 is a single IPv6 address.

    +

    Example:

    +
      +
    • externalIPv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334
    • +
    + ", "type": "String" }, { "name": "externalAddress", "doc": "External IP address of the media server.

    - This setting is normally NOT needed. Only use it if you know what you're - doing, and understand 100% WHY you want it. For the majority of cases, you - should prefer configuring STUN or TURN servers over using this setting. -

    -

    - This setting implements a hack that will mangle all local ICE candidates so - that their candidate address is replaced with the provided external address, - even for candidates of type 'host'. In doing so, this KMS will not need a - STUN or TURN server, but remote peers will still be able to contact it. + Forces all local IPv4 and IPv6 ICE candidates to have the given address. This + is really nothing more than a hack, but it's very effective to force a public + IP address when one is known in advance for the media server. In doing so, KMS + will not need a STUN or TURN server, but remote peers will still be able to + contact it.

    You can try using this setting if KMS is deployed on a publicly accessible server, without NAT, and with a static public IP address. But if it doesn't - work for you, just go back to using the STUN or TURN settings above. + work for you, just go back to configuring a STUN or TURN server for ICE.

    - externalAddress is an IPv4 or IPv6 address. + Only set this parameter if you know what you're doing, and you understand 100% + WHY you need it. For the majority of cases, you should just prefer to + configure a STUN or TURN server.

    +

    externalAddress is a single IPv4 or IPv6 address.

    Examples:

      -
    • externalAddress=10.70.35.2
    • +
    • externalAddress=198.51.100.1
    • externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334
    ", From 82cebc86c036c0e2b9593b2c04e1a59539e25b17 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 11 Sep 2020 14:41:44 +0200 Subject: [PATCH 28/69] webrtcendpoint: deprecate externalAddress This is now superseded by the combination of externalIPv4 and externalIPv6. --- src/server/config/WebRtcEndpoint.conf.ini | 2 ++ src/server/interface/elements.WebRtcEndpoint.kmd.json | 1 + 2 files changed, 3 insertions(+) diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index 7ee9cb0eb..145044dbd 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -104,6 +104,8 @@ ;; External IP address of the media server. ;; +;; DEPRECATED: Use "externalIPv4" and/or "externalIPv6" instead. +;; ;; Forces all local IPv4 and IPv6 ICE candidates to have the given address. This ;; is really nothing more than a hack, but it's very effective to force a public ;; IP address when one is known in advance for the media server. In doing so, diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index 5103b804d..2938cd367 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -437,6 +437,7 @@
  • externalAddress=198.51.100.1
  • externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • +@deprecated Use externalIPv4 and/or externalIPv6 instead. ", "type": "String" }, From 2137fb24d026db183f3d3007319f044a3b9c87a6 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 9 Oct 2020 19:34:28 +0200 Subject: [PATCH 29/69] minor, cmake: "Warnings are Errors" exception samples --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 842972dea..fa3bbb7b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,11 @@ include(CommonBuildFlags) common_buildflags_set() #common_buildflags_print() -# Development: Exceptions to "Warnings are Errors" rule +# Development: Add here exceptions to the "Warnings are Errors" rule. +# Also, DOCUMENT WHY and always remove them as soon as the problem is fixed. +# For example: #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=unused-function") +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=unused-variable") # FIXME Disable error when macros __TIME__, __DATE__ or __TIMESTAMP__ are encountered include(CheckCXXCompilerFlag) From 018e1919bea3732fe4864c4baf862fb347b381c7 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 9 Oct 2020 19:35:14 +0200 Subject: [PATCH 30/69] Replace GST_PAD_PROBE_INFO_* macros with type-checked getters Macros are dangerous; they would hide if some probe was accessing the GstPadProbeInfo with the incorrect type info. Use the getter functions, which at least the provide some type validation. --- src/gst-plugins/kmsalphablending.c | 4 ++-- src/gst-plugins/kmscompositemixer.c | 4 ++-- src/gst-plugins/kmshttppostendpoint.c | 2 +- src/gst-plugins/kmsplayerendpoint.c | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gst-plugins/kmsalphablending.c b/src/gst-plugins/kmsalphablending.c index 45c1fb6d7..347286e79 100644 --- a/src/gst-plugins/kmsalphablending.c +++ b/src/gst-plugins/kmsalphablending.c @@ -465,7 +465,7 @@ cb_EOS_received (GstPad * pad, GstPadProbeInfo * info, KmsAlphaBlending *self = port_data->mixer; GstEvent *event; - if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_EOS) { + if (GST_EVENT_TYPE (gst_pad_probe_info_get_event (info)) != GST_EVENT_EOS) { return GST_PAD_PROBE_OK; } @@ -607,7 +607,7 @@ link_to_videomixer (GstPad * pad, GstPadProbeInfo * info, GstPadTemplate *sink_pad_template; KmsAlphaBlending *mixer = data->mixer; - if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_CAPS) { + if (GST_EVENT_TYPE (gst_pad_probe_info_get_event (info)) != GST_EVENT_CAPS) { return GST_PAD_PROBE_PASS; } diff --git a/src/gst-plugins/kmscompositemixer.c b/src/gst-plugins/kmscompositemixer.c index 8c2efcb05..7f25ab5ad 100644 --- a/src/gst-plugins/kmscompositemixer.c +++ b/src/gst-plugins/kmscompositemixer.c @@ -264,7 +264,7 @@ cb_EOS_received (GstPad * pad, GstPadProbeInfo * info, gpointer data) KmsCompositeMixer *self = port_data->mixer; GstEvent *event; - if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_EOS) { + if (GST_EVENT_TYPE (gst_pad_probe_info_get_event (info)) != GST_EVENT_EOS) { return GST_PAD_PROBE_OK; } @@ -429,7 +429,7 @@ link_to_videomixer (GstPad * pad, GstPadProbeInfo * info, KmsCompositeMixer *mixer; GstPad *tee_src; - if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != + if (GST_EVENT_TYPE (gst_pad_probe_info_get_event (info)) != GST_EVENT_STREAM_START) { return GST_PAD_PROBE_PASS; } diff --git a/src/gst-plugins/kmshttppostendpoint.c b/src/gst-plugins/kmshttppostendpoint.c index f165626d3..f0eec9d39 100644 --- a/src/gst-plugins/kmshttppostendpoint.c +++ b/src/gst-plugins/kmshttppostendpoint.c @@ -168,7 +168,7 @@ static GstPadProbeReturn set_appsrc_caps (GstPad * pad, GstPadProbeInfo * info, gpointer httpep) { KmsHttpPostEndpoint *self = KMS_HTTP_POST_ENDPOINT (httpep); - GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstEvent *event = gst_pad_probe_info_get_event (info); GstCaps *audio_caps = NULL, *video_caps = NULL; GstElement *appsrc, *appsink, *agnosticbin; GstCaps *caps; diff --git a/src/gst-plugins/kmsplayerendpoint.c b/src/gst-plugins/kmsplayerendpoint.c index 9cc195dc8..c4a7b1554 100644 --- a/src/gst-plugins/kmsplayerendpoint.c +++ b/src/gst-plugins/kmsplayerendpoint.c @@ -655,7 +655,7 @@ appsink_eos_cb (GstAppSink * appsink, gpointer user_data) static GstPadProbeReturn appsrc_query_probe (GstPad * pad, GstPadProbeInfo * info, gpointer element) { - GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info); + GstQuery *query = gst_pad_probe_info_get_query (info); GstQueryType type = GST_QUERY_TYPE (query); GstElement *appsink = GST_ELEMENT (element); @@ -764,7 +764,7 @@ static GstPadProbeReturn appsink_probe_set_appsrc_caps (GstPad * pad, GstPadProbeInfo * info, gpointer element) { - GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstEvent *event = gst_pad_probe_info_get_event (info); GstElement *appsrc = GST_ELEMENT (element); GstCaps *caps; @@ -788,7 +788,7 @@ static GstPadProbeReturn appsink_probe_query_appsrc_caps (GstPad * pad, GstPadProbeInfo * info, gpointer element) { - GstQuery *query = GST_PAD_PROBE_INFO_QUERY (info); + GstQuery *query = gst_pad_probe_info_get_query (info); GstQueryType type = GST_QUERY_TYPE (query); GstElement *appsrc = GST_ELEMENT (element); From 372bd58e3b7a71201d0b957dfae46ac977d6218a Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 9 Oct 2020 19:35:49 +0200 Subject: [PATCH 31/69] PlayerEndpoint API: document networkCache (RTSP buffer length) --- .../elements.PlayerEndpoint.kmd.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/server/interface/elements.PlayerEndpoint.kmd.json b/src/server/interface/elements.PlayerEndpoint.kmd.json index 31ede992b..23ebca7ef 100644 --- a/src/server/interface/elements.PlayerEndpoint.kmd.json +++ b/src/server/interface/elements.PlayerEndpoint.kmd.json @@ -89,7 +89,24 @@ }, { "name": "networkCache", - "doc": "When using RTSP sources: Amount of milliseconds to buffer", + "doc": "RTSP buffer length. +

    + When receiving media from an RTSP source, the streamed video can suffer spikes + or stuttering, caused by hardware or network issues. Having a reception buffer + helps alleviate these problems, because it smoothes the stream of incoming + data to the receiving endpoint. +

    +

    + Finding a buffer length that works best for your connection might take some + tweaking, which can be done with this optional property. Note that a longer + buffer will be able to fix bigger network spikes, but at the cost of + introducing more latency to the media playback. +

    +
      +
    • Unit: milliseconds.
    • +
    • Default: 2000.
    • +
    + ", "type": "int", "optional": true, "defaultValue": 2000 From fb0cbce09aca61d8ad9c70c1cc2635fdb3e864f2 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 9 Oct 2020 20:20:25 +0200 Subject: [PATCH 32/69] minor, recorder: reorganize code and add comments The code needed a bit of structuring in order to be easier to read and understand. I'm adding some explicit blocks in order to differentiate the different actions that are taken, especially in the recv_sample function. --- .../recorderendpoint/kmsrecorderendpoint.c | 193 ++++++++++-------- 1 file changed, 107 insertions(+), 86 deletions(-) diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index 42c4ecc69..a75412257 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -262,35 +262,35 @@ release_base_time_type (gpointer data) g_slice_free (BaseTimeType, data); } +// Adjust timestamps to avoid gaps created by paused recordings. static GstFlowReturn recv_sample (GstAppSink * appsink, gpointer user_data) { KmsRecorderEndpoint *self = KMS_RECORDER_ENDPOINT (GST_OBJECT_PARENT (appsink)); - KmsUriEndpointState state; - GstAppSrc *appsrc; - GstFlowReturn ret; - GstSample *sample; - GstSegment *segment; - GstBuffer *buffer; - BaseTimeType *base_time; - GstClockTime offset; - GstCaps *caps; - gboolean unlock_element = TRUE; + KmsUriEndpointState state = KMS_URI_ENDPOINT_STATE_STOP; + BaseTimeType *base_time = NULL; + GstCaps *caps = NULL; - appsrc = g_object_get_qdata (G_OBJECT (appsink), kms_appsrc_id_key_quark ()); + gboolean unlock_element = FALSE; + GstSample *sample = NULL; + GstFlowReturn ret = GST_FLOW_OK; + GstAppSrc *appsrc = + g_object_get_qdata (G_OBJECT (appsink), kms_appsrc_id_key_quark ()); if (appsrc == NULL) { GST_ERROR_OBJECT (appsink, "No appsrc attached"); - return GST_FLOW_NOT_LINKED; + ret = GST_FLOW_NOT_LINKED; + goto end; } sample = gst_app_sink_pull_sample (appsink); if (sample == NULL) { - return GST_FLOW_OK; + ret = GST_FLOW_OK; + goto end; } - buffer = gst_sample_get_buffer (sample); + GstBuffer *buffer = gst_sample_get_buffer (sample); if (buffer == NULL) { if (gst_sample_get_buffer_list (sample) != NULL) { GST_ERROR_OBJECT (appsink, @@ -301,16 +301,18 @@ recv_sample (GstAppSink * appsink, gpointer user_data) goto end; } - segment = gst_sample_get_segment (sample); + const GstSegment *segment = gst_sample_get_segment (sample); + unlock_element = TRUE; KMS_ELEMENT_LOCK (self); + state = kms_uri_endpoint_get_state (KMS_URI_ENDPOINT (self)); if (!((state == KMS_URI_ENDPOINT_STATE_START && self->priv->transition == KMS_RECORDER_ENDPOINT_COMPLETED) || self->priv->transition == KMS_RECORDER_ENDPOINT_STARTING)) { GST_LOG_OBJECT (appsink, - "Not recording, dropping buffer %" GST_PTR_FORMAT, buffer); + "Not recording, drop buffer %" GST_PTR_FORMAT, buffer); ret = GST_FLOW_OK; goto end; } @@ -318,51 +320,75 @@ recv_sample (GstAppSink * appsink, gpointer user_data) gst_buffer_ref (buffer); buffer = gst_buffer_make_writable (buffer); - if (GST_BUFFER_PTS_IS_VALID (buffer)) - GST_BUFFER_PTS (buffer) = - gst_segment_to_running_time (segment, GST_FORMAT_TIME, - GST_BUFFER_PTS (buffer)); - if (GST_BUFFER_DTS_IS_VALID (buffer)) - GST_BUFFER_DTS (buffer) = - gst_segment_to_running_time (segment, GST_FORMAT_TIME, - GST_BUFFER_DTS (buffer)); + // Ensure that PTS/DTS are measured from 00:00:00. Do this by replacing each + // one by their GStreamer running time, which always starts from 0 wrt. its + // containing segment. + { + if (GST_BUFFER_PTS_IS_VALID (buffer)) { + GST_BUFFER_PTS (buffer) = gst_segment_to_running_time ( + segment, GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)); + } + + if (GST_BUFFER_DTS_IS_VALID (buffer)) { + GST_BUFFER_DTS (buffer) = gst_segment_to_running_time ( + segment, GST_FORMAT_TIME, GST_BUFFER_DTS (buffer)); + } + } BASE_TIME_LOCK (self); - base_time = g_object_get_qdata (G_OBJECT (self), base_time_key_quark ()); + // First time this runs, create a new BaseTime storage. + { + base_time = g_object_get_qdata (G_OBJECT (self), base_time_key_quark ()); + if (base_time == NULL) { + base_time = g_slice_new0 (BaseTimeType); + base_time->pts = GST_CLOCK_TIME_NONE; + base_time->dts = GST_CLOCK_TIME_NONE; + base_time->audio_gaps = 0; + + g_object_set_qdata_full (G_OBJECT (self), base_time_key_quark (), + base_time, release_base_time_type); + } - if (base_time == NULL) { - base_time = g_slice_new0 (BaseTimeType); - base_time->pts = GST_BUFFER_PTS (buffer); - base_time->dts = GST_BUFFER_DTS (buffer); - GST_DEBUG_OBJECT (appsrc, "Setting pts base time to: %" G_GUINT64_FORMAT, - base_time->pts); - g_object_set_qdata_full (G_OBJECT (self), base_time_key_quark (), base_time, - release_base_time_type); - } + if (!GST_CLOCK_TIME_IS_VALID (base_time->pts) + && GST_BUFFER_PTS_IS_VALID (buffer)) { + base_time->pts = GST_BUFFER_PTS (buffer); + GST_DEBUG_OBJECT (self, "Setting PTS base time to %" GST_TIME_FORMAT, + GST_TIME_ARGS (base_time->pts)); + } - if (!GST_CLOCK_TIME_IS_VALID (base_time->pts) - && GST_BUFFER_PTS_IS_VALID (buffer)) { - base_time->pts = GST_BUFFER_PTS (buffer); - GST_DEBUG_OBJECT (appsrc, "Setting pts base time to: %" G_GUINT64_FORMAT, - base_time->pts); - base_time->dts = GST_BUFFER_DTS (buffer); + if (!GST_CLOCK_TIME_IS_VALID (base_time->dts) + && GST_BUFFER_DTS_IS_VALID (buffer)) { + base_time->dts = GST_BUFFER_DTS (buffer); + GST_DEBUG_OBJECT (self, "Setting DTS base time to %" GST_TIME_FORMAT, + GST_TIME_ARGS (base_time->dts)); + } } - if (GST_CLOCK_TIME_IS_VALID (base_time->pts)) { - if (GST_BUFFER_PTS_IS_VALID (buffer)) { - offset = base_time->pts + self->priv->paused_time; + // Adjust PTS/DTS of all buffers, so recordings are always created with an + // initial timestamp of 0 (0:00:00.000). + { + // FIXME: There is some skew introduced each time the recording is paused. + // The 'paused_time' doesn't account exactly for all the time, it is missing + // some milliseconds. Maybe due to latency in upstream elements? + + if (GST_CLOCK_TIME_IS_VALID (base_time->pts) + && GST_BUFFER_PTS_IS_VALID (buffer)) { + const GstClockTime offset = + base_time->pts + self->priv->paused_time; + // PTS -= offset, but preventing underflows. if (GST_BUFFER_PTS (buffer) > offset) { GST_BUFFER_PTS (buffer) -= offset; } else { GST_BUFFER_PTS (buffer) = 0; } } - } - if (GST_CLOCK_TIME_IS_VALID (base_time->dts)) { - if (GST_BUFFER_DTS_IS_VALID (buffer)) { - offset = base_time->dts + self->priv->paused_time; + if (GST_CLOCK_TIME_IS_VALID (base_time->dts) + && GST_BUFFER_DTS_IS_VALID (buffer)) { + const GstClockTime offset = + base_time->dts + self->priv->paused_time; + // DTS -= offset, but preventing underflows. if (GST_BUFFER_DTS (buffer) > offset) { GST_BUFFER_DTS (buffer) -= offset; } else { @@ -371,29 +397,26 @@ recv_sample (GstAppSink * appsink, gpointer user_data) } } - BASE_TIME_UNLOCK (GST_OBJECT_PARENT (appsink)); + BASE_TIME_UNLOCK (self); + // Set some flags to make sure the buffer is appropriately handled downstream. GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_LIVE); - - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER)) + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER)) { GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } - caps = gst_app_src_get_caps (appsrc); + KMS_ELEMENT_UNLOCK (self); + unlock_element = FALSE; + caps = gst_app_src_get_caps (appsrc); if (caps == NULL) { GST_ERROR_OBJECT (appsrc, "Trying to push buffer without setting caps"); } else { gst_caps_unref (caps); } - - KMS_ELEMENT_UNLOCK (self); - - unlock_element = FALSE; - ret = gst_app_src_push_buffer (appsrc, buffer); if (ret != GST_FLOW_OK) { - /* something wrong */ GST_ERROR_OBJECT (self, "Could not send buffer to appsrc %s. Cause: %s", GST_ELEMENT_NAME (appsrc), gst_flow_get_name (ret)); ret = GST_FLOW_CUSTOM_SUCCESS; @@ -995,42 +1018,40 @@ configure_pipeline_capabilities (GstPad * pad, GstPadProbeInfo * info, { KmsRecorderEndpoint *self = KMS_RECORDER_ENDPOINT (user_data); GstEvent *event = gst_pad_probe_info_get_event (info); - GstElement *appsrc, *appsink; - GstCaps *caps; - - if (GST_EVENT_TYPE (event) != GST_EVENT_CAPS) - return GST_PAD_PROBE_OK; - - gst_event_parse_caps (event, &caps); - - GST_DEBUG_OBJECT (pad, "Processing caps event %" GST_PTR_FORMAT, caps); + GstCaps *caps = NULL; + GstPadProbeReturn ret = GST_PAD_PROBE_OK; - if (gst_caps_get_size (caps) == 0) { - GST_ERROR_OBJECT (pad, "Invalid event %" GST_PTR_FORMAT, event); + if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) { + gst_event_parse_caps (event, &caps); - return GST_PAD_PROBE_OK; - } + if (gst_caps_get_size (caps) == 0) { + GST_ERROR_OBJECT (pad, "Invalid CAPS event %" GST_PTR_FORMAT, event); + return GST_PAD_PROBE_OK; + } - if (!gst_caps_is_fixed (caps)) { - GST_WARNING_OBJECT (pad, "Not fixed caps in event %" GST_PTR_FORMAT, event); - } + GST_DEBUG_OBJECT (pad, "Processing CAPS event %" GST_PTR_FORMAT, caps); - appsink = gst_pad_get_parent_element (pad); - GST_DEBUG_OBJECT (appsink, "Setting caps: %" GST_PTR_FORMAT, caps); + if (!gst_caps_is_fixed (caps)) { + GST_WARNING_OBJECT ( + pad, "Not fixed caps in CAPS event %" GST_PTR_FORMAT, event); + } - set_appsink_caps (appsink, caps, self->priv->profile); + GstElement *appsink = gst_pad_get_parent_element (pad); + GST_DEBUG_OBJECT (appsink, "Setting caps: %" GST_PTR_FORMAT, caps); + set_appsink_caps (appsink, caps, self->priv->profile); - appsrc = g_object_get_qdata (G_OBJECT (appsink), kms_appsrc_id_key_quark ()); + GstElement *appsrc = + g_object_get_qdata (G_OBJECT (appsink), kms_appsrc_id_key_quark ()); + if (appsrc != NULL) { + set_appsrc_caps (appsrc, caps); + } else { + GST_ERROR_OBJECT (pad, "No appsrc attached"); + } - if (appsrc != NULL) { - set_appsrc_caps (appsrc, caps); - } else { - GST_ERROR_OBJECT (pad, "No appsrc attached"); + g_object_unref (appsink); } - g_object_unref (appsink); - - return GST_PAD_PROBE_OK; + return ret; } static GstPadLinkReturn @@ -1168,12 +1189,12 @@ kms_recorder_endpoint_add_appsink (KmsRecorderEndpoint * self, GstPad *sinkpad; if (g_hash_table_contains (self->priv->sink_pad_data, name)) { - GST_WARNING_OBJECT (self, "Sink %s already added", name); + GST_WARNING_OBJECT (self, "Sink '%s' already added", name); return; } if (type != KMS_ELEMENT_PAD_TYPE_AUDIO && type != KMS_ELEMENT_PAD_TYPE_VIDEO) { - GST_WARNING_OBJECT (self, "Unsupported pad type %u", type); + GST_WARNING_OBJECT (self, "Unsupported pad type: %u", type); return; } From 29fad84f437093aa8eb8c1f333eaa67ef9b327c4 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 22 Oct 2020 16:41:28 +0200 Subject: [PATCH 33/69] webrtc: fix test_not_enough_ports for libnice 0.1.18 --- tests/check/element/webrtcendpoint.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index d4d4dbc81..2e23adb1d 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -2124,7 +2124,7 @@ not_enough_ports_on_ice_candidate (GstElement *self, gchar *sess_id, kms_ice_candidate_get_candidate_tcp_type (candidate); const KmsIceProtocol proto = kms_ice_candidate_get_protocol (candidate); - GST_DEBUG ("SessionId: '%s', candidate: '%s'", sess_id, + GST_DEBUG_OBJECT (self, "SessionId: '%s', candidate: '%s'", sess_id, kms_ice_candidate_get_candidate (candidate)); if (tcp_type == KMS_ICE_TCP_CANDIDATE_TYPE_ACTIVE) { @@ -2212,29 +2212,19 @@ GST_START_TEST (test_not_enough_ports) g_signal_emit_by_name (offerer, "gather-candidates", offerer_sess_id, &ret); fail_unless (ret); - /* libnice 0.1.13 should fail here because the second offerer cannot get - * two UDP ports for its two components. + /* Since libnice 0.1.18, the next call should fail because there are not + * enough available ports for the seconds WebRtcEndpoint to gather its + * candidates. * - * libnice 0.1.14 was improved in this regard, and shouldn't fail because - * even if it doesn't find UDP candidates, it should be able to find - * TCP-ACTIVE type ones for the second component of the second offerer. + * Ports 55000-55002 UDP/TCP combinations are all used up by both components + * 1 (RTP) and 2 (RTCP) from the first WebRtcEndpoint, and libnice fails with + * this message: * - * Example list of candidate gathering (from libnice-0.1.14 debug log): - * Component 1: - * UDP local candidate : [192.168.56.5]:55000 for s1/c1 - * TCP-ACT local candidate : [192.168.56.5]:0 for s1/c1 - * TCP-PASS local candidate : [192.168.56.5]:55000 for s1/c1 - * UDP local candidate : [192.168.1.2]:55000 for s1/c1 - * TCP-ACT local candidate : [192.168.1.2]:0 for s1/c1 - * TCP-PASS local candidate : [192.168.1.2]:55002 for s1/c1 - * Component 2: - * TCP-ACT local candidate : [192.168.56.5]:0 for s1/c2 - * TCP-ACT local candidate : [192.168.1.2]:0 for s1/c2 + * Agent 0x55d0e51021e0: Unable to add local host 192.168.1.2 candidate tcp-pass for s1:2. Every port is duplicated */ - g_signal_emit_by_name ( second_offerer, "gather-candidates", second_offerer_sess_id, &ret); - fail_unless (ret); + fail_if (ret); gst_sdp_message_free (offer); gst_sdp_message_free (second_offer); From 443f3dc88f513fb78ac9e784879b17d134b07e94 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 22 Oct 2020 17:15:38 +0200 Subject: [PATCH 34/69] fixup! minor, recorder: reorganize code and add comments --- src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index a75412257..a2834b381 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -344,7 +344,6 @@ recv_sample (GstAppSink * appsink, gpointer user_data) base_time = g_slice_new0 (BaseTimeType); base_time->pts = GST_CLOCK_TIME_NONE; base_time->dts = GST_CLOCK_TIME_NONE; - base_time->audio_gaps = 0; g_object_set_qdata_full (G_OBJECT (self), base_time_key_quark (), base_time, release_base_time_type); From c5e16515984909c4a45ee27cac19e0c13d688f65 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 22 Oct 2020 17:25:50 +0200 Subject: [PATCH 35/69] recorder: improve a/v sync compensating gaps in audio timestamps This change touches on kms-core, kms-elements --- .../recorderendpoint/kmsrecorderendpoint.c | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index a2834b381..c918bfd5c 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -254,6 +254,7 @@ typedef struct _BaseTimeType { GstClockTime pts; GstClockTime dts; + GstClockTime audio_gaps; } BaseTimeType; static void @@ -344,6 +345,7 @@ recv_sample (GstAppSink * appsink, gpointer user_data) base_time = g_slice_new0 (BaseTimeType); base_time->pts = GST_CLOCK_TIME_NONE; base_time->dts = GST_CLOCK_TIME_NONE; + base_time->audio_gaps = 0; g_object_set_qdata_full (G_OBJECT (self), base_time_key_quark (), base_time, release_base_time_type); @@ -374,7 +376,7 @@ recv_sample (GstAppSink * appsink, gpointer user_data) if (GST_CLOCK_TIME_IS_VALID (base_time->pts) && GST_BUFFER_PTS_IS_VALID (buffer)) { const GstClockTime offset = - base_time->pts + self->priv->paused_time; + base_time->pts + base_time->audio_gaps + self->priv->paused_time; // PTS -= offset, but preventing underflows. if (GST_BUFFER_PTS (buffer) > offset) { GST_BUFFER_PTS (buffer) -= offset; @@ -386,7 +388,7 @@ recv_sample (GstAppSink * appsink, gpointer user_data) if (GST_CLOCK_TIME_IS_VALID (base_time->dts) && GST_BUFFER_DTS_IS_VALID (buffer)) { const GstClockTime offset = - base_time->dts + self->priv->paused_time; + base_time->dts + base_time->audio_gaps + self->priv->paused_time; // DTS -= offset, but preventing underflows. if (GST_BUFFER_DTS (buffer) > offset) { GST_BUFFER_DTS (buffer) -= offset; @@ -1012,7 +1014,7 @@ set_appsink_caps (GstElement * appsink, const GstCaps * caps, } static GstPadProbeReturn -configure_pipeline_capabilities (GstPad * pad, GstPadProbeInfo * info, +appsink_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { KmsRecorderEndpoint *self = KMS_RECORDER_ENDPOINT (user_data); @@ -1047,6 +1049,41 @@ configure_pipeline_capabilities (GstPad * pad, GstPadProbeInfo * info, GST_ERROR_OBJECT (pad, "No appsrc attached"); } + g_object_unref (appsink); + } else if (GST_EVENT_TYPE (event) == GST_EVENT_GAP) { + /* + This event could arrive from upstream if, for example, the RtpBin inside a + WebRtcEndpoint detects missing audio frames and generates a GAP event + (technicaly, the RtpJitterBuffer generates a CUSTOM_DOWNSTREAM event + named "GstRTPPacketLost", and the RTP depayloader converts it into a GAP + event). + + The GAP event for video streams is handled at the output of the + RtpJitterBuffer (see gap_detection_probe in kmsutils), but the audio one + isn't, so it will reach downstream elements such as this one. + */ + + // Get the RecorderEndpoint base timings, if any yet. + BaseTimeType *base_time = + g_object_get_qdata (G_OBJECT (self), base_time_key_quark ()); + + // Get the current appsink caps to see if this is applies to the audio. + GstAppSink *appsink = GST_APP_SINK (gst_pad_get_parent_element (pad)); + caps = gst_app_sink_get_caps (appsink); + + if (base_time != NULL && kms_utils_caps_is_audio (caps)) { + GstClockTime gap_pts; + GstClockTime gap_duration; + gst_event_parse_gap (event, &gap_pts, &gap_duration); + + // This will later be used to adjust timestamp of audio buffers. + base_time->audio_gaps += gap_duration; + + // The GAP event has been handled here, so no need to pass it downstream. + ret = GST_PAD_PROBE_DROP; + } + + gst_caps_unref (caps); g_object_unref (appsink); } @@ -1213,7 +1250,7 @@ kms_recorder_endpoint_add_appsink (KmsRecorderEndpoint * self, g_strdup (name), g_free); gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - configure_pipeline_capabilities, self, NULL); + appsink_event_probe, self, NULL); g_object_unref (sinkpad); From 3130845017880ed2a61d3f9713141424921f6a6f Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 22 Oct 2020 17:27:28 +0200 Subject: [PATCH 36/69] webrtc: log effective STUN/TURN server, network interfaces, ICE cand --- .../webrtcendpoint/kmswebrtcendpoint.c | 12 ++++++--- .../webrtcendpoint/kmswebrtcsession.c | 25 +++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index f6a559040..70307b6cc 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -116,10 +116,12 @@ on_ice_candidate (KmsWebrtcSession * sess, KmsIceCandidate * candidate, KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess); GST_DEBUG_OBJECT (self, - "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d", + "[IceCandidateFound] local: '%s', stream_id: %s, component_id: %d, sdpMid: '%s', sdpMLineIndex: %u", kms_ice_candidate_get_candidate (candidate), kms_ice_candidate_get_stream_id (candidate), - kms_ice_candidate_get_component (candidate)); + kms_ice_candidate_get_component (candidate), + kms_ice_candidate_get_sdp_mid (candidate), + kms_ice_candidate_get_sdp_m_line_index (candidate)); g_signal_emit (G_OBJECT (self), kms_webrtc_endpoint_signals[SIGNAL_ON_ICE_CANDIDATE], 0, @@ -479,9 +481,11 @@ kms_webrtc_endpoint_add_ice_candidate (KmsWebrtcEndpoint * self, // Remote candidates haven't been assigned a stream_id yet, so don't print it GST_DEBUG_OBJECT (self, - "[AddIceCandidate] remote: '%s', component_id: %d", + "[AddIceCandidate] remote: '%s', component_id: %d, sdpMid: '%s', sdpMLineIndex: %u", kms_ice_candidate_get_candidate (candidate), - kms_ice_candidate_get_component (candidate)); + kms_ice_candidate_get_component (candidate), + kms_ice_candidate_get_sdp_mid (candidate), + kms_ice_candidate_get_sdp_m_line_index (candidate)); webrtc_sess = KMS_WEBRTC_SESSION (sess); g_signal_emit_by_name (webrtc_sess, "add-ice-candidate", candidate, &ret); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 8d7649a17..59ddef95c 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -384,10 +384,6 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "... (Will add later)"); } - else { - GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, - "... (Error)"); - } return allow_error; } @@ -398,10 +394,6 @@ kms_webrtc_session_agent_add_ice_candidate (KmsWebrtcSession * self, GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, "... (Will add later)"); } - else { - GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self, - "... (Error)"); - } return allow_error; } @@ -589,10 +581,12 @@ kms_webrtc_session_new_candidate (KmsIceBaseAgent * agent, gboolean is_candidate_ipv6 = kms_ice_candidate_get_ip_version (candidate) == IP_VERSION_6; - if (self->external_address != NULL) { + if (self->external_address != NULL + && kms_ice_candidate_get_candidate_type (candidate) + == KMS_ICE_CANDIDATE_TYPE_HOST) { + // DEPRECATED kms_ice_candidate_set_address (candidate, self->external_address); - GST_DEBUG_OBJECT (self, - "[IceCandidateFound] Mangled local: '%s'", + GST_DEBUG_OBJECT (self, "[IceCandidateFound] Mangled local: '%s'", kms_ice_candidate_get_candidate (candidate)); } else if (self->external_ipv4 != NULL && is_candidate_ipv6 == FALSE) { kms_ice_candidate_set_address (candidate, self->external_ipv4); @@ -803,6 +797,9 @@ kms_webrtc_session_set_network_ifs_info (KmsWebrtcSession * self, return; } + GST_DEBUG_OBJECT (self, "Use network interfaces: %s", + self->network_interfaces); + kms_webrtc_base_connection_set_network_ifs_info (conn, self->network_interfaces); } @@ -815,6 +812,9 @@ kms_webrtc_session_set_stun_server_info (KmsWebrtcSession * self, return; } + GST_DEBUG_OBJECT (self, "Use STUN server: %s:%u", self->stun_server_ip, + self->stun_server_port); + kms_webrtc_base_connection_set_stun_server_info (conn, self->stun_server_ip, self->stun_server_port); } @@ -827,6 +827,9 @@ kms_webrtc_session_set_relay_info (KmsWebrtcSession * self, return; } + GST_DEBUG_OBJECT (self, "Use TURN server: %s:%u", self->turn_address, + self->turn_port); + kms_webrtc_base_connection_set_relay_info (conn, self->turn_address, self->turn_port, self->turn_user, self->turn_password, self->turn_transport); From bbd1c7ccb479df805d199f9c0827000cbc29715b Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 22 Oct 2020 19:16:56 +0200 Subject: [PATCH 37/69] debian: update versions of Kurento dependencies --- debian/control | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/debian/control b/debian/control index 36997a121..03dfa6cc6 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,9 @@ Build-Depends: debhelper (>= 9), gstreamer1.5-plugins-bad, gstreamer1.5-plugins-good, gstreamer1.5-x, - kms-cmake-utils (>= 6.7.0), - kms-core-dev (>= 6.7.0), - kurento-module-creator (>= 6.7.0), + kms-cmake-utils (>= 6.14.0), + kms-core-dev (>= 6.14.0), + kurento-module-creator (>= 6.14.0), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, @@ -32,7 +32,7 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer1.5-nice, - kms-core (>= 6.7.0), + kms-core (>= 6.14.0), openh264-gst-plugins-bad-1.5, openwebrtc-gst-plugins Breaks: kms-elements-6.0 @@ -44,9 +44,9 @@ Package: kms-elements-dev Architecture: any Section: libdevel Depends: kms-elements (= ${binary:Version}), - kms-cmake-utils (>= 6.7.0), - kms-core-dev (>= 6.7.0), - kurento-module-creator (>= 6.7.0), + kms-cmake-utils (>= 6.14.0), + kms-core-dev (>= 6.14.0), + kurento-module-creator (>= 6.14.0), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, From 6bf0de20ebe5de0a8217f1674bd886ff913eb576 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 23 Oct 2020 11:42:44 +0200 Subject: [PATCH 38/69] cmake: add "-Wno-error=unused-function" for tests --- tests/check/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/check/CMakeLists.txt b/tests/check/CMakeLists.txt index ed8f2b022..c3a39b0f6 100644 --- a/tests/check/CMakeLists.txt +++ b/tests/check/CMakeLists.txt @@ -1,4 +1,5 @@ -# Disable error when functions are unused +# Disable error when functions are unused. This allows commenting out calls to +# `tcase_add_test()` to skip running certain tests during a debug session. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=unused-function") set(KMS_TEST_UTILS_SOURCES From aaf37748db94d417ef1999025215552d317033e2 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 3 Nov 2020 17:52:46 +0100 Subject: [PATCH 39/69] minor: proper example IPs and comments on TURN --- src/server/config/WebRtcEndpoint.conf.ini | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index 145044dbd..eb6325047 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -26,15 +26,17 @@ ;; ;; The ICE process uses STUN to punch holes through NAT firewalls. ;; +;; You don't need to configure both STUN and TURN, because TURN already includes +;; STUN functionality. +;; ;; MUST be an IP address; domain names are NOT supported. ;; ;; You need to use a well-working STUN server. Use this to check if it works: ;; https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ -;; ;; From that check, you should get at least one Server-Reflexive Candidate ;; (type "srflx"). ;; -;stunServerAddress=127.0.0.1 +;stunServerAddress=198.51.100.1 ;stunServerPort=3478 ;; TURN server URL. @@ -42,8 +44,8 @@ ;; When STUN is not enough to open connections through some NAT firewalls, ;; using TURN is the remaining alternative. ;; -;; Note that TURN is a superset of STUN, so you don't need to configure STUN -;; if you are using TURN. +;; You don't need to configure both STUN and TURN, because TURN already includes +;; STUN functionality. ;; ;; The provided URL should follow one of these formats: ;; @@ -59,7 +61,7 @@ ;; From that check, you should get at least one Server-Reflexive Candidate ;; (type "srflx") AND one Relay Candidate (type "relay"). ;; -;turnURL=user:password@127.0.0.1:3478?transport=udp +;turnURL=user:password@198.51.100.1:3478?transport=udp ;; Certificate used for DTLS authentication. ;; @@ -77,6 +79,7 @@ ;; ;; Absolute path to the concatenated certificate (chain) file(s) + private key, ;; in PEM format. +;; ;pemCertificateRSA=/path/to/cert+key.pem ;pemCertificateECDSA=/path/to/cert+key.pem From fdbe294845c93042b76ab209a3ab49ec7bbfb28a Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 3 Nov 2020 18:04:22 +0100 Subject: [PATCH 40/69] Prepare release 6.15.0-0kurento1 --- debian/changelog | 7 ++++--- src/server/interface/elements.kmd.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index 5e673f4e3..b5b22d344 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ -kms-elements (6.14.1-0kurento1) UNRELEASED; urgency=medium +kms-elements (6.15.0-0kurento1) testing; urgency=medium - * UNRELEASED + * Prepare release 6.15.0-0kurento1 + * debian: update versions of Kurento dependencies - -- Juan Navarro Thu, 18 Jun 2020 11:43:48 +0200 + -- Juan Navarro Tue, 03 Nov 2020 18:04:22 +0100 kms-elements (6.14.0-0kurento1) testing; urgency=medium diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 0cb0eb37a..99e71add0 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.14.1-dev", + "version": "6.15.0", "kurentoVersion": "^6.7.0", "code": { "kmd": { From 02fdd64d8a17b68e93bba97d7180f8b70688ea71 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 6 Nov 2020 11:55:56 +0100 Subject: [PATCH 41/69] Prepare for next development iteration --- debian/changelog | 6 ++++++ src/server/interface/elements.kmd.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index b5b22d344..56bc09851 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +kms-elements (6.15.1-0kurento1) UNRELEASED; urgency=medium + + * UNRELEASED + + -- Juan Navarro Fri, 06 Nov 2020 11:55:56 +0100 + kms-elements (6.15.0-0kurento1) testing; urgency=medium * Prepare release 6.15.0-0kurento1 diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 99e71add0..36611b732 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.15.0", + "version": "6.15.1-dev", "kurentoVersion": "^6.7.0", "code": { "kmd": { From 25de1c710b2a8c142aecf8130be3662b866c2dcb Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 6 Nov 2020 19:18:53 +0100 Subject: [PATCH 42/69] Update all dependency versions to 6.15.1 --- debian/control | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/debian/control b/debian/control index 03dfa6cc6..a01714e77 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,9 @@ Build-Depends: debhelper (>= 9), gstreamer1.5-plugins-bad, gstreamer1.5-plugins-good, gstreamer1.5-x, - kms-cmake-utils (>= 6.14.0), - kms-core-dev (>= 6.14.0), - kurento-module-creator (>= 6.14.0), + kms-cmake-utils (>= 6.15.1), + kms-core-dev (>= 6.15.1), + kurento-module-creator (>= 6.15.1), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, @@ -32,7 +32,7 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer1.5-nice, - kms-core (>= 6.14.0), + kms-core (>= 6.15.1), openh264-gst-plugins-bad-1.5, openwebrtc-gst-plugins Breaks: kms-elements-6.0 @@ -44,9 +44,9 @@ Package: kms-elements-dev Architecture: any Section: libdevel Depends: kms-elements (= ${binary:Version}), - kms-cmake-utils (>= 6.14.0), - kms-core-dev (>= 6.14.0), - kurento-module-creator (>= 6.14.0), + kms-cmake-utils (>= 6.15.1), + kms-core-dev (>= 6.15.1), + kurento-module-creator (>= 6.15.1), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, From 4e23ff027e55ce7004666af54d75c1459f848523 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 9 Nov 2020 15:26:40 +0100 Subject: [PATCH 43/69] Update URLs to latest Kurento doc versions in ReadTheDocs --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 51beb2ae0..c3022b405 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,14 @@ Useful Links Usage: -* [Installation Guide](http://doc-kurento.readthedocs.io/en/stable/user/installation.html) -* [Compilation Guide](http://doc-kurento.readthedocs.io/en/stable/dev/dev_guide.html#developing-kms) -* [Contribution Guide](http://doc-kurento.readthedocs.io/en/stable/project/contribute.html) +* [Installation Guide](https://doc-kurento.readthedocs.io/en/latest/user/installation.html) +* [Compilation Guide](https://doc-kurento.readthedocs.io/en/latest/dev/dev_guide.html#developing-kms) +* [Contribution Guide](https://doc-kurento.readthedocs.io/en/latest/project/contribute.html) Issues: * [Bug Tracker](https://github.com/Kurento/bugtracker/issues) -* [Support](http://doc-kurento.readthedocs.io/en/stable/user/support.html) +* [Support](https://doc-kurento.readthedocs.io/en/latest/user/support.html) News: From 904d229465c33a763285e30e9077d1adc85e7fd6 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 4 Dec 2020 11:54:09 +0100 Subject: [PATCH 44/69] Fail when IPv6 is not available, avoid getting stuck When IPv6 is not available, this test would get stuck forever because the GSTreamer pipeline keeps failing with a warning (not shown with the default log level) and the Kurento garbage collector would not kick in and delete the elements until the default timeout of 4 minutes. Fixes https://github.com/Kurento/bugtracker/issues/560 --- tests/server/rtpEndpoint.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/server/rtpEndpoint.cpp b/tests/server/rtpEndpoint.cpp index c1b4800a7..06b7415e7 100644 --- a/tests/server/rtpEndpoint.cpp +++ b/tests/server/rtpEndpoint.cpp @@ -250,7 +250,10 @@ connection_state_changes_ipv6 () test_suite * init_unit_test_suite ( int , char *[] ) { - test_suite *test = BOOST_TEST_SUITE ( "WebRtcEndpoint" ); + test_suite *test = BOOST_TEST_SUITE ( "RtpEndpoint" ); + + // Prevents getting stuck for the default 4 minutes when IPv6 is not available. + MediaSet::setCollectorInterval(std::chrono::seconds (5)); test->add (BOOST_TEST_CASE ( &media_state_changes ), 0, /* timeout */ 15); test->add (BOOST_TEST_CASE ( &connection_state_changes ), 0, /* timeout */ 15); From 968a03cbc60fc5d49379c373a1006ea84cd1b828 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 9 Dec 2020 19:52:22 +0100 Subject: [PATCH 45/69] recorder: allow duplicate STOP requests (don't fail) If the recorder is already stopped, just warn about it but don't raise an error event. --- .../recorderendpoint/kmsrecorderendpoint.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index c918bfd5c..4f66a722f 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -777,19 +777,18 @@ kms_recorder_endpoint_stopped (KmsUriEndpoint * obj, GError ** error) state = kms_uri_endpoint_get_state (KMS_URI_ENDPOINT (self)); if (self->priv->stopped) { - g_set_error_literal (error, KMS_URI_ENDPOINT_ERROR, - KMS_URI_ENDPOINT_INVALID_TRANSITION, "Recorder is stopped"); - GST_ERROR_OBJECT (self, "No stop"); - return FALSE; + GST_WARNING_OBJECT (self, + "Stop requested, but recorder is already stopped"); + return TRUE; } else if (state == KMS_URI_ENDPOINT_STATE_STOP || self->priv->transition == KMS_RECORDER_ENDPOINT_STOPPING) { g_set_error_literal (error, KMS_URI_ENDPOINT_ERROR, - KMS_URI_ENDPOINT_INVALID_TRANSITION, "Recorder is stopping"); + KMS_URI_ENDPOINT_INVALID_TRANSITION, "Stop requested, but recorder is already stopping"); return FALSE; } else if (self->priv->transition != KMS_RECORDER_ENDPOINT_COMPLETED) { g_set_error (error, KMS_URI_ENDPOINT_ERROR, KMS_URI_ENDPOINT_INVALID_TRANSITION, - "Can not go to stop. Recorder is %s", + "Cannot stop. Recorder status is '%s'", transition[self->priv->transition]); return FALSE; } From b4f3d1efc38c2a39158a34617ef0d476c290a37f Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 8 Jan 2021 16:21:55 +0100 Subject: [PATCH 46/69] player, recorder: add notes about special chars (@, :) + text review --- .../elements.PlayerEndpoint.kmd.json | 153 ++++++++++++------ .../elements.RecorderEndpoint.kmd.json | 121 ++++++++------ 2 files changed, 181 insertions(+), 93 deletions(-) diff --git a/src/server/interface/elements.PlayerEndpoint.kmd.json b/src/server/interface/elements.PlayerEndpoint.kmd.json index 23ebca7ef..a2d8fad92 100644 --- a/src/server/interface/elements.PlayerEndpoint.kmd.json +++ b/src/server/interface/elements.PlayerEndpoint.kmd.json @@ -3,51 +3,114 @@ { "name": "PlayerEndpoint", "extends": "UriEndpoint", - "doc": " -

    - Retrieves content from seekable or non-seekable sources, and injects them into :term:`KMS`, so they can be delivered to any Filter or Endpoint in the same MediaPipeline. Following URI schemas are supported: -

      -
    • - Files: Mounted in the local file system. -
      • file:///path/to/file
      -
    • -
    • - RTSP: Those of IP cameras would be a good example. -
        -
      • rtsp://
      • -
      • rtsp://username:password@
      • -
      -
    • -
    • - HTTP: Any file available in an HTTP server -
        -
      • http(s):///path/to/file
      • -
      • http(s)://username:password@/path/to/file
      • -
      -
    • -
    -

    -

    - For the player to stream the contents of the file, the server must have access to the resource. In case of local files, the user running the process must have read permissions over the file. For network resources, the path to the resource must be accessible: IP and port access not blocked, correct credentials, etc.The resource location can’t be changed after the player is created, and a new player should be created for streaming a different resource. -

    -

    - The list of valid operations is -

      -
    • *play*: starts streaming media. If invoked after pause, it will resume playback.
    • -
    • *stop*: stops streaming media. If play is invoked afterwards, the file will be streamed from the beginning.
    • -
    • *pause*: pauses media streaming. Play must be invoked in order to resume playback.
    • -
    • *seek*: If the source supports “jumps” in the timeline, then the PlayerEndpoint can -
        -
      • *setPosition*: allows to set the position in the file.
      • -
      • *getPosition*: returns the current position being streamed.
      • -
      -
    • -
    -

    -

    -

    Events fired:

    -
    • EndOfStreamEvent: If the file is streamed completely.
    -

    + "doc": "Retrieves content from external sources. +

    + PlayerEndpoint will access the given resource, read all available data, and + inject it into :term:`KMS`. Once this is is done, the injected video or audio + will be available for passing through any other Filter or Endpoint to which + the PlayerEndpoint gets connected. +

    +

    + The source can provide either seekable or non-seekable media; this will + dictate whether the PlayerEndpoint is able (or not) to seek through the file, + for example to jump to any given timestamp. +

    +

    The Source URI supports these formats:

    +
      +
    • + File: A file path that will be read from the local file system. Example: +
        +
      • file:///path/to/file
      • +
      +
    • +
    • + HTTP: Any file available in an HTTP server. Examples: +
        +
      • http(s)://{server-ip}/path/to/file
      • +
      • + + http(s)://{username}:{password}@{server-ip}:{server-port}/path/to/file + +
      • +
      +
    • +
    • + RTSP: Typically used to capture a feed from an IP Camera. Examples: +
        +
      • rtsp://{server-ip}
      • +
      • + + rtsp://{username}:{password}@{server-ip}:{server-port}/path/to/file + +
      • +
      +
    • +
    • + + NOTE (for current versions of Kurento 6.x): special characters are not + supported in {username} or {password}. + + This means that {username} cannot contain colons + (:), and {password} cannot contain 'at' signs + (@). This is a limitation of GStreamer 1.8 (the underlying + media framework behind Kurento), and is already fixed in newer versions + (which the upcoming Kurento 7.x will use). +
    • +
    • + + NOTE (for upcoming Kurento 7.x): special characters in + {username} or {password} must be url-encoded. + + This means that colons (:) should be replaced with + %3A, and 'at' signs (@) should be replaced with + %40. +
    • +
    +

    + Note that + PlayerEndpoint requires read permissions to the source + ; otherwise, the media server won't be able to retrieve any data, and an + :rom:evt:`Error` will be fired. Make sure your application subscribes to this + event, otherwise troubleshooting issues will be difficult. +

    + +

    The list of valid operations is:

    +
      +
    • + play + : Starts streaming media. If invoked after pause, it will resume playback. +
    • +
    • + stop + : Stops streaming media. If play is invoked afterwards, the file will be + streamed from the beginning. +
    • +
    • + pause + : Pauses media streaming. Play must be invoked in order to resume playback. +
    • +
    • + seek + : If the source supports seeking to a different time position, then the + PlayerEndpoint can: +
        +
      • + setPosition + : Allows to set the position in the file. +
      • +
      • + getPosition + : Returns the current position being streamed. +
      • +
      +
    • +
    +

    Events fired

    +
      +
    • + EndOfStreamEvent: If the file is streamed completely. +
    • +
    ", "constructor": { diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index 8a636bccc..6b4a261ea 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -5,13 +5,14 @@ "extends": "UriEndpoint", "doc": "Provides functionality to store media contents.

    - The RecorderEndpoint can store media in local files or in a network resource. - It receives a media stream from another :rom:cls:`MediaElement` (i.e. the - source), and stores it in the designated location. + RecorderEndpoint can store media into local files or send it to a remote + network storage. When another :rom:cls:`MediaElement` is connected to a + RecorderEndpoint, the media coming from the former will be encapsulated into + the selected recording format and stored in the designated location.

    - The following information has to be provided in order to create a - RecorderEndpoint, and cannot be changed afterwards: + These parameters must be provided to create a RecorderEndpoint, and they + cannot be changed afterwards:

    • @@ -19,7 +20,8 @@ are supported:
      • - File: A file path that exists in the local file system. + File: A file path that will be written into the local file system. + Example:
        • file:///path/to/file
        @@ -27,67 +29,87 @@
      • HTTP: A POST request will be used against a remote server. The server must support using the chunked encoding mode (HTTP header - Transfer-Encoding: chunked). + Transfer-Encoding: chunked). Examples:
        • http(s)://{server-ip}/path/to/file
        • - http(s)://{username}:{password}@{server-ip}/path/to/file + http(s)://{username}:{password}@{server-ip}:{server-port}/path/to/file
      • +
      • + Relative URIs (with no schema) are supported. They are completed by + prepending a default URI defined by property defaultPath. This + property is defined in the configuration file + /etc/kurento/modules/kurento/UriEndpoint.conf.ini, and the + default value is file:///var/lib/kurento/ +
      • +
      • + + NOTE (for current versions of Kurento 6.x): special characters are not + supported in {username} or {password}. + + This means that {username} cannot contain colons + (:), and {password} cannot contain 'at' signs + (@). This is a limitation of GStreamer 1.8 (the underlying + media framework behind Kurento), and is already fixed in newer versions + (which the upcoming Kurento 7.x will use). +
      • +
      • + + NOTE (for upcoming Kurento 7.x): special characters in + {username} or {password} must be + url-encoded. + + This means that colons (:) should be replaced with + %3A, and 'at' signs (@) should be replaced + with %40. +
    • - Relative URIs (with no schema) are supported. They are completed by - prepending a default URI defined by property defaultPath. This - property is defined in the configuration file - /etc/kurento/modules/kurento/UriEndpoint.conf.ini, and the default - value is file:///var/lib/kurento/ -
    • -
    • - The Media Profile (:rom:attr:`MediaProfileSpecType`) used for + Media Profile (:rom:attr:`MediaProfileSpecType`), used for storage. This will determine the video and audio encoding. See below for more details about Media Profile.
    • - Optionally, the user can select if the endpoint will stop processing once - the EndOfStream event is detected. + EndOfStream (optional), a parameter that dictates if the + recording should be automatically stopped once the EOS event is detected.

    + Note that - RecorderEndpoint requires access to the resource where stream is going to be - recorded + RecorderEndpoint requires write permissions to the destination - . Otherwise, the media server won't be able to store any information, and an - :rom:evt:`Error` will be fired. Please note that if you haven't subscribed to - that type of event, you can be left wondering why your media is not being - saved, while the error message was ignored. + ; otherwise, the media server won't be able to store any information, and an + :rom:evt:`Error` will be fired. Make sure your application subscribes to this + event, otherwise troubleshooting issues will be difficult.

    • - To write local files (if you use file://), the user running the - media server (by default, user kurento) needs to have write - permissions for the requested path. + To write local files (if you use file://), the system user that + is owner of the media server process needs to have write permissions for the + requested path. By default, this user is named 'kurento'.
    • - To save into an HTTP server, the server must be accessible through the - network, and also have the correct access rights to the destination path. + To record through HTTP, the remote server must be accessible through the + network, and also have the correct write permissions for the destination + path.

    - The media profile is quite an important parameter, as it will determine - whether the server needs to perform on-the-fly transcoding of the media. If - the input stream codec if not compatible with the selected media profile, the - media will be transcoded into a suitable format. This will result in a higher - CPU load and will impact overall performance of the media server. + The Media Profile is quite an important parameter, as it will + determine whether the server needs to perform on-the-fly transcoding of the + media. If the input stream codec if not compatible with the selected media + profile, the media will be transcoded into a suitable format. This will result + in a higher CPU load and will impact overall performance of the media server.

    - For example: Say that your pipeline will receive VP8-encoded video from - WebRTC, and sends it to a RecorderEndpoint; depending on the format - selected... + For example: If your pipeline receives VP8-encoded video from WebRTC, + and sends it to a RecorderEndpoint; depending on the format selected...

    • @@ -116,21 +138,24 @@ as it gets it.

      - Stopping the recording process is done through the - stopAndWait method, which will return only after all the - information was stored correctly. If the file is empty, this means that no - media arrived at the recorder. + Recording must be stopped when no more data should be stored. + This can be with the stopAndWait method, which blocks and returns + only after all the information was stored correctly.

      + + If your output file is empty, this means that the recorder is waiting for + input media. + When another endpoint is connected to the recorder, by default both AUDIO and VIDEO media types are expected, unless specified otherwise when invoking the - connect method. Failing to provide both types, will result in teh recording - buffering the received media: it won't be written to the file until the - recording is stopped. This is due to the recorder waiting for the other type - of media to arrive, so they are synchronized. + connect method. Failing to provide both types, will result in the + RecorderEndpoint buffering the received media: it won't be written to the file + until the recording is stopped. The recorder waits until all types of media + start arriving, in order to synchronize them appropriately.

      - The source endpoint can be hot-swapped, while the recording is taking place. + The source endpoint can be hot-swapped while the recording is taking place. The recorded file will then contain different feeds. When switching video sources, if the new video has different size, the recorder will retain the size of the previous source. If the source is disconnected, the last frame @@ -139,9 +164,9 @@

      - It is recommended to start recording only after media arrives + It is recommended to start recording only after media arrives. - . For this, you may use the MediaFlowInStateChange and + For this, you may use the MediaFlowInStateChange and MediaFlowOutStateChange events of your endpoints, and synchronize the recording with the moment media comes into the Recorder. For example: From bef5735127a051171c8ff08beb0e73809b4f32aa Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 29 Jan 2021 17:49:40 +0100 Subject: [PATCH 47/69] recorder doc minor: improve some lines --- src/server/interface/elements.RecorderEndpoint.kmd.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index 6b4a261ea..5bc4d86c3 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -192,7 +192,7 @@ ", "constructor": { - "doc": "", + "doc": "Builder for the :rom:cls:`RecorderEndpoint`", "params": [ { "name": "mediaPipeline", @@ -201,16 +201,16 @@ }, { "name": "uri", - "doc": "URI where the recording will be stored. It has to be accessible to the KMS process. + "doc": "URI where the recording will be stored. It must be accessible from the media server process itself:

      • Local server resources: The user running the Kurento Media Server must have write permission over the file.
      • -
      • Network resources: Must be accessible from the server where the media server is running.
      • +
      • Network resources: Must be accessible from the network where the media server is running.
      ", "type": "String" }, { "name": "mediaProfile", - "doc": "Sets the media profile used for recording. If the profile is different than the one being recieved at the sink pad, media will be trnascoded, resulting in a higher CPU load. For instance, when recording a VP8 encoded video from a WebRTC endpoint in MP4, the load is higher that when recording in WEBM.", + "doc": "Sets the media profile used for recording. If the profile is different than the one being received at the sink pad, media will be transcoded, resulting in a higher CPU load. For instance, when recording a VP8 encoded video from a WebRTC endpoint in MP4, the load is higher that when recording to WEBM.", "type": "MediaProfileSpecType", "optional": true, "defaultValue": "WEBM" From f4fe60722cc436524be7be407b4be3aa7217e958 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 29 Jan 2021 17:50:16 +0100 Subject: [PATCH 48/69] ALL: change kms-api dependency range to the current release version This prevents language-specific tools from allowing a too wide range of compatible versions. Specifically, Maven will attempt to access ALL versions in the range, which meant that it would download manifests of all artifacts since 6.7.0. kms-omni-build/bin/set-versions.sh has been updated too, so future releases will have this range automatically updated to the newest release version. Fixes https://groups.google.com/g/kurento/c/7B5k_cZ2Ya0/ --- src/server/interface/elements.kmd.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 36611b732..3acea434e 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,7 +1,7 @@ { "name": "elements", "version": "6.15.1-dev", - "kurentoVersion": "^6.7.0", + "kurentoVersion": "^6.15.0", "code": { "kmd": { "java": { From c0da4dafb164eb38697526ea195450cdc0dd841c Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 5 Feb 2021 12:16:41 +0100 Subject: [PATCH 49/69] API: clearer explanations of RecvBandwidth, SendBandwidth --- .../elements.WebRtcEndpoint.kmd.json | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index 2938cd367..b61deadd7 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -160,9 +160,10 @@ original stream.

      - The default bandwidth range of the endpoint is - [100 kbps, 500 kbps], but it can be changed separately for - input/output directions and for audio/video streams. + Note that the default VideoSendBandwidth range of the + endpoint is a VERY conservative one, and leads to a low maximum video quality. + Most applications will probably want to increase this to higher values such as + 2000 kbps (2 mbps).

      @@ -173,34 +174,36 @@

      • - Input bandwidth: Configuration value used to inform remote peers about the - bitrate that can be pushed into this endpoint. + Input bandwidth: Values used to inform remote peers about the bitrate that + can be sent to this endpoint.
        • - {get,set}MinVideoRecvBandwidth: Minimum bitrate - requested on the received video stream. + MinVideoRecvBandwidth: Minimum input bitrate, requested + from WebRTC senders with REMB (Default: 30 Kbps).
        • - {get,set}Max{Audio,Video}RecvBandwidth: Maximum bitrate - expected for the received stream. + MaxAudioRecvBandwidth and + MaxVideoRecvBandwidth: Maximum input bitrate, signaled + in SDP Offers to WebRTC and RTP senders (Default: unlimited).
      • - Output bandwidth: Configuration values used to control bitrate of the output - video stream sent to remote peers. It is important to keep in mind that - pushed bitrate depends on network and remote peer capabilities. Remote peers - can also announce bandwidth limitation in their SDPs (through the - b={modifier}:{value} tag). Kurento will always enforce bitrate - limitations specified by the remote peer over internal configurations. + Output bandwidth: Values used to control bitrate of the video streams sent + to remote peers. It is important to keep in mind that pushed bitrate depends + on network and remote peer capabilities. Remote peers can also announce + bandwidth limitation in their SDPs (through the + b={modifier}:{value} attribute). Kurento will always enforce + bitrate limitations specified by the remote peer over internal + configurations.
        • - {get,set}MinVideoSendBandwidth: Minimum video bitrate - sent to remote peer. + MinVideoSendBandwidth: REMB override of minimum bitrate + sent to WebRTC receivers (Default: 100 Kbps).
        • - {get,set}MaxVideoSendBandwidth: Maximum video bitrate - sent to remote peer. + MaxVideoSendBandwidth: REMB override of maximum bitrate + sent to WebRTC receivers (Default: 500 Kbps).
        • RembParams.rembOnConnect: Initial local REMB bandwidth @@ -223,12 +226,8 @@ WebRtcElement. DataChannels can be configured to provide the following:

            -
          • - Reliable or partially reliable delivery of sent messages -
          • -
          • - In-order or out-of-order delivery of sent messages -
          • +
          • Reliable or partially reliable delivery of sent messages
          • +
          • In-order or out-of-order delivery of sent messages

          Unreliable, out-of-order delivery is equivalent to raw UDP semantics. The From fe6092bc1adc9df1f1cbbbc2cb4af48dda0254f3 Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:35:37 -0300 Subject: [PATCH 50/69] Add niceAgentIceTcp configuration option and API methods for it (#26) * Add niceAgentIceTcp configuration option and API methods for it This adds the ability to configure libnice`s NiceAgent ice-tcp property through WebRtcEndpoint.conf.ini or via API The rationale behind this is to give the ability to deactivate TCP candidate gathering in setups where they aren`t needed, thus potentially speeding up the gathering process and connectivity checks Property reference: https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--ice-tcp * rename to "iceTcp": don't leak name of internal class Avoid leaking that the feature is internally implemented by a class named NiceAgent, which the users don't care about. * review docs, use same text in API and config Co-authored-by: Juan Navarro --- .../webrtcendpoint/kmswebrtcbaseconnection.c | 11 ++++++++ .../webrtcendpoint/kmswebrtcbaseconnection.h | 2 ++ .../webrtcendpoint/kmswebrtcendpoint.c | 22 ++++++++++++++- .../webrtcendpoint/kmswebrtcsession.c | 23 +++++++++++++++ .../webrtcendpoint/kmswebrtcsession.h | 1 + src/server/config/WebRtcEndpoint.conf.ini | 13 +++++++++ .../objects/WebRtcEndpointImpl.cpp | 28 +++++++++++++++++++ .../objects/WebRtcEndpointImpl.hpp | 3 ++ .../elements.WebRtcEndpoint.kmd.json | 16 +++++++++++ 9 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c index e5a020438..6960e616b 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c @@ -253,6 +253,17 @@ kms_webrtc_base_connection_set_network_ifs_info (KmsWebRtcBaseConnection * } } +void +kms_webrtc_base_connection_set_ice_tcp (KmsWebRtcBaseConnection *self, + gboolean ice_tcp) +{ + if (KMS_IS_ICE_NICE_AGENT (self->agent)) { + KmsIceNiceAgent *nice_agent = KMS_ICE_NICE_AGENT (self->agent); + g_object_set ( + kms_ice_nice_agent_get_agent (nice_agent), "ice-tcp", ice_tcp, NULL); + } +} + void kms_webrtc_base_connection_set_stun_server_info (KmsWebRtcBaseConnection * self, const gchar * ip, guint port) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.h b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.h index 63f1183b4..43b44db60 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.h +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.h @@ -81,6 +81,8 @@ gchar *kms_webrtc_base_connection_get_certificate_pem (KmsWebRtcBaseConnection * self); void kms_webrtc_base_connection_set_network_ifs_info (KmsWebRtcBaseConnection * self, const gchar * net_names); +void kms_webrtc_base_connection_set_ice_tcp (KmsWebRtcBaseConnection *self, + gboolean ice_tcp); void kms_webrtc_base_connection_set_stun_server_info (KmsWebRtcBaseConnection * self, const gchar * stun_server_ip, guint stun_server_port); void kms_webrtc_base_connection_set_relay_info (KmsWebRtcBaseConnection * self, diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index 70307b6cc..c4017f666 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -58,6 +58,7 @@ G_DEFINE_TYPE (KmsWebrtcEndpoint, kms_webrtc_endpoint, #define DEFAULT_EXTERNAL_ADDRESS NULL #define DEFAULT_EXTERNAL_IPV4 NULL #define DEFAULT_EXTERNAL_IPV6 NULL +#define DEFAULT_ICE_TCP TRUE enum { @@ -70,6 +71,7 @@ enum PROP_EXTERNAL_ADDRESS, PROP_EXTERNAL_IPV4, PROP_EXTERNAL_IPV6, + PROP_ICE_TCP, N_PROPERTIES }; @@ -105,6 +107,7 @@ struct _KmsWebrtcEndpointPrivate gchar *external_address; gchar *external_ipv4; gchar *external_ipv6; + gboolean ice_tcp; }; /* Internal session management begin */ @@ -341,6 +344,8 @@ kms_webrtc_endpoint_create_session_internal (KmsBaseSdpEndpoint * base_sdp, webrtc_sess, "external-ipv4", G_BINDING_DEFAULT); g_object_bind_property (self, "external-ipv6", webrtc_sess, "external-ipv6", G_BINDING_DEFAULT); + g_object_bind_property (self, "ice-tcp", + webrtc_sess, "ice-tcp", G_BINDING_DEFAULT); g_object_set (webrtc_sess, "stun-server", self->priv->stun_server_ip, "stun-server-port", self->priv->stun_server_port, @@ -349,7 +354,9 @@ kms_webrtc_endpoint_create_session_internal (KmsBaseSdpEndpoint * base_sdp, "network-interfaces", self->priv->network_interfaces, "external-address", self->priv->external_address, "external-ipv4", self->priv->external_ipv4, - "external-ipv6", self->priv->external_ipv6, NULL); + "external-ipv6", self->priv->external_ipv6, + "ice-tcp", self->priv->ice_tcp, + NULL); g_signal_connect (webrtc_sess, "on-ice-candidate", G_CALLBACK (on_ice_candidate), self); @@ -535,6 +542,9 @@ kms_webrtc_endpoint_set_property (GObject * object, guint prop_id, g_free (self->priv->external_ipv6); self->priv->external_ipv6 = g_value_dup_string (value); break; + case PROP_ICE_TCP: + self->priv->ice_tcp = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -576,6 +586,9 @@ kms_webrtc_endpoint_get_property (GObject * object, guint prop_id, case PROP_EXTERNAL_IPV6: g_value_set_string (value, self->priv->external_ipv6); break; + case PROP_ICE_TCP: + g_value_set_boolean (value, self->priv->ice_tcp); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -815,6 +828,12 @@ kms_webrtc_endpoint_class_init (KmsWebrtcEndpointClass * klass) "External (public) IPv6 address of the media server", DEFAULT_EXTERNAL_IPV6, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ICE_TCP, + g_param_spec_boolean ("ice-tcp", + "iceTcp", + "Enable ICE-TCP candidate gathering", + DEFAULT_ICE_TCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * KmsWebrtcEndpoint::on-ice-candidate: * @self: the object which received the signal @@ -951,6 +970,7 @@ kms_webrtc_endpoint_init (KmsWebrtcEndpoint * self) self->priv->external_address = DEFAULT_EXTERNAL_ADDRESS; self->priv->external_ipv4 = DEFAULT_EXTERNAL_IPV4; self->priv->external_ipv6 = DEFAULT_EXTERNAL_IPV6; + self->priv->ice_tcp = DEFAULT_ICE_TCP; self->priv->loop = kms_loop_new (); g_object_get (self->priv->loop, "context", &self->priv->context, NULL); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 59ddef95c..767b03043 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -58,6 +58,7 @@ G_DEFINE_TYPE (KmsWebrtcSession, kms_webrtc_session, KMS_TYPE_BASE_RTP_SESSION); #define DEFAULT_EXTERNAL_ADDRESS NULL #define DEFAULT_EXTERNAL_IPV4 NULL #define DEFAULT_EXTERNAL_IPV6 NULL +#define DEFAULT_ICE_TCP TRUE #define IP_VERSION_6 6 @@ -94,6 +95,7 @@ enum PROP_EXTERNAL_ADDRESS, PROP_EXTERNAL_IPV4, PROP_EXTERNAL_IPV6, + PROP_ICE_TCP, N_PROPERTIES }; @@ -804,6 +806,13 @@ kms_webrtc_session_set_network_ifs_info (KmsWebrtcSession * self, self->network_interfaces); } +static void +kms_webrtc_session_set_ice_tcp (KmsWebrtcSession *self, + KmsWebRtcBaseConnection *conn) +{ + kms_webrtc_base_connection_set_ice_tcp (conn, self->ice_tcp); +} + static void kms_webrtc_session_set_stun_server_info (KmsWebrtcSession * self, KmsWebRtcBaseConnection * conn) @@ -849,6 +858,7 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self) KmsWebRtcBaseConnection *conn = KMS_WEBRTC_BASE_CONNECTION (v); kms_webrtc_session_set_network_ifs_info (self, conn); + kms_webrtc_session_set_ice_tcp (self, conn); kms_webrtc_session_set_stun_server_info (self, conn); kms_webrtc_session_set_relay_info (self, conn); @@ -1759,6 +1769,9 @@ kms_webrtc_session_set_property (GObject * object, guint prop_id, g_free (self->external_ipv6); self->external_ipv6 = g_value_dup_string (value); break; + case PROP_ICE_TCP: + self->ice_tcp = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1803,6 +1816,9 @@ kms_webrtc_session_get_property (GObject * object, guint prop_id, case PROP_EXTERNAL_IPV6: g_value_set_string (value, self->external_ipv6); break; + case PROP_ICE_TCP: + g_value_set_boolean (value, self->ice_tcp); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1943,6 +1959,7 @@ kms_webrtc_session_init (KmsWebrtcSession * self) self->external_address = DEFAULT_EXTERNAL_ADDRESS; self->external_ipv4= DEFAULT_EXTERNAL_IPV4; self->external_ipv6 = DEFAULT_EXTERNAL_IPV6; + self->ice_tcp = DEFAULT_ICE_TCP; self->gather_started = FALSE; self->data_channels = g_hash_table_new_full (g_direct_hash, @@ -2060,6 +2077,12 @@ kms_webrtc_session_class_init (KmsWebrtcSessionClass * klass) "External (public) IPv6 address of the media server", DEFAULT_EXTERNAL_IPV6, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_ICE_TCP, + g_param_spec_boolean ("ice-tcp", + "iceTcp", + "Enable ICE-TCP candidate gathering", + DEFAULT_ICE_TCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DATA_CHANNEL_SUPPORTED, g_param_spec_boolean ("data-channel-supported", "Data channel supported", diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h index 7575f860b..4f016d1f3 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h @@ -74,6 +74,7 @@ struct _KmsWebrtcSession gchar *external_address; gchar *external_ipv4; gchar *external_ipv6; + gboolean ice_tcp; guint16 min_port; guint16 max_port; diff --git a/src/server/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini index eb6325047..def81997b 100644 --- a/src/server/config/WebRtcEndpoint.conf.ini +++ b/src/server/config/WebRtcEndpoint.conf.ini @@ -127,3 +127,16 @@ ;; ;externalAddress=198.51.100.1 ;externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334 + +;; Enable ICE-TCP candidate gathering. +;; +;; This setting enables or disables using TCP for ICE candidate gathering in +;; the underlying libnice library: +;; https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--ice-tcp +;; +;; You might want to disable ICE-TCP to potentially speed up ICE gathering +;; by avoiding TCP candidates in scenarios where they are not needed. +;; +;; is either 1 (ON) or 0 (OFF). Default: 1 (ON). +;; +;iceTcp=1 diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index 7cd29fa57..f91bfee20 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -56,11 +56,13 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define PARAM_EXTERNAL_IPV4 "externalIPv4" #define PARAM_EXTERNAL_IPV6 "externalIPv6" #define PARAM_NETWORK_INTERFACES "networkInterfaces" +#define PARAM_ICE_TCP "iceTcp" #define PROP_EXTERNAL_ADDRESS "external-address" #define PROP_EXTERNAL_IPV4 "external-ipv4" #define PROP_EXTERNAL_IPV6 "external-ipv6" #define PROP_NETWORK_INTERFACES "network-interfaces" +#define PROP_ICE_TCP "ice-tcp" namespace kurento { @@ -558,6 +560,16 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, " you can set one or default to ICE automatic discovery"); } + gboolean iceTcp; + if (getConfigValue (&iceTcp, PARAM_ICE_TCP)) { + GST_INFO ("ICE-TCP candidate gathering is %s", + iceTcp ? "ENABLED" : "DISABLED"); + g_object_set (G_OBJECT (element), PROP_ICE_TCP, iceTcp, NULL); + } else { + GST_DEBUG ("ICE-TCP option not found in config;" + " you can set one or default to 1 (TRUE)"); + } + uint stunPort = 0; if (!getConfigValue (&stunPort, "stunServerPort", @@ -746,6 +758,22 @@ WebRtcEndpointImpl::setNetworkInterfaces (const std::string &networkInterfaces) networkInterfaces.c_str(), NULL); } +bool +WebRtcEndpointImpl::getIceTcp () +{ + bool ret; + + g_object_get (G_OBJECT (element), "ice-tcp", &ret, NULL); + + return ret; +} + +void +WebRtcEndpointImpl::setIceTcp (bool iceTcp) +{ + g_object_set (G_OBJECT (element), "ice-tcp", iceTcp, NULL); +} + std::string WebRtcEndpointImpl::getStunServerAddress () { diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.hpp b/src/server/implementation/objects/WebRtcEndpointImpl.hpp index b3f0586b7..63fca4c7b 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.hpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.hpp @@ -58,6 +58,9 @@ class WebRtcEndpointImpl : public BaseRtpEndpointImpl, std::string getNetworkInterfaces () override; void setNetworkInterfaces (const std::string &networkInterfaces) override; + bool getIceTcp () override; + void setIceTcp (bool iceTcp) override; + std::string getStunServerAddress () override; void setStunServerAddress (const std::string &stunServerAddress) override; diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index b61deadd7..c84ddc2dc 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -303,6 +303,22 @@ ", "type": "String" }, + { + "name": "iceTcp", + "doc": "Enable ICE-TCP candidate gathering. +

          + This setting enables or disables using TCP for ICE candidate gathering in the + underlying libnice library: + https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--ice-tcp +

          +

          + You might want to disable ICE-TCP to potentially speed up ICE gathering by + avoiding TCP candidates in scenarios where they are not needed. +

          +

          iceTcp is either 1 (ON) or 0 (OFF). Default: 1 (ON).

          + ", + "type": "boolean" + }, { "name": "stunServerAddress", "doc": "STUN server IP address. From fbaddbc5f812ddab05a72d70605b4bc1f2ae2223 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 19 Feb 2021 14:42:49 +0100 Subject: [PATCH 51/69] recorder: add config to choose gaps fix method (#31) ## What is the current behavior you want to change? Allow choosing between the original broken behavior or the recorder (keep gaps) and the new one introduced in Kurento 6.15.0 (remove gaps in recordings): https://doc-kurento.readthedocs.io/en/6.15.0/project/relnotes/v6_15_0.html#fixed-recorder-synchronization-on-streaming-gaps ## What is the new behavior provided by this change? * New CONFIG FILE: **RecorderEndpoint.conf.ini** * New CONFIG OPTION: **gapsFix = (NONE | GENPTS)** * DEFAULT setting: `gapsFix=NONE` ## How has this been tested? Debug log to show that the feature is enabled. Not really tested yet (will publish experimental packages for it) ## Types of changes Kurento 6.15.0 changed the recording behavior because a recording with gaps in it should be considered a bug, thus an invalid file. So the breaking change was allowed within minor versions because it was considered as a bug fix. But XKCD 1172 came to bite us because we need that the total duration of the recordings matches with the total duration of the sessions that were recorded, which does not happen if you remove timestamp gaps from recordings. So we need to roll back the change. Doc changes already included in the PR. --- .../recorderendpoint/CMakeLists.txt | 15 ++ .../recorderendpoint/kmsrecorderendpoint.c | 34 ++++- .../kmsrecordergapsfixmethod.h | 34 +++++ src/server/config/RecorderEndpoint.conf.ini | 20 +++ .../objects/RecorderEndpointImpl.cpp | 13 ++ .../elements.RecorderEndpoint.kmd.json | 128 ++++++++++++++++++ 6 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 src/gst-plugins/recorderendpoint/kmsrecordergapsfixmethod.h create mode 100644 src/server/config/RecorderEndpoint.conf.ini diff --git a/src/gst-plugins/recorderendpoint/CMakeLists.txt b/src/gst-plugins/recorderendpoint/CMakeLists.txt index 20c7d9129..2272561fa 100644 --- a/src/gst-plugins/recorderendpoint/CMakeLists.txt +++ b/src/gst-plugins/recorderendpoint/CMakeLists.txt @@ -14,6 +14,19 @@ set(KMS_RECORDERENDPOINT_HEADERS kmsrecorderendpoint.h ) +set(KMS_RECORDERENDPOINT_ENUM_HEADERS + kmsrecordergapsfixmethod.h +) + +list(APPEND KMS_RECORDERENDPOINT_HEADERS ${KMS_RECORDERENDPOINT_ENUM_HEADERS}) +add_glib_enumtypes( + KMS_RECORDERENDPOINT_SOURCES + KMS_RECORDERENDPOINT_HEADERS + kms-recorder-enumtypes + KMS + ${KMS_RECORDERENDPOINT_ENUM_HEADERS} +) + add_library(recorderendpoint MODULE ${KMS_RECORDERENDPOINT_SOURCES} ${KMS_RECORDERENDPOINT_HEADERS}) if(SANITIZERS_ENABLED) add_sanitizers(recorderendpoint) @@ -21,6 +34,8 @@ endif() set_property (TARGET recorderendpoint PROPERTY INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../../.. ${gstreamer-1.5_INCLUDE_DIRS} ${KmsGstCommons_INCLUDE_DIRS} diff --git a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c index 4f66a722f..0531763df 100644 --- a/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c +++ b/src/gst-plugins/recorderendpoint/kmsrecorderendpoint.c @@ -41,11 +41,15 @@ #include "kmsavmuxer.h" #include "kmsksrmuxer.h" +#include "kmsrecordergapsfixmethod.h" +#include "kms-recorder-enumtypes.h" + #define PLUGIN_NAME "recorderendpoint" #define RECORDER_DEFAULT_SUFFIX "_default" #define DEFAULT_RECORDING_PROFILE KMS_RECORDING_PROFILE_NONE +#define DEFAULT_GAPS_FIX KMS_RECORDER_GAPS_FIX_NONE #define KMS_BASE_TIME_KEY "base-time-key" G_DEFINE_QUARK (KMS_BASE_TIME_KEY, base_time_key); @@ -92,6 +96,7 @@ enum PROP_0, PROP_DVR, PROP_PROFILE, + PROP_GAPS_FIX, N_PROPERTIES }; @@ -133,6 +138,7 @@ typedef struct _KmsRecorderStats struct _KmsRecorderEndpointPrivate { KmsRecordingProfile profile; + KmsRecorderGapsFixMethod gaps_fix; GstClockTime paused_time; GstClockTime paused_start; gboolean use_dvr; @@ -373,10 +379,18 @@ recv_sample (GstAppSink * appsink, gpointer user_data) // The 'paused_time' doesn't account exactly for all the time, it is missing // some milliseconds. Maybe due to latency in upstream elements? + GstClockTime common_offset = self->priv->paused_time; + + if (self->priv->gaps_fix == KMS_RECORDER_GAPS_FIX_GENPTS) { + // In GenPTS mode, add the total time that has been lost in the form + // of gaps, typically caused by packet loss from an RTP source. + common_offset += base_time->audio_gaps; + } + if (GST_CLOCK_TIME_IS_VALID (base_time->pts) && GST_BUFFER_PTS_IS_VALID (buffer)) { - const GstClockTime offset = - base_time->pts + base_time->audio_gaps + self->priv->paused_time; + const GstClockTime offset = common_offset + base_time->pts; + // PTS -= offset, but preventing underflows. if (GST_BUFFER_PTS (buffer) > offset) { GST_BUFFER_PTS (buffer) -= offset; @@ -387,8 +401,8 @@ recv_sample (GstAppSink * appsink, gpointer user_data) if (GST_CLOCK_TIME_IS_VALID (base_time->dts) && GST_BUFFER_DTS_IS_VALID (buffer)) { - const GstClockTime offset = - base_time->dts + base_time->audio_gaps + self->priv->paused_time; + const GstClockTime offset = common_offset + base_time->dts; + // DTS -= offset, but preventing underflows. if (GST_BUFFER_DTS (buffer) > offset) { GST_BUFFER_DTS (buffer) -= offset; @@ -1438,6 +1452,9 @@ kms_recorder_endpoint_set_property (GObject * object, guint property_id, break; } + case PROP_GAPS_FIX: + self->priv->gaps_fix = g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1460,6 +1477,10 @@ kms_recorder_endpoint_get_property (GObject * object, guint property_id, g_value_set_enum (value, self->priv->profile); break; } + case PROP_GAPS_FIX:{ + g_value_set_enum (value, self->priv->gaps_fix); + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1956,6 +1977,10 @@ kms_recorder_endpoint_class_init (KmsRecorderEndpointClass * klass) "The profile used for encapsulating the media", KMS_TYPE_RECORDING_PROFILE, DEFAULT_RECORDING_PROFILE, G_PARAM_READWRITE); + obj_properties[PROP_GAPS_FIX] = g_param_spec_enum ("gaps-fix", + "Gaps fix method", "The method used to fix gaps in the stream", + KMS_TYPE_RECORDER_GAPS_FIX_METHOD, DEFAULT_GAPS_FIX, G_PARAM_READWRITE); + g_object_class_install_properties (gobject_class, N_PROPERTIES, obj_properties); @@ -2088,6 +2113,7 @@ kms_recorder_endpoint_init (KmsRecorderEndpoint * self) g_object_unref); self->priv->profile = DEFAULT_RECORDING_PROFILE; + self->priv->gaps_fix = DEFAULT_GAPS_FIX; self->priv->paused_time = G_GUINT64_CONSTANT (0); self->priv->paused_start = GST_CLOCK_TIME_NONE; diff --git a/src/gst-plugins/recorderendpoint/kmsrecordergapsfixmethod.h b/src/gst-plugins/recorderendpoint/kmsrecordergapsfixmethod.h new file mode 100644 index 000000000..428afd30d --- /dev/null +++ b/src/gst-plugins/recorderendpoint/kmsrecordergapsfixmethod.h @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2021 Kurento (http://kurento.org/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __KMS_RECORDER_GAPS_FIX_METHOD_H__ +#define __KMS_RECORDER_GAPS_FIX_METHOD_H__ + +#include + +G_BEGIN_DECLS + +typedef enum +{ + KMS_RECORDER_GAPS_FIX_NONE, + KMS_RECORDER_GAPS_FIX_GENPTS, + KMS_RECORDER_GAPS_FIX_FILL_IF_TRANSCODING, +} KmsRecorderGapsFixMethod; + +G_END_DECLS + +#endif /* __KMS_RECORDER_GAPS_FIX_METHOD_H__ */ diff --git a/src/server/config/RecorderEndpoint.conf.ini b/src/server/config/RecorderEndpoint.conf.ini new file mode 100644 index 000000000..237d73b79 --- /dev/null +++ b/src/server/config/RecorderEndpoint.conf.ini @@ -0,0 +1,20 @@ +;; How to fix gaps when they are found in the recorded stream. +;; +;; Gaps are typically caused by packet loss in the input streams, such as when +;; an RTP or WebRTC media flow suffers from network congestion and some packets +;; don't arrive at the media server. +;; +;; Possible values: +;; +;; * NONE: Do not fix gaps. Leave the stream as-is, and store it with any gaps +;; that the stream might have. This is the best choice if you need consistent +;; recording lengths, or if you are anyway going to post-process the +;; recordings. +;; +;; * GENPTS: Adjust timestamps to generate a smooth progression over all frames. +;; This technique rewrites the timestamp of all frames, so that gaps are +;; corrected. It provides the best experience for recordings that need to be +;; played as-is (i.e. they won't be post-processed). +;; +;; Default: NONE. +;gapsFix=NONE diff --git a/src/server/implementation/objects/RecorderEndpointImpl.cpp b/src/server/implementation/objects/RecorderEndpointImpl.cpp index 288a00ac6..7ab1f3fef 100644 --- a/src/server/implementation/objects/RecorderEndpointImpl.cpp +++ b/src/server/implementation/objects/RecorderEndpointImpl.cpp @@ -18,6 +18,7 @@ #include "MediaType.hpp" #include "MediaPipeline.hpp" #include "MediaProfileSpecType.hpp" +#include "GapsFixMethod.hpp" #include #include "RecorderEndpointImpl.hpp" #include @@ -39,6 +40,9 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define FACTORY_NAME "recorderendpoint" +#define PARAM_GAPS_FIX "gapsFix" +#define PROP_GAPS_FIX "gaps-fix" + #define TIMEOUT 4 /* seconds */ namespace kurento @@ -145,6 +149,15 @@ RecorderEndpointImpl::RecorderEndpointImpl (const boost::property_tree::ptree GST_INFO ("Set KSR profile"); break; } + + GapsFixMethod gapsFix; + if (getConfigValue ( + &gapsFix, PARAM_GAPS_FIX)) { + GST_INFO ("Set RecorderEndpoint gaps fix mode: %s", + gapsFix.getString ().c_str ()); + g_object_set (getGstreamerElement (), + PROP_GAPS_FIX, gapsFix.getValue (), NULL); + } } void RecorderEndpointImpl::postConstructor() diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index 5bc4d86c3..30820ffcd 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -262,5 +262,133 @@ "doc": "@deprecated
          Fired when the recorder has been stopped and all the media has been written to storage.", "properties": [] } + ], + "complexTypes": [ + { + "name": "GapsFixMethod", + "typeFormat": "ENUM", + "doc": "How to fix gaps when they are found in the recorded stream. +

          + Gaps are typically caused by packet loss in the input streams, such as when an + RTP or WebRTC media flow suffers from network congestion and some packets + don't arrive at the media server. +

          +

          Different ways of handling gaps have different tradeoffs:

          +
            +
          • + NONE: Do not fix gaps. This means that the + resulting files will contain gaps in the timestamps. Some players are clever + enough to adapt to this during playback, so that the gaps are reduced to a + minimum and no problems are perceived by the user; other players are not so + sophisticated, and will struggle trying to decode a file that contains gaps. + For example, trying to play such a file directly with Chrome will cause + lipsync issues (audio and video out of sync). +

            + For example, assume a session length of 15 seconds: packets arrive + correctly during the first 5 seconds, then there is a gap, then data + arrives again for the last 5 seconds. Also, for simplicity, assume 1 frame + per second. With no fix for gaps, the RecorderEndpoint will store each + frame as-is, with these timestamps: +

            +
            +      frame 1  - 00:01
            +      frame 2  - 00:02
            +      frame 3  - 00:03
            +      frame 4  - 00:04
            +      frame 5  - 00:05
            +      frame 11 - 00:11
            +      frame 12 - 00:12
            +      frame 13 - 00:13
            +      frame 14 - 00:14
            +      frame 15 - 00:15
            +    
            +

            + Notice how the frames between 6 to 10 are missing, but the last 5 frames + still conserve their original timestamp. The total length of the file is + detected as 15 seconds by most players, although playback could stutter or + hang during the missing section. +

            +
          • +
          • + GENPTS: Replace timestamps of all frames arriving + after a gap. With this method, the length of each gap will be taken into + account, to be subtracted from the timestamp of all following frames. This + provides for a continuous, gap-less recording, at the expense of reducing + the total apparent length of each file. +

            + In our example, the RecorderEndpoint will change all timestamps that + follow a gap in the stream, and store each frame as follows: +

            +
            +      frame 1  - 00:01
            +      frame 2  - 00:02
            +      frame 3  - 00:03
            +      frame 4  - 00:04
            +      frame 5  - 00:05
            +      frame 11 - 00:06
            +      frame 12 - 00:07
            +      frame 13 - 00:08
            +      frame 14 - 00:09
            +      frame 15 - 00:10
            +    
            +

            + Notice how the frames between 6 to 10 are missing, and the last 5 frames + have their timestamps corrected to provide a smooth increment over the + previous ones. The total length of the file is detected as 10 seconds, and + playback should be correct throughout the whole file. +

            +
          • +
          • + FILL_IF_TRANSCODING: NOT IMPLEMENTED. +

            This is a proposal for future improvement of the RecorderEndpoint.

            +

            + It is possible to perform a dynamic adaptation of audio rate and add frame + duplication to the video, such that the missing parts are filled with + artificial data. This has the advantage of providing a smooth playback + result, and at the same time conserving all original timestamps. +

            +

            + However, the main issue with this method is that it requires accessing the + decoded media; i.e., transcoding must be active. For this reason, the + proposal is to offer this option to be enabled only when transcoding would + still happen anyways. +

            +

            + In our example, the RecorderEndpoint would change all missing frames like + this: +

            +
            +      frame 1  - 00:01
            +      frame 2  - 00:02
            +      frame 3  - 00:03
            +      frame 4  - 00:04
            +      frame 5  - 00:05
            +      fake frame - 00:06
            +      fake frame - 00:07
            +      fake frame - 00:08
            +      fake frame - 00:09
            +      fake frame - 00:10
            +      frame 11 - 00:11
            +      frame 12 - 00:12
            +      frame 13 - 00:13
            +      frame 14 - 00:14
            +      frame 15 - 00:15
            +    
            +

            + This joins the best of both worlds: on one hand, the playback should be + smooth and even the most basic players should be able to handle the + recording files without issue. On the other, the total length of the file + is left unmodified, so it matches with the expected duration of the + sessions that are being recorded. +

            +
          • +
          + ", + "values": [ + "NONE", + "GENPTS", + "FILL_IF_TRANSCODING" + ] + } ] } From a29b64e56281914980baa97882c3ad26fcc21481 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 26 Feb 2021 12:01:38 +0100 Subject: [PATCH 52/69] Prepare release 6.16.0-0kurento1 --- debian/changelog | 7 ++++--- debian/control | 14 +++++++------- src/server/interface/elements.kmd.json | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/debian/changelog b/debian/changelog index 56bc09851..bde4fb5bb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ -kms-elements (6.15.1-0kurento1) UNRELEASED; urgency=medium +kms-elements (6.16.0-0kurento1) testing; urgency=medium - * UNRELEASED + * Prepare release 6.16.0-0kurento1 + * Update all dependency versions to 6.15.1 - -- Juan Navarro Fri, 06 Nov 2020 11:55:56 +0100 + -- Juan Navarro Fri, 26 Feb 2021 12:01:38 +0100 kms-elements (6.15.0-0kurento1) testing; urgency=medium diff --git a/debian/control b/debian/control index a01714e77..aac9d875c 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,9 @@ Build-Depends: debhelper (>= 9), gstreamer1.5-plugins-bad, gstreamer1.5-plugins-good, gstreamer1.5-x, - kms-cmake-utils (>= 6.15.1), - kms-core-dev (>= 6.15.1), - kurento-module-creator (>= 6.15.1), + kms-cmake-utils (>= 6.16.0), + kms-core-dev (>= 6.16.0), + kurento-module-creator (>= 6.16.0), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, @@ -32,7 +32,7 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer1.5-nice, - kms-core (>= 6.15.1), + kms-core (>= 6.16.0), openh264-gst-plugins-bad-1.5, openwebrtc-gst-plugins Breaks: kms-elements-6.0 @@ -44,9 +44,9 @@ Package: kms-elements-dev Architecture: any Section: libdevel Depends: kms-elements (= ${binary:Version}), - kms-cmake-utils (>= 6.15.1), - kms-core-dev (>= 6.15.1), - kurento-module-creator (>= 6.15.1), + kms-cmake-utils (>= 6.16.0), + kms-core-dev (>= 6.16.0), + kurento-module-creator (>= 6.16.0), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 3acea434e..683530eef 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,7 +1,7 @@ { "name": "elements", - "version": "6.15.1-dev", - "kurentoVersion": "^6.15.0", + "version": "6.16.0", + "kurentoVersion": "^6.16.0", "code": { "kmd": { "java": { From 8c3c0baf10d0dfd5b916c580f68418699103df88 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 3 Mar 2021 12:28:21 +0100 Subject: [PATCH 53/69] Prepare for next development iteration --- debian/changelog | 6 ++++++ debian/control | 14 +++++++------- src/server/interface/elements.kmd.json | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/debian/changelog b/debian/changelog index bde4fb5bb..c74b539a3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +kms-elements (6.16.1-0kurento1) UNRELEASED; urgency=medium + + * UNRELEASED + + -- Juan Navarro Wed, 03 Mar 2021 12:26:48 +0100 + kms-elements (6.16.0-0kurento1) testing; urgency=medium * Prepare release 6.16.0-0kurento1 diff --git a/debian/control b/debian/control index aac9d875c..92ff6d6e8 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,9 @@ Build-Depends: debhelper (>= 9), gstreamer1.5-plugins-bad, gstreamer1.5-plugins-good, gstreamer1.5-x, - kms-cmake-utils (>= 6.16.0), - kms-core-dev (>= 6.16.0), - kurento-module-creator (>= 6.16.0), + kms-cmake-utils (>= 6.16.1), + kms-core-dev (>= 6.16.1), + kurento-module-creator (>= 6.16.1), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, @@ -32,7 +32,7 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer1.5-nice, - kms-core (>= 6.16.0), + kms-core (>= 6.16.1), openh264-gst-plugins-bad-1.5, openwebrtc-gst-plugins Breaks: kms-elements-6.0 @@ -44,9 +44,9 @@ Package: kms-elements-dev Architecture: any Section: libdevel Depends: kms-elements (= ${binary:Version}), - kms-cmake-utils (>= 6.16.0), - kms-core-dev (>= 6.16.0), - kurento-module-creator (>= 6.16.0), + kms-cmake-utils (>= 6.16.1), + kms-core-dev (>= 6.16.1), + kurento-module-creator (>= 6.16.1), libboost-filesystem-dev, libboost-system-dev, libboost-test-dev, diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json index 683530eef..116e917c1 100644 --- a/src/server/interface/elements.kmd.json +++ b/src/server/interface/elements.kmd.json @@ -1,6 +1,6 @@ { "name": "elements", - "version": "6.16.0", + "version": "6.16.1-dev", "kurentoVersion": "^6.16.0", "code": { "kmd": { From d12b82bb6ce0b12c1e30230020ac0ec033a7e513 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 5 Mar 2021 10:53:06 +0100 Subject: [PATCH 54/69] recorder: extend doc of GapsFixMethod --- src/server/config/RecorderEndpoint.conf.ini | 26 +++++++--- .../elements.RecorderEndpoint.kmd.json | 49 ++++++++++++------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/server/config/RecorderEndpoint.conf.ini b/src/server/config/RecorderEndpoint.conf.ini index 237d73b79..348c7f1f5 100644 --- a/src/server/config/RecorderEndpoint.conf.ini +++ b/src/server/config/RecorderEndpoint.conf.ini @@ -6,15 +6,29 @@ ;; ;; Possible values: ;; -;; * NONE: Do not fix gaps. Leave the stream as-is, and store it with any gaps -;; that the stream might have. This is the best choice if you need consistent -;; recording lengths, or if you are anyway going to post-process the -;; recordings. +;; * NONE: Do not fix gaps. +;; +;; Leave the stream as-is, and store it with any gaps that the stream might +;; have. Some players are clever enough to adapt to this during playback, so +;; that the gaps are reduced to a minimum and no problems are perceived by the +;; user; other players are not so sophisticated, and will struggle trying to +;; decode a file that contains gaps. For example, trying to play such a file +;; directly with Chrome will cause lipsync issues (audio and video will fall +;; out of sync). +;; +;; This is the best choice if you need consistent durations across multiple +;; simultaneous recordings, or if you are anyway going to post-process the +;; recordings (e.g. with an extra FFmpeg step). ;; ;; * GENPTS: Adjust timestamps to generate a smooth progression over all frames. +;; ;; This technique rewrites the timestamp of all frames, so that gaps are -;; corrected. It provides the best experience for recordings that need to be -;; played as-is (i.e. they won't be post-processed). +;; suppressed. It provides the best playback experience for recordings that +;; need to be played as-is (i.e. they won't be post-processed). However, +;; fixing timestamps might cause a change in the total duration of a file. So +;; different recordings from the same session might end up with slightly +;; different durations. ;; ;; Default: NONE. +;; ;gapsFix=NONE diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json index 30820ffcd..36e9f1776 100644 --- a/src/server/interface/elements.RecorderEndpoint.kmd.json +++ b/src/server/interface/elements.RecorderEndpoint.kmd.json @@ -64,8 +64,8 @@ url-encoded.
          This means that colons (:) should be replaced with - %3A, and 'at' signs (@) should be replaced - with %40. + '%3A', and 'at' signs (@) should be replaced + with '%40'.
      • @@ -269,20 +269,28 @@ "typeFormat": "ENUM", "doc": "How to fix gaps when they are found in the recorded stream.

        - Gaps are typically caused by packet loss in the input streams, such as when an - RTP or WebRTC media flow suffers from network congestion and some packets - don't arrive at the media server. +Gaps are typically caused by packet loss in the input streams, such as when an +RTP or WebRTC media flow suffers from network congestion and some packets don't +arrive at the media server.

        Different ways of handling gaps have different tradeoffs:

        • - NONE: Do not fix gaps. This means that the - resulting files will contain gaps in the timestamps. Some players are clever - enough to adapt to this during playback, so that the gaps are reduced to a - minimum and no problems are perceived by the user; other players are not so - sophisticated, and will struggle trying to decode a file that contains gaps. - For example, trying to play such a file directly with Chrome will cause - lipsync issues (audio and video out of sync). + NONE: Do not fix gaps. +

          + Leave the stream as-is, and store it with any gaps that the stream might + have. Some players are clever enough to adapt to this during playback, so + that the gaps are reduced to a minimum and no problems are perceived by + the user; other players are not so sophisticated, and will struggle trying + to decode a file that contains gaps. For example, trying to play such a + file directly with Chrome will cause lipsync issues (audio and video will + fall out of sync). +

          +

          + This is the best choice if you need consistent durations across multiple + simultaneous recordings, or if you are anyway going to post-process the + recordings (e.g. with an extra FFmpeg step). +

          For example, assume a session length of 15 seconds: packets arrive correctly during the first 5 seconds, then there is a gap, then data @@ -310,11 +318,16 @@

        • - GENPTS: Replace timestamps of all frames arriving - after a gap. With this method, the length of each gap will be taken into - account, to be subtracted from the timestamp of all following frames. This - provides for a continuous, gap-less recording, at the expense of reducing - the total apparent length of each file. + GENPTS: Adjust timestamps to generate a smooth progression + over all frames. +

          + This technique rewrites the timestamp of all frames, so that gaps are + suppressed. It provides the best playback experience for recordings that + need to be played as-is (i.e. they won't be post-processed). However, + fixing timestamps might cause a change in the total duration of a file. So + different recordings from the same session might end up with slightly + different durations. +

          In our example, the RecorderEndpoint will change all timestamps that follow a gap in the stream, and store each frame as follows: @@ -339,7 +352,7 @@

        • - FILL_IF_TRANSCODING: NOT IMPLEMENTED. + FILL_IF_TRANSCODING: (NOT IMPLEMENTED YET).

          This is a proposal for future improvement of the RecorderEndpoint.

          It is possible to perform a dynamic adaptation of audio rate and add frame From 9327a89df1ace9cf3fa6544bc89e08a245c08ac0 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 15 Apr 2021 17:30:57 +0200 Subject: [PATCH 55/69] cmake: remove unused var DEBUG_MEDIASET Usage of DEBUG_MEDIASET was removed from code in this commit: https://github.com/Kurento/kms-core/commit/6bff4216f89328842c4361dd10eda935a6fd36a0 --- tests/server/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index b34c75f2b..bf26e97c2 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -3,7 +3,6 @@ set(TEST_VARIABLES ) set(VALGRIND_TEST_VARIABLES "${TEST_VARIABLES}" - "DEBUG_MEDIASET=TRUE" ) list(APPEND SUPPRESSIONS "${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp") From 83db2054f70ec5cfbef5e811519b2e8fc38f21e3 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Fri, 23 Apr 2021 14:49:37 +0200 Subject: [PATCH 56/69] player: rewrite docs of useEncodedMedia --- .../elements.PlayerEndpoint.kmd.json | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/server/interface/elements.PlayerEndpoint.kmd.json b/src/server/interface/elements.PlayerEndpoint.kmd.json index a2d8fad92..cedac6c15 100644 --- a/src/server/interface/elements.PlayerEndpoint.kmd.json +++ b/src/server/interface/elements.PlayerEndpoint.kmd.json @@ -132,19 +132,51 @@ }, { "name": "useEncodedMedia", - "doc": "Feed the input media as-is to the Media Pipeline, instead of first decoding it. + "doc": "Feed an encoded media as-is to the Media Pipeline, instead of first decoding it.

          - When this property is not enabled, the input media gets always decoded into a raw format before being processed by the rest of the Media Pipeline; this is done to ensure that Kurento is able to keep track of lost keyframes among other quality-control measurements. Of course, having to decode the media has a cost in terms of CPU usage, but ensures that the output streaming will be robust and reliable. + This property is disabled by default. The input media gets always decoded into + a raw format upon receiving it, before being processed by the rest of the + Media Pipeline. This is done to ensure that Kurento is able to keep track of + lost keyframes among other quality-control measurements. Of course, having to + decode the media has a cost in terms of CPU usage, but ensures that the output + streaming will be more robust and reliable.

          - When this property is enabled, the explained behavior gets disabled. Instead, The endpoint will provide any input media directly to the Media Pipeline, without prior decoding. Enabling this mode of operation could have a severe effect on stability, because lost video keyframes will not be regenerated; however, avoiding a full cycle of decoding and encoding can be very useful for certain applications, because it improves performance by greatly reducing the CPU processing load. + When this property is enabled, Kurento simply passes the encoded media as-is + to the rest of the Media Pipeline, without decoding. Enabling this mode of + operation could have a severe effect on stability, because lost video + keyframes will not be regenerated; however, not having to encode the video + greatly reduces the CPU load.

          - Keep in mind that if this property is enabled, the original source media MUST already have an encoding format which is compatible with the destination target. For example: given a pipeline which uses this endpoint to read a file and then streams it to a WebRTC browser such as Chrome, then the file must already be encoded with a VP8 or H.264 codec profile which Chrome is able to decode. Note that for this example, most browsers don't support ANY combination of H.264 encoding options; instead, they tend to support only a very specific subset of the codec features (also known as 'profiles'). + Keep in mind that if this property is enabled, the original source media MUST + already be in a format that is compatible with the destination target. For + example: Given a Pipeline that reads a file and then streams it to a WebRTC + browser such as Chrome, the file must already be encoded with a VP8 or H.264 + codec profile, which Chrome is able to decode.

          - We strongly recommend to avoid using this option, because correct behavior cannot be guaranteed. + Of special note is that you cannot feed any random combination of H.264 + encoding options to a web browser; instead, they tend to support only a very + specific subset of the codec features (also known as 'profiles'). The most + compatible config for H.264 is + Constrained Baseline profile, level 3.1.

          +

          Code examples:

          +
          
          +                # Java
          +                PlayerEndpoint player = new PlayerEndpoint
          +                  .Builder(pipeline, 'rtsp://localhost:5000/video')
          +                  .useEncodedMedia()
          +                  .build();
          +              
          +
          
          +                # JavaScript
          +                let player = await pipeline.create('PlayerEndpoint', {
          +                  uri: 'rtsp://localhost:5000/video',
          +                  useEncodedMedia: true,
          +                });
          +              
          ", "type": "boolean", "optional": true, From 9d27dcf1dc6d9f1362d2f1d8e35ca8a3339911f3 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 5 May 2021 15:58:41 +0200 Subject: [PATCH 57/69] webrtc: check STUN & TURN before printing warning message --- .../webrtcendpoint/kmswebrtcsession.c | 16 +++++++++-- .../objects/WebRtcEndpointImpl.cpp | 28 ++++++++----------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index 767b03043..e9e0067e4 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -799,7 +799,7 @@ kms_webrtc_session_set_network_ifs_info (KmsWebrtcSession * self, return; } - GST_DEBUG_OBJECT (self, "Use network interfaces: %s", + GST_DEBUG_OBJECT (self, "Using network interfaces: %s", self->network_interfaces); kms_webrtc_base_connection_set_network_ifs_info (conn, @@ -818,10 +818,15 @@ kms_webrtc_session_set_stun_server_info (KmsWebrtcSession * self, KmsWebRtcBaseConnection * conn) { if (self->stun_server_ip == NULL) { + if (self->turn_address == NULL) { + GST_WARNING_OBJECT (self, + "STUN server not configured! NAT traversal requires STUN or TURN"); + } + return; } - GST_DEBUG_OBJECT (self, "Use STUN server: %s:%u", self->stun_server_ip, + GST_INFO_OBJECT (self, "Using STUN server: %s:%u", self->stun_server_ip, self->stun_server_port); kms_webrtc_base_connection_set_stun_server_info (conn, self->stun_server_ip, @@ -833,10 +838,15 @@ kms_webrtc_session_set_relay_info (KmsWebrtcSession * self, KmsWebRtcBaseConnection * conn) { if (self->turn_address == NULL) { + if (self->stun_server_ip == NULL) { + GST_WARNING_OBJECT (self, + "TURN relay server not configured! NAT traversal requires STUN or TURN"); + } + return; } - GST_DEBUG_OBJECT (self, "Use TURN server: %s:%u", self->turn_address, + GST_INFO_OBJECT (self, "Using TURN relay server: %s:%u", self->turn_address, self->turn_port); kms_webrtc_base_connection_set_relay_info (conn, self->turn_address, diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index f91bfee20..f51f44959 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -567,29 +567,26 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, g_object_set (G_OBJECT (element), PROP_ICE_TCP, iceTcp, NULL); } else { GST_DEBUG ("ICE-TCP option not found in config;" - " you can set one or default to 1 (TRUE)"); + " you can set it or default to 1 (TRUE)"); } uint stunPort = 0; - if (!getConfigValue (&stunPort, "stunServerPort", DEFAULT_STUN_PORT) ) { - GST_INFO ("STUN port not found in config;" - " using default value: %d", DEFAULT_STUN_PORT); + GST_DEBUG ("STUN port not found in config;" + " using default value: %d", DEFAULT_STUN_PORT); } std::string stunAddress; - - if (!getConfigValue (&stunAddress, - "stunServerAddress") ) { - GST_INFO ("STUN server not found in config;" - " remember that NAT traversal requires STUN or TURN"); - } else { - GST_INFO ("Using STUN reflexive server: %s:%d", stunAddress.c_str(), - stunPort); + if (getConfigValue (&stunAddress, + "stunServerAddress")) { + GST_INFO ("Predefined STUN server: %s:%d", stunAddress.c_str (), stunPort); g_object_set (G_OBJECT (element), "stun-server-port", stunPort, NULL); - g_object_set (G_OBJECT (element), "stun-server", stunAddress.c_str(), NULL); + g_object_set (G_OBJECT (element), "stun-server", stunAddress.c_str (), + NULL); + } else { + GST_DEBUG ("STUN server not found in config"); } std::string turnURL; @@ -601,12 +598,11 @@ WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf, } else { safeURL.append(turnURL.substr(separatorPos)); } - GST_INFO ("Using TURN relay server: %s", safeURL.c_str()); + GST_INFO ("Predefined TURN relay server: %s", safeURL.c_str()); g_object_set (G_OBJECT (element), "turn-url", turnURL.c_str(), NULL); } else { - GST_INFO ("TURN server not found in config;" - " remember that NAT traversal requires STUN or TURN"); + GST_DEBUG ("TURN relay server not found in config"); } switch (certificateKeyType->getValue () ) { From 1b08cb5e90d40268f3f9aba19e1d622779849bec Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Wed, 5 May 2021 16:00:04 +0200 Subject: [PATCH 58/69] webrtc: remove unused macros; improve error check --- src/gst-plugins/webrtcendpoint/kmsicebaseagent.h | 6 ------ src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsicebaseagent.h b/src/gst-plugins/webrtcendpoint/kmsicebaseagent.h index 916201e7a..52d7dad96 100644 --- a/src/gst-plugins/webrtcendpoint/kmsicebaseagent.h +++ b/src/gst-plugins/webrtcendpoint/kmsicebaseagent.h @@ -34,11 +34,6 @@ G_BEGIN_DECLS (G_TYPE_CHECK_CLASS_TYPE((klass),KMS_TYPE_ICE_BASE_AGENT)) #define KMS_ICE_BASE_AGENT_CAST(obj) ((KmsIceBaseAgent*)(obj)) -#define KMS_ICE_BASE_AGENT_LOCK(conn) \ - (g_rec_mutex_lock (&KMS_ICE_BASE_AGENT_CAST ((conn))->mutex)) -#define KMS_ICE_BASE_AGENT_UNLOCK(conn) \ - (g_rec_mutex_unlock (&KMS_ICE_BASE_AGENT_CAST ((conn))->mutex)) - typedef enum TurnProtocol { TURN_PROTOCOL_UDP, @@ -201,4 +196,3 @@ GType kms_ice_base_agent_get_type (void); G_END_DECLS #endif /* __KMS_ICE_BASE_AGENT_H__ */ - diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c index 6960e616b..7dd535f71 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c @@ -46,8 +46,8 @@ kms_webrtc_base_connection_configure (KmsWebRtcBaseConnection * self, kms_ice_base_agent_add_stream (agent, self->name, self->min_port, self->max_port); - if (g_strcmp0 (self->stream_id, "0") == 0) { - GST_ERROR_OBJECT (self, "Cannot add stream for %s.", name); + if (self->stream_id == NULL) { + GST_ERROR_OBJECT (self, "Cannot add stream for %s.", self->name); return FALSE; } From b046a0f2bc8257b9c408d06f875148221ff3245b Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 6 May 2021 15:36:34 +0200 Subject: [PATCH 59/69] webrtc niceagent: push signal emissions to owned context thread Prevent a thread race detected by ThreadSanitizer: the libnice Agent is calling our callbacks from different threads, instead of the specific KmsLoop thread that we expected (where the GMainContext instance lives). Using `g_main_context_invoke_full` is the best way to ensure that the signal emission is performed from our context thread. More info: https://lists.freedesktop.org/archives/nice/2021-May/001493.html --- .../webrtcendpoint/kmsiceniceagent.c | 241 +++++++++++++----- 1 file changed, 177 insertions(+), 64 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c index 9eedf4de3..bc692d644 100644 --- a/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c +++ b/src/gst-plugins/webrtcendpoint/kmsiceniceagent.c @@ -72,72 +72,129 @@ kms_ice_nice_agent_create_candidate_from_nice (NiceAgent * nice_agent, return candidate; } +// ---------------------------------------------------------------------------- + +// FIXME: Avoid emitting the signal directly from the callback, because +// because libnice doesn't seem to use our provided GMainContext to emit all +// of its signals from the same thread. +// More info: https://lists.freedesktop.org/archives/nice/2021-May/001493.html + +typedef struct { + KmsIceNiceAgent *self; + KmsIceCandidate *kms_candidate; +} OnIceCandidateData; + +static void +on_ice_candidate_data_free (OnIceCandidateData *data) +{ + g_object_unref (data->self); + g_object_unref (data->kms_candidate); + g_free (data); +} + +static gboolean +kms_ice_nice_agent_new_candidate_full_emit (OnIceCandidateData *data) +{ + // This function should be called only from our GMainContext's thread. + g_assert (g_main_context_is_owner (data->self->priv->context)); + + g_signal_emit_by_name (KMS_ICE_BASE_AGENT (data->self), "on-ice-candidate", + data->kms_candidate); + + return FALSE; +} + static void kms_ice_nice_agent_new_candidate_full (NiceAgent * agent, NiceCandidate * candidate, KmsIceNiceAgent * self) { - KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); - const guint stream_id = candidate->stream_id; - const guint component_id = candidate->component_id; - - gchar *stream_id_str = g_strdup_printf ("%u", stream_id); + gchar *stream_id_str = g_strdup_printf ("%u", candidate->stream_id); KmsIceCandidate *kms_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, - stream_id_str); - g_free (stream_id_str); + stream_id_str); GST_LOG_OBJECT (self, "[IceCandidateFound] local: '%s', stream_id: %u, component_id: %u", - kms_ice_candidate_get_candidate (kms_candidate), - stream_id, component_id); + kms_ice_candidate_get_candidate (kms_candidate), candidate->stream_id, + candidate->component_id); + + OnIceCandidateData *data = g_new0 (OnIceCandidateData, 1); + data->self = g_object_ref (self); + data->kms_candidate = g_object_ref (kms_candidate); + g_main_context_invoke_full (self->priv->context, G_PRIORITY_DEFAULT, + (GSourceFunc)kms_ice_nice_agent_new_candidate_full_emit, data, + (GDestroyNotify)on_ice_candidate_data_free); - g_signal_emit_by_name (parent, "on-ice-candidate", kms_candidate); + g_free (stream_id_str); g_object_unref (kms_candidate); } +// ---------------------------------------------------------------------------- + static void kms_ice_nice_agent_new_remote_candidate_full (NiceAgent * agent, NiceCandidate * candidate, KmsIceNiceAgent * self) { - KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); - const guint stream_id = candidate->stream_id; - - gchar *stream_id_str = g_strdup_printf ("%u", stream_id); - + gchar *stream_id_str = g_strdup_printf ("%u", candidate->stream_id); KmsIceCandidate *kms_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, candidate, - stream_id_str); + stream_id_str); GST_DEBUG_OBJECT (self, "[AddIceCandidate] Found peer-reflexive remote: '%s'", kms_ice_candidate_get_candidate (kms_candidate)); - kms_ice_nice_agent_add_ice_candidate (parent, kms_candidate, stream_id_str); + kms_ice_nice_agent_add_ice_candidate (KMS_ICE_BASE_AGENT (self), + kms_candidate, stream_id_str); g_free (stream_id_str); g_object_unref (kms_candidate); } +// ---------------------------------------------------------------------------- + +typedef struct { + KmsIceNiceAgent *self; + char *stream_id; +} OnGatheringDoneData; + static void -kms_ice_nice_agent_gathering_done (NiceAgent * agent, guint stream_id, - KmsIceNiceAgent * self) +on_gathering_done_data_free (OnGatheringDoneData *data) { - KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); - char buff[33]; - char *ret; + g_object_unref (data->self); + g_free (data->stream_id); + g_free (data); +} - GST_LOG_OBJECT (self, "[IceGatheringDone] stream_id: %u", stream_id); +static gboolean +kms_ice_nice_agent_gathering_done_emit (OnGatheringDoneData *data) +{ + // This function should be called only from our GMainContext's thread. + g_assert (g_main_context_is_owner (data->self->priv->context)); - //convert id to char* - g_snprintf (buff, 32, "%u", stream_id); + g_signal_emit_by_name (KMS_ICE_BASE_AGENT (data->self), + "on-ice-gathering-done", data->stream_id); - ret = g_strdup (buff); + return FALSE; +} - g_signal_emit_by_name (parent, "on-ice-gathering-done", ret); +static void +kms_ice_nice_agent_gathering_done (NiceAgent * agent, guint stream_id, + KmsIceNiceAgent * self) +{ + GST_LOG_OBJECT (self, "[IceGatheringDone] stream_id: %u", stream_id); + + OnGatheringDoneData *data = g_new0 (OnGatheringDoneData, 1); + data->self = g_object_ref (self); + data->stream_id = g_strdup_printf ("%u", stream_id); - g_free (ret); + g_main_context_invoke_full (self->priv->context, G_PRIORITY_DEFAULT, + (GSourceFunc)kms_ice_nice_agent_gathering_done_emit, data, + (GDestroyNotify)on_gathering_done_data_free); } +// ---------------------------------------------------------------------------- + static IceState kms_ice_nice_agent_nice_to_ice_state (NiceComponentState state) { @@ -159,28 +216,84 @@ kms_ice_nice_agent_nice_to_ice_state (NiceComponentState state) } } +// ---------------------------------------------------------------------------- + +typedef struct { + KmsIceNiceAgent *self; + char *stream_id; + guint component_id; + IceState ice_state; +} OnComponentStateChangedData; + +static void +on_component_state_changed_data_free (OnComponentStateChangedData *data) +{ + g_object_unref (data->self); + g_free (data->stream_id); + g_free (data); +} + +static gboolean +kms_ice_nice_agent_component_state_change_emit (OnComponentStateChangedData *data) +{ + // This function should be called only from our GMainContext's thread. + g_assert (g_main_context_is_owner (data->self->priv->context)); + + g_signal_emit_by_name (KMS_ICE_BASE_AGENT (data->self), + "on-ice-component-state-changed", data->stream_id, data->component_id, + data->ice_state); + + return FALSE; +} + static void kms_ice_nice_agent_component_state_change (NiceAgent * agent, guint stream_id, guint component_id, NiceComponentState state, KmsIceNiceAgent * self) { - KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); - IceState state_; - char buff[33]; - char *ret; + IceState ice_state = kms_ice_nice_agent_nice_to_ice_state (state); + + OnComponentStateChangedData *data = g_new0 (OnComponentStateChangedData, 1); + data->self = g_object_ref (self); + data->stream_id = g_strdup_printf ("%u", stream_id); + data->component_id = component_id; + data->ice_state = ice_state; - //convert id to char* - g_snprintf (buff, 32, "%u", stream_id); + g_main_context_invoke_full (self->priv->context, G_PRIORITY_DEFAULT, + (GSourceFunc)kms_ice_nice_agent_component_state_change_emit, data, + (GDestroyNotify)on_component_state_changed_data_free); +} - ret = g_strdup (buff); - state_ = kms_ice_nice_agent_nice_to_ice_state (state); +// ---------------------------------------------------------------------------- - GST_LOG_OBJECT (self, - "[IceComponentStateChanged] state: %s, stream_id: %u, component_id: %u", - nice_component_state_to_string (state), stream_id, component_id); +typedef struct { + KmsIceNiceAgent *self; + char *stream_id; + guint component_id; + KmsIceCandidate *local_candidate; + KmsIceCandidate *remote_candidate; +} OnNewSelectedPairData; + +static void +on_new_selected_pair_data_free (OnNewSelectedPairData *data) +{ + g_object_unref (data->self); + g_free (data->stream_id); + g_object_unref (data->local_candidate); + g_object_unref (data->remote_candidate); + g_free (data); +} + +static gboolean +kms_ice_nice_agent_new_selected_pair_full_emit (OnNewSelectedPairData *data) +{ + // This function should be called only from our GMainContext's thread. + g_assert (g_main_context_is_owner (data->self->priv->context)); + + g_signal_emit_by_name (KMS_ICE_BASE_AGENT (data->self), + "new-selected-pair-full", data->stream_id, data->component_id, + data->local_candidate, data->remote_candidate); - g_signal_emit_by_name (parent, "on-ice-component-state-changed", ret, - component_id, state_); - g_free (ret); + return FALSE; } void @@ -190,14 +303,15 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, NiceCandidate * lcandidate, NiceCandidate * rcandidate, KmsIceNiceAgent * self) { - KmsIceBaseAgent *parent = KMS_ICE_BASE_AGENT (self); - gchar *stream_id_str; - KmsIceCandidate *local_candidate = NULL, *remote_candidate = NULL; - stream_id_str = g_strdup_printf ("%u", stream_id); + KmsIceCandidate *local_candidate = NULL; + KmsIceCandidate *remote_candidate = NULL; + + gchar *stream_id_str = g_strdup_printf ("%u", stream_id); local_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, lcandidate, stream_id_str); + if (!local_candidate) { gchar *cand_str = kms_ice_nice_agent_get_candidate_sdp_string (agent, lcandidate); @@ -210,6 +324,7 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, remote_candidate = kms_ice_nice_agent_create_candidate_from_nice (agent, rcandidate, stream_id_str); + if (!remote_candidate) { gchar *cand_str = kms_ice_nice_agent_get_candidate_sdp_string (agent, rcandidate); @@ -227,8 +342,16 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, kms_ice_candidate_get_candidate (remote_candidate), stream_id, component_id); - g_signal_emit_by_name (parent, "new-selected-pair-full", stream_id_str, - component_id, local_candidate, remote_candidate); + OnNewSelectedPairData *data = g_new0 (OnNewSelectedPairData, 1); + data->self = g_object_ref (self); + data->stream_id = g_strdup (stream_id_str); + data->component_id = component_id; + data->local_candidate = g_object_ref (local_candidate); + data->remote_candidate = g_object_ref (remote_candidate); + + g_main_context_invoke_full (self->priv->context, G_PRIORITY_DEFAULT, + (GSourceFunc)kms_ice_nice_agent_new_selected_pair_full_emit, data, + (GDestroyNotify)on_new_selected_pair_data_free); end: g_free (stream_id_str); @@ -236,6 +359,8 @@ kms_ice_nice_agent_new_selected_pair_full (NiceAgent * agent, if (remote_candidate) { g_object_unref (remote_candidate); } } +// ---------------------------------------------------------------------------- + KmsIceNiceAgent * kms_ice_nice_agent_new (GMainContext * context) { @@ -293,16 +418,6 @@ kms_ice_nice_agent_init (KmsIceNiceAgent * self) self->priv = KMS_ICE_NICE_AGENT_GET_PRIVATE (self); } -// TODO Ask in libnice mail lists if attaching a callback function is really needed -//static void -//kms_ice_nice_agent_recv_cb (NiceAgent *agent, guint stream_id, -// guint component_id, guint len, gchar *buf, gpointer user_data) -//{ -//// ((void)0); // Nothing to do, noop -// KmsIceBaseAgent *self = user_data; -// GST_DEBUG_OBJECT (self, "Callback data received"); -//} - static char * kms_ice_nice_agent_add_stream (KmsIceBaseAgent * self, const char *stream_id, guint16 min_port, guint16 max_port) @@ -327,12 +442,10 @@ kms_ice_nice_agent_add_stream (KmsIceBaseAgent * self, const char *stream_id, max_port); } -// TODO Ask in libnice mail lists if attaching a callback function is really needed -// GST_DEBUG_OBJECT (self, "Attach recv callback to mainloop"); -// for (i = 1; i <= KMS_NICE_N_COMPONENTS; i++) { -// nice_agent_attach_recv (nice_agent->priv->agent, id, i, -// nice_agent->priv->context, kms_ice_nice_agent_recv_cb, self); -// } + // NOTE: Docs say [0] that an I/O callback must be registered in order to receive + // data through the ICE transport. We don't need to do that here, because it + // is already done inside the GStreamer's plugin (gstnicesrc). + // [0]: https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent.description return g_strdup_printf ("%u", id); } From c82305f9811cb37ff4a07a1d272bc2d3fc20ced7 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 14 Jun 2021 14:22:52 +0200 Subject: [PATCH 60/69] webrtc, rtp: replace G_MAXUINT16 with 65535 Using G_MAXUINT16 was semantically confusing. It conveys something like "this value is limited by the range of 16bit integers", when that's just a coincidence. We want to say "max port as defined by Linux", so if tomorrow they change to 65539, then that's the new value to use. The fact that Linux happens to use MAXUINT16 as their max port number, is just an implementation detail on their part. --- src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c | 2 +- src/gst-plugins/webrtcendpoint/kmswebrtcsession.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c index c4017f666..c0c307286 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c @@ -787,7 +787,7 @@ kms_webrtc_endpoint_class_init (KmsWebrtcEndpointClass * klass) g_param_spec_uint ("stun-server-port", "StunServerPort", "Stun Server Port", - 1, G_MAXUINT16, DEFAULT_STUN_SERVER_PORT, + 1, 65535, DEFAULT_STUN_SERVER_PORT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TURN_URL, diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c index e9e0067e4..983c673c0 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c @@ -2046,7 +2046,7 @@ kms_webrtc_session_class_init (KmsWebrtcSessionClass * klass) g_param_spec_uint ("stun-server-port", "StunServerPort", "Stun Server Port", - 1, G_MAXUINT16, DEFAULT_STUN_SERVER_PORT, + 1, 65535, DEFAULT_STUN_SERVER_PORT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TURN_URL, From bee32814d5c16474f41722a75d9978fae926abd3 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 14 Jun 2021 14:23:16 +0200 Subject: [PATCH 61/69] rtp: move max/min port logic to BaseRtpEndpoint --- src/gst-plugins/rtpendpoint/kmssocketutils.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/gst-plugins/rtpendpoint/kmssocketutils.c b/src/gst-plugins/rtpendpoint/kmssocketutils.c index cd835c82f..d5bbb8240 100644 --- a/src/gst-plugins/rtpendpoint/kmssocketutils.c +++ b/src/gst-plugins/rtpendpoint/kmssocketutils.c @@ -107,25 +107,11 @@ kms_rtp_connection_get_rtp_rtcp_sockets (GSocket ** rtp, GSocket ** rtcp, return FALSE; } - /* Minimum port that a normal user can open */ - if (min_port <= 1024) { - min_port = 1025; - } - - if (max_port == 0) { - max_port = G_MAXUINT16; - } - - if (min_port + 1 > max_port) { - return FALSE; - } - start_port = (guint16) g_random_int_range (min_port, max_port + 1); for (port1 = start_port; !all_checked; - port1 = - inc_port (port1, min_port, max_port, start_port, &max_reached, - &all_checked)) { + port1 = inc_port ( + port1, min_port, max_port, start_port, &max_reached, &all_checked)) { GSocket *s1, *s2; s1 = kms_socket_open (port1, socket_family); From c017eb260ed14f0ecf10f1dfda3f6c25647d6fcc Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 14 Jun 2021 19:03:55 +0200 Subject: [PATCH 62/69] webrtc: extend doc for gatherCandidates() method Fixes https://github.com/Kurento/bugtracker/issues/600 --- .../elements.WebRtcEndpoint.kmd.json | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index c84ddc2dc..287a8d805 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -126,7 +126,7 @@ unusable.
        • - IceGatheringDone: Raised when the ICE harvesting process is + IceGatheringDone: Raised when the ICE gathering process is completed. This means that all candidates have already been discovered.
        • @@ -511,12 +511,29 @@ "methods": [ { "name": "gatherCandidates", - "doc": "Start the gathering of ICE candidates. + "doc": "Start the ICE candidate gathering.

          - It must be called after SdpEndpoint::generateOffer or - SdpEndpoint::processOffer for Trickle ICE. If - invoked before generating or processing an SDP offer, the candidates gathered - will be added to the SDP processed. + This method triggers the asynchronous discovery of ICE candidates (as per the + Trickle ICE mechanism), and returns immediately. Every newly trickled + candidate is reported to the application by means of an + IceCandidateFound event. Finally, when all candidates have been + gathered, the IceGatheringDone event is emitted. +

          +

          + Normally, you would call this method as soon as possible after calling + SdpEndpoint::generateOffer or + SdpEndpoint::processOffer, to quickly start discovering + candidates and sending them to the remote peer. +

          +

          + You can also call this method before calling + generateOffer or processOffer. Doing so will include + any already gathered candidates into the resulting SDP. You can leverage this + behavior to implement fully traditional ICE (without Trickle): first call + gatherCandidates, then only handle the SDP messages after the + IceGatheringDone event has been received. This way, you're making + sure that all candidates have indeed been gathered, so the resulting SDP will + include all of them.

          ", "params": [] From fe060fca54fbcfe3c9d2662dd4ad94bfb3d5664a Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 8 Jul 2021 13:47:32 +0200 Subject: [PATCH 63/69] webrtc: rename JSON-RPC streamID, componentID to streamId, componentId Follow the same naming used in other similar properties, where "Id" is used rather than "ID". This was making direct JSON-RPC implementations very confusing because some properties were using the "Id" style, while just these two were using "ID". --- src/server/interface/elements.WebRtcEndpoint.kmd.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index 287a8d805..cb8672a8e 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -849,13 +849,13 @@ This could also happen in the middle of a session, though not likely. "doc": "The ICE candidate pair used by the ice library, for a certain stream.", "properties": [ { - "name": "streamID", "doc": "Stream ID of the ice connection", + "name": "streamId", "type": "String" }, { - "name": "componentID", "doc": "Component ID of the ice connection", + "name": "componentId", "type": "int" }, { From bfc40647e25771155ae44379a2928fcd04709215 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Thu, 8 Jul 2021 13:50:10 +0200 Subject: [PATCH 64/69] webrtc minor: say "ICE" instead of "ice" --- src/gst-plugins/webrtcendpoint/kmsicecandidate.c | 4 ++-- .../interface/elements.WebRtcEndpoint.kmd.json | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c index 02a2c1dd4..436521263 100644 --- a/src/gst-plugins/webrtcendpoint/kmsicecandidate.c +++ b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c @@ -129,7 +129,7 @@ kms_ice_candidate_update_values (KmsIceCandidate * self) } else if (g_strcmp0 (tmp, "2") == 0) { self->priv->component = KMS_ICE_COMPONENT_RTCP; } else { - GST_ERROR_OBJECT (self, "Unsupported ice candidate component %s", tmp); + GST_ERROR_OBJECT (self, "Unsupported ICE candidate component %s", tmp); goto end; } g_free (tmp); @@ -155,7 +155,7 @@ kms_ice_candidate_update_values (KmsIceCandidate * self) } else if (g_strcmp0 (tmp, "relay") == 0) { self->priv->type = KMS_ICE_CANDIDATE_TYPE_RELAY; } else { - GST_ERROR_OBJECT (self, "Unsupported ice candidate type %s", tmp); + GST_ERROR_OBJECT (self, "Unsupported ICE candidate type %s", tmp); goto end; } g_free (tmp); diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index cb8672a8e..572e8d2d0 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -458,7 +458,7 @@ }, { "name": "ICECandidatePairs", - "doc": "the ICE candidate pair (local and remote candidates) used by the ice library for each stream.", + "doc": "the ICE candidate pair (local and remote candidates) used by the ICE library for each stream.", "type": "IceCandidatePair[]", "readOnly": true }, @@ -846,26 +846,26 @@ This could also happen in the middle of a session, though not likely. { "typeFormat": "REGISTER", "name": "IceCandidatePair", - "doc": "The ICE candidate pair used by the ice library, for a certain stream.", + "doc": "The ICE candidate pair used by the ICE library, for a certain stream.", "properties": [ { - "doc": "Stream ID of the ice connection", "name": "streamId", + "doc": "Stream ID of the ICE connection", "type": "String" }, { - "doc": "Component ID of the ice connection", "name": "componentId", + "doc": "Component ID of the ICE connection", "type": "int" }, { "name": "localCandidate", - "doc": "The local candidate used by the ice library.", + "doc": "The local candidate used by the ICE library.", "type": "String" }, { "name": "remoteCandidate", - "doc": "The remote candidate used by the ice library.", + "doc": "The remote candidate used by the ICE library.", "type": "String" } ] From 1fd17d95d04834116f7a17509eb1c2c6019faa77 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 12 Jul 2021 09:49:27 +0200 Subject: [PATCH 65/69] Revert "webrtc: rename JSON-RPC streamID, componentID to streamId, componentId" Leave this change for the major release 7.0, which can contain breaking changes in the API. Meanwhile, mark these as deprecated. This reverts commit fe060fca54fbcfe3c9d2662dd4ad94bfb3d5664a. --- src/server/interface/elements.WebRtcEndpoint.kmd.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index 572e8d2d0..840baebaf 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -849,12 +849,12 @@ This could also happen in the middle of a session, though not likely. "doc": "The ICE candidate pair used by the ICE library, for a certain stream.", "properties": [ { - "name": "streamId", + "name": "streamID", "doc": "Stream ID of the ICE connection", "type": "String" }, { - "name": "componentId", + "name": "componentID", "doc": "Component ID of the ICE connection", "type": "int" }, From 810fd49981d318aef2fef946a1ba53765614ffdd Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 12 Jul 2021 09:59:31 +0200 Subject: [PATCH 66/69] webrtc: deprecate JSON-RPC streamID, componentID for streamId, componentId Follow the same naming used in other similar properties, where "Id" is used rather than "ID". This was making direct JSON-RPC implementations confusing because some properties were using the "Id" style, while just these were using "ID". --- .../interface/elements.WebRtcEndpoint.kmd.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json index 840baebaf..6827e2783 100644 --- a/src/server/interface/elements.WebRtcEndpoint.kmd.json +++ b/src/server/interface/elements.WebRtcEndpoint.kmd.json @@ -850,11 +850,25 @@ This could also happen in the middle of a session, though not likely. "properties": [ { "name": "streamID", + "doc": "Stream ID of the ICE connection. +@deprecated Use streamId instead. + ", + "type": "String" + }, + { + "name": "streamId", "doc": "Stream ID of the ICE connection", "type": "String" }, { "name": "componentID", + "doc": "Component ID of the ICE connection +@deprecated Use componentId instead. + ", + "type": "int" + }, + { + "name": "componentId", "doc": "Component ID of the ICE connection", "type": "int" }, From 0b6993e75ff020fe7462a564261b862036cbbd3a Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Tue, 13 Jul 2021 11:38:50 +0200 Subject: [PATCH 67/69] fixup! webrtc: deprecate JSON-RPC streamID, componentID for streamId, componentId The change was missing corresponding implementation in C++-land --- src/server/implementation/objects/WebRtcEndpointImpl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp index f51f44959..c509ec138 100644 --- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp +++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp @@ -334,8 +334,8 @@ void WebRtcEndpointImpl::newSelectedPairFull (gchar *sessId, kms_ice_candidate_get_candidate (remoteCandidate), streamId, componentId); - candidatePair = std::make_shared< IceCandidatePair > (streamId, - componentId, + candidatePair = std::make_shared< IceCandidatePair > (streamId, streamId, + componentId, componentId, kms_ice_candidate_get_candidate (localCandidate), kms_ice_candidate_get_candidate (remoteCandidate) ); key = std::string (streamId) + "_" + std::to_string (componentId); From 7a1ae524647b1821e0644807bee12d67c3d1b9e7 Mon Sep 17 00:00:00 2001 From: Juan Navarro Date: Mon, 2 Aug 2021 13:11:42 +0200 Subject: [PATCH 68/69] cmake: remove KURENTO_DOT_DIR, make GST_DEBUG_DUMP_DOT_DIR dir to work --- tests/check/CMakeLists.txt | 7 ++++++- tests/server/CMakeLists.txt | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/check/CMakeLists.txt b/tests/check/CMakeLists.txt index c3a39b0f6..077839615 100644 --- a/tests/check/CMakeLists.txt +++ b/tests/check/CMakeLists.txt @@ -24,8 +24,13 @@ set_property(TARGET kmstestutils ) include(TestHelpers) + +set(GST_DEBUG_DUMP_DOT_DIR "${CMAKE_BINARY_DIR}/debug/dots" CACHE PATH "Sets the directory where dot files will be dumped") +file(MAKE_DIRECTORY "${GST_DEBUG_DUMP_DOT_DIR}") + set(TEST_VARIABLES - "GST_PLUGIN_PATH=$ENV{GST_PLUGIN_PATH}:${CMAKE_BINARY_DIR}" + "GST_DEBUG_DUMP_DOT_DIR=${GST_DEBUG_DUMP_DOT_DIR}" + "GST_PLUGIN_PATH=${CMAKE_BINARY_DIR}:$ENV{GST_PLUGIN_PATH}" "CK_DEFAULT_TIMEOUT=50" ) set(VALGRIND_TEST_VARIABLES diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index bf26e97c2..52a37337e 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -1,5 +1,5 @@ set(TEST_VARIABLES - "GST_PLUGIN_PATH=$ENV{GST_PLUGIN_PATH}:${CMAKE_BINARY_DIR}" + "GST_PLUGIN_PATH=${CMAKE_BINARY_DIR}:$ENV{GST_PLUGIN_PATH}" ) set(VALGRIND_TEST_VARIABLES "${TEST_VARIABLES}" From 7550a7470a9ccf344419674e201652e067c4c88a Mon Sep 17 00:00:00 2001 From: Daniel Pocock Date: Tue, 31 Aug 2021 21:37:31 +0200 Subject: [PATCH 69/69] CMakeLists.txt: explicitly list the Boost libraries required for linking --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa3bbb7b1..f732e7e72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ set(NICE_REQUIRED ^0.1.13) set(GLIBMM_REQUIRED ^2.37) include(GenericFind) -generic_find(LIBNAME Boost REQUIRED COMPONENTS unit_test_framework) +generic_find(LIBNAME Boost REQUIRED COMPONENTS unit_test_framework system filesystem thread) generic_find(LIBNAME gstreamer-1.5 VERSION ${GST_REQUIRED} REQUIRED) generic_find(LIBNAME gstreamer-base-1.5 VERSION ${GST_REQUIRED} REQUIRED) generic_find(LIBNAME gstreamer-video-1.5 VERSION ${GST_REQUIRED} REQUIRED)