diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e96db077..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)
@@ -77,12 +80,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/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:
diff --git a/debian/changelog b/debian/changelog
index 1e2ea9e5b..c74b539a3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,28 @@
-kms-elements (6.13.1-0kurento1) UNRELEASED; urgency=medium
+kms-elements (6.16.1-0kurento1) UNRELEASED; urgency=medium
* UNRELEASED
- -- Juan Navarro Thu, 19 Dec 2019 19:17:38 +0100
+ -- 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
+ * Update all dependency versions to 6.15.1
+
+ -- Juan Navarro Fri, 26 Feb 2021 12:01:38 +0100
+
+kms-elements (6.15.0-0kurento1) testing; urgency=medium
+
+ * Prepare release 6.15.0-0kurento1
+ * debian: update versions of Kurento dependencies
+
+ -- Juan Navarro Tue, 03 Nov 2020 18:04:22 +0100
+
+kms-elements (6.14.0-0kurento1) testing; urgency=medium
+
+ * Prepare release 6.14.0-0kurento1
+
+ -- Juan Navarro Tue, 16 Jun 2020 17:48:04 +0200
kms-elements (6.13.0-0kurento1) testing; urgency=medium
diff --git a/debian/control b/debian/control
index 36997a121..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.7.0),
- kms-core-dev (>= 6.7.0),
- kurento-module-creator (>= 6.7.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.7.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.7.0),
- kms-core-dev (>= 6.7.0),
- kurento-module-creator (>= 6.7.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/gst-plugins/kmsalphablending.c b/src/gst-plugins/kmsalphablending.c
index 434018707..347286e79 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;
@@ -479,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;
}
@@ -621,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 c8ef22ac0..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);
@@ -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);
}
@@ -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/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/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 0751d40ca..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;
@@ -254,6 +260,7 @@ typedef struct _BaseTimeType
{
GstClockTime pts;
GstClockTime dts;
+ GstClockTime audio_gaps;
} BaseTimeType;
static void
@@ -262,35 +269,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 +308,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 +327,83 @@ 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?
+
+ 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 = common_offset + base_time->pts;
+
+ // 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 = common_offset + base_time->dts;
+
+ // DTS -= offset, but preventing underflows.
if (GST_BUFFER_DTS (buffer) > offset) {
GST_BUFFER_DTS (buffer) -= offset;
} else {
@@ -371,29 +412,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;
@@ -753,19 +791,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;
}
@@ -990,47 +1027,80 @@ 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);
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);
+ GstCaps *caps = NULL;
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
- GST_DEBUG_OBJECT (pad, "Processing caps event %" GST_PTR_FORMAT, caps);
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
+ gst_event_parse_caps (event, &caps);
- if (gst_caps_get_size (caps) == 0) {
- GST_ERROR_OBJECT (pad, "Invalid event %" GST_PTR_FORMAT, event);
+ if (gst_caps_get_size (caps) == 0) {
+ GST_ERROR_OBJECT (pad, "Invalid CAPS event %" GST_PTR_FORMAT, event);
+ return GST_PAD_PROBE_OK;
+ }
- return GST_PAD_PROBE_OK;
- }
+ GST_DEBUG_OBJECT (pad, "Processing CAPS event %" GST_PTR_FORMAT, caps);
- if (!gst_caps_is_fixed (caps)) {
- GST_WARNING_OBJECT (pad, "Not fixed caps in event %" GST_PTR_FORMAT, event);
- }
+ if (!gst_caps_is_fixed (caps)) {
+ GST_WARNING_OBJECT (
+ pad, "Not fixed caps in CAPS event %" GST_PTR_FORMAT, event);
+ }
- appsink = gst_pad_get_parent_element (pad);
- GST_DEBUG_OBJECT (appsink, "Setting caps: %" GST_PTR_FORMAT, caps);
+ 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);
- set_appsink_caps (appsink, caps, self->priv->profile);
+ 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");
+ }
- appsrc = g_object_get_qdata (G_OBJECT (appsink), kms_appsrc_id_key_quark ());
+ 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;
+ }
- if (appsrc != NULL) {
- set_appsrc_caps (appsrc, caps);
- } else {
- GST_ERROR_OBJECT (pad, "No appsrc attached");
+ gst_caps_unref (caps);
+ g_object_unref (appsink);
}
- g_object_unref (appsink);
-
- return GST_PAD_PROBE_OK;
+ return ret;
}
static GstPadLinkReturn
@@ -1168,12 +1238,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;
}
@@ -1193,7 +1263,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);
@@ -1382,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;
@@ -1404,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;
@@ -1638,7 +1715,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:
@@ -1897,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);
@@ -1955,52 +2039,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;
}
@@ -2022,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/gst-plugins/rtpendpoint/kmsrtpendpoint.c b/src/gst-plugins/rtpendpoint/kmsrtpendpoint.c
index ef0256ce9..091de4823 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);
}
}
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);
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/kmsicecandidate.c b/src/gst-plugins/webrtcendpoint/kmsicecandidate.c
index d0587fa96..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);
@@ -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);
+ GST_STR_NULL (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..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 ("%d", 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_DEBUG_OBJECT (self,
- "[IceCandidateFound] local: '%s', stream_id: %d, component_id: %d",
- kms_ice_candidate_get_candidate (kms_candidate),
- stream_id, component_id);
+ GST_LOG_OBJECT (self,
+ "[IceCandidateFound] local: '%s', stream_id: %u, component_id: %u",
+ 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 ("%d", 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'",
+ "[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_DEBUG_OBJECT (self, "[IceGatheringDone] stream_id: %d", 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, "%d", 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);
- g_free (ret);
+ OnGatheringDoneData *data = g_new0 (OnGatheringDoneData, 1);
+ data->self = g_object_ref (self);
+ data->stream_id = g_strdup_printf ("%u", stream_id);
+
+ 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);
- //convert id to char*
- g_snprintf (buff, 32, "%d", stream_id);
+ 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;
- ret = g_strdup (buff);
- state_ = kms_ice_nice_agent_nice_to_ice_state (state);
+ 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);
+}
- GST_DEBUG_OBJECT (self,
- "[IceComponentStateChanged] state: %s, stream_id: %d, component_id: %d",
- 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;
- g_signal_emit_by_name (parent, "on-ice-component-state-changed", ret,
- component_id, state_);
- g_free (ret);
+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);
+
+ return FALSE;
}
void
@@ -190,19 +303,20 @@ 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 ("%d", 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);
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;
@@ -210,25 +324,34 @@ 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);
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;
}
- GST_INFO_OBJECT (self,
+ GST_LOG_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);
- 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)
{
@@ -272,7 +397,13 @@ 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
+ // 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);
@@ -287,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)
@@ -312,23 +433,21 @@ 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_LOG_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_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);
}
-// 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 ("%d", id);
+ return g_strdup_printf ("%u", id);
}
static void
@@ -337,7 +456,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_LOG_OBJECT (self, "Remove data stream, stream_id: %u", id);
nice_agent_remove_stream (nice_agent->priv->agent, id);
}
@@ -349,7 +468,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_LOG_OBJECT (self, "Set remote credentials, stream_id: %u", id);
return nice_agent_set_remote_credentials (nice_agent->priv->agent,
id, ufrag, pwd);
@@ -362,7 +481,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_LOG_OBJECT (self, "Get local credentials, stream_id: %u", id);
nice_agent_get_local_credentials (nice_agent->priv->agent, id, ufrag, pwd);
}
@@ -371,14 +490,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
@@ -399,6 +518,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 +543,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: %d, stream_id: %d",
- 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,
@@ -435,7 +571,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_LOG_OBJECT (self, "[IceGatheringStarted] stream_id: %s", stream_id);
}
return ok;
@@ -460,49 +596,29 @@ 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] Error in libnice parsing, remote: '%s'",
kms_ice_candidate_get_candidate (candidate));
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);
- GST_DEBUG_OBJECT (self,
- "[AddIceCandidate] remote: '%s', stream_id: %d, component_id: %d",
+ 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);
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, adding remote: '%s'",
+ kms_ice_candidate_get_candidate (candidate));
ret = FALSE;
} else {
ret = TRUE;
@@ -624,7 +740,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/kmswebrtcbaseconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcbaseconnection.c
index e5a020438..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;
}
@@ -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 1884d4087..f37638ee1 100644
--- a/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c
+++ b/src/gst-plugins/webrtcendpoint/kmswebrtcendpoint.c
@@ -56,6 +56,9 @@ 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
+#define DEFAULT_ICE_TCP TRUE
enum
{
@@ -66,6 +69,9 @@ enum
PROP_PEM_CERTIFICATE,
PROP_NETWORK_INTERFACES,
PROP_EXTERNAL_ADDRESS,
+ PROP_EXTERNAL_IPV4,
+ PROP_EXTERNAL_IPV6,
+ PROP_ICE_TCP,
N_PROPERTIES
};
@@ -99,6 +105,9 @@ struct _KmsWebrtcEndpointPrivate
gchar *pem_certificate;
gchar *network_interfaces;
gchar *external_address;
+ gchar *external_ipv4;
+ gchar *external_ipv6;
+ gboolean ice_tcp;
};
/* Internal session management begin */
@@ -109,6 +118,14 @@ 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, 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_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,
sdp_sess->id_str, candidate);
@@ -119,6 +136,8 @@ on_ice_gathering_done (KmsWebrtcSession * sess, KmsWebrtcEndpoint * self)
{
KmsSdpSession *sdp_sess = KMS_SDP_SESSION (sess);
+ 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,
sdp_sess->id_str);
@@ -130,6 +149,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);
@@ -189,7 +212,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;
}
@@ -260,7 +283,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);
@@ -276,9 +299,9 @@ 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",
+ GST_DEBUG_OBJECT (self,
+ "[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);
@@ -317,13 +340,23 @@ 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_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,
"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,
+ "ice-tcp", self->priv->ice_tcp,
+ NULL);
g_signal_connect (webrtc_sess, "on-ice-candidate",
G_CALLBACK (on_ice_candidate), self);
@@ -424,14 +457,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_DEBUG_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 +480,20 @@ 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_DEBUG_OBJECT (self,
+ "[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_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);
@@ -496,6 +534,17 @@ 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;
+ 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;
@@ -531,6 +580,15 @@ 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;
+ 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;
@@ -544,7 +602,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);
@@ -561,13 +619,15 @@ 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);
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);
@@ -587,7 +647,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 +668,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 +687,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;
}
@@ -727,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,
@@ -756,6 +816,24 @@ 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));
+
+ 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
@@ -890,6 +968,9 @@ 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->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 753045151..983c673c0 100644
--- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c
+++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.c
@@ -56,6 +56,9 @@ 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 DEFAULT_ICE_TCP TRUE
#define IP_VERSION_6 6
@@ -90,6 +93,9 @@ enum
PROP_PEM_CERTIFICATE,
PROP_NETWORK_INTERFACES,
PROP_EXTERNAL_ADDRESS,
+ PROP_EXTERNAL_IPV4,
+ PROP_EXTERNAL_IPV6,
+ PROP_ICE_TCP,
N_PROPERTIES
};
@@ -304,6 +310,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 +331,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 +340,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_LOG_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 +359,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,33 +377,25 @@ 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,
- "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)");
}
- else {
- GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self,
- "... (Error)");
- }
return allow_error;
}
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)");
}
- else {
- GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, dbg, self,
- "... (Error)");
- }
return allow_error;
}
@@ -401,8 +403,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,
- "Adding remote candidate to libnice agent:"
- " Invalid media index: %u", index);
+ "[AddIceCandidate] Invalid media index: %u, remote: '%s'", index,
+ candidate_str);
return FALSE;
}
@@ -410,16 +412,15 @@ 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, remote: '%s'", index,
+ candidate_str);
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",
- 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;
}
@@ -428,9 +429,8 @@ 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",
- 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;
}
@@ -439,28 +439,28 @@ 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, remote: '%s'", index,
+ candidate_str);
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', remote: '%s'",
+ stream_id, candidate_str);
return FALSE;
}
- GST_DEBUG_OBJECT (self,
- "Added remote candidate to libnice agent, stream_id: '%s'", stream_id);
+ GST_LOG_OBJECT (self,
+ "[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);
@@ -468,8 +468,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;
}
}
@@ -544,7 +545,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);
@@ -572,17 +573,36 @@ 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)
{
- if (self->external_address != NULL) {
- kms_ice_candidate_set_address (cand, self->external_address);
-
+ 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),
+ 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_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'",
+ 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: '%s'",
- kms_ice_candidate_get_candidate (cand));
+ "[IceCandidateFound] Mangled local candidate with IPv6: '%s'",
+ 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 +748,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_LOG_OBJECT (self, "[IceGatheringDone] stream_id: %s", stream_id);
KMS_SDP_SESSION_LOCK (self);
@@ -763,7 +783,7 @@ kms_webrtc_session_component_state_change (KmsIceBaseAgent * agent,
KmsWebrtcSession * self)
{
GST_LOG_OBJECT (self,
- "state: %s, stream_id: '%s', component_id: %d",
+ "[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),
@@ -779,18 +799,36 @@ kms_webrtc_session_set_network_ifs_info (KmsWebrtcSession * self,
return;
}
+ GST_DEBUG_OBJECT (self, "Using network interfaces: %s",
+ self->network_interfaces);
+
kms_webrtc_base_connection_set_network_ifs_info (conn,
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)
{
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_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,
self->stun_server_port);
}
@@ -800,9 +838,17 @@ 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_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,
self->turn_port, self->turn_user, self->turn_password,
self->turn_transport);
@@ -816,21 +862,21 @@ 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)) {
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);
+
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,11 +884,8 @@ kms_webrtc_session_gather_candidates (KmsWebrtcSession * self)
if (ret) {
self->gather_started = TRUE;
- GST_DEBUG_OBJECT (self, "Gather candidates: 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);
+ GST_DEBUG_OBJECT (self, "[IceGatheringStarted] Add stored remote candidates");
+ kms_webrtc_session_agent_add_stored_ice_candidates (self);
}
KMS_SDP_SESSION_UNLOCK (self);
@@ -856,8 +899,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_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),
+ kms_ice_candidate_get_component (candidate));
KMS_SDP_SESSION_LOCK (self);
self->remote_candidates =
@@ -998,10 +1044,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");
@@ -1065,7 +1111,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,
@@ -1306,7 +1352,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,
@@ -1432,7 +1478,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));
@@ -1452,7 +1498,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;
@@ -1539,7 +1585,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;
@@ -1570,21 +1616,16 @@ 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);
}
- // 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:"
@@ -1635,7 +1676,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;
}
@@ -1653,13 +1694,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;
@@ -1669,16 +1725,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_INFO_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])'",
@@ -1723,6 +1771,17 @@ 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;
+ 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;
@@ -1761,6 +1820,15 @@ 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;
+ 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;
@@ -1774,7 +1842,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);
@@ -1788,6 +1856,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);
@@ -1821,9 +1891,9 @@ kms_webrtc_session_new_selected_pair_full (KmsIceBaseAgent * agent,
KmsIceCandidate * lcandidate,
KmsIceCandidate * rcandidate, KmsWebrtcSession * self)
{
- GST_DEBUG_OBJECT (self,
- "New candidate pair selected, local: '%s', remote: '%s'"
- ", stream_id: '%s', component_id: %d",
+ GST_LOG_OBJECT (self,
+ "[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);
@@ -1897,6 +1967,9 @@ 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->ice_tcp = DEFAULT_ICE_TCP;
self->gather_started = FALSE;
self->data_channels = g_hash_table_new_full (g_direct_hash,
@@ -1973,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,
@@ -2002,6 +2075,24 @@ 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_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 294c2b1b6..4f016d1f3 100644
--- a/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h
+++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsession.h
@@ -72,6 +72,9 @@ struct _KmsWebrtcSession
gchar *pem_certificate;
gchar *network_interfaces;
gchar *external_address;
+ gchar *external_ipv4;
+ gchar *external_ipv6;
+ gboolean ice_tcp;
guint16 min_port;
guint16 max_port;
diff --git a/src/server/config/RecorderEndpoint.conf.ini b/src/server/config/RecorderEndpoint.conf.ini
new file mode 100644
index 000000000..348c7f1f5
--- /dev/null
+++ b/src/server/config/RecorderEndpoint.conf.ini
@@ -0,0 +1,34 @@
+;; 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. 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
+;; 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/config/WebRtcEndpoint.conf.ini b/src/server/config/WebRtcEndpoint.conf.ini
index adb1c090a..def81997b 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
@@ -50,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.
@@ -66,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:
;;
@@ -83,9 +61,82 @@
;; 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.
+;;
+;; 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
-;pemCertificate is deprecated. Please use pemCertificateRSA instead
-;pemCertificate=
-;pemCertificateRSA=
-;pemCertificateECDSA=
+;; External IPv4 and IPv6 addresses of the media server.
+;;
+;; 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 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 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.
+;;
+;; is a single IPv4 address.
+;; is a single IPv6 address.
+;;
+;externalIPv4=198.51.100.1
+;externalIPv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334
+
+;; 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,
+;; 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.
+;;
+;; is a single IPv4 or IPv6 address.
+;;
+;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/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/implementation/objects/WebRtcEndpointImpl.cpp b/src/server/implementation/objects/WebRtcEndpointImpl.cpp
index 1424c23af..c509ec138 100644
--- a/src/server/implementation/objects/WebRtcEndpointImpl.cpp
+++ b/src/server/implementation/objects/WebRtcEndpointImpl.cpp
@@ -53,10 +53,16 @@ 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 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
{
@@ -328,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);
@@ -510,6 +516,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)) {
@@ -517,8 +545,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,28 +556,37 @@ 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");
+ }
+
+ 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 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);
- } 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);
+ DEFAULT_STUN_PORT) ) {
+ GST_DEBUG ("STUN port not found in config;"
+ " using default value: %d", DEFAULT_STUN_PORT);
+ }
- 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 stunAddress;
+ 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);
+ } else {
+ GST_DEBUG ("STUN server not found in config");
}
std::string turnURL;
@@ -561,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 () ) {
@@ -622,6 +658,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 ()
{
@@ -670,6 +754,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 d651d1725..63fca4c7b 100644
--- a/src/server/implementation/objects/WebRtcEndpointImpl.hpp
+++ b/src/server/implementation/objects/WebRtcEndpointImpl.hpp
@@ -46,12 +46,21 @@ 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;
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.PlayerEndpoint.kmd.json b/src/server/interface/elements.PlayerEndpoint.kmd.json
index 31ede992b..cedac6c15 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.
-
-
- -
- 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:
+
+
+ -
+ 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":
{
@@ -69,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,
@@ -89,7 +184,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
diff --git a/src/server/interface/elements.RecorderEndpoint.kmd.json b/src/server/interface/elements.RecorderEndpoint.kmd.json
index 5f341db86..40b9c59ef 100644
--- a/src/server/interface/elements.RecorderEndpoint.kmd.json
+++ b/src/server/interface/elements.RecorderEndpoint.kmd.json
@@ -3,77 +3,113 @@
{
"name": "RecorderEndpoint",
"extends": "UriEndpoint",
- "doc": "Provides the functionality to store contents.
+ "doc": "Provides functionality to store media 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.
+ 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:
-
- 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 will be written into the local file system.
+ Example:
-
- HTTP: Requires the server to support method PUT
+ 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
). 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 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.
+ 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.
- 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
- :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.
+ Note that
+
+ 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. Make sure your application subscribes to this
+ event, otherwise troubleshooting issues will be difficult.
+
+ -
+ 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 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...
-
@@ -94,22 +130,32 @@
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.
+
+
+ Recording must be stopped when no more data should be stored.
+ This is done 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
@@ -117,21 +163,36 @@
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:
+
+ -
+ When the remote video arrives to KMS, your WebRtcEndpoint will start
+ generating packets into the Kurento Pipeline, and it will trigger a
+
MediaFlowOutStateChange
event.
+
+ -
+ When video packets arrive from the WebRtcEndpoint to the RecorderEndpoint,
+ the RecorderEndpoint will raise a
MediaFlowInStateChange
event.
+
+ -
+ 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).
+
+
",
"constructor":
{
- "doc": "",
+ "doc": "Builder for the :rom:cls:`RecorderEndpoint`",
"params": [
{
"name": "mediaPipeline",
@@ -140,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"
@@ -201,5 +262,146 @@
"doc": "@deprecatedFired 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.
+
+ 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
+ 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: 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:
+
+
+ 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 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
+ 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"
+ ]
+ }
]
}
diff --git a/src/server/interface/elements.WebRtcEndpoint.kmd.json b/src/server/interface/elements.WebRtcEndpoint.kmd.json
index cfa1bea78..6827e2783 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.
-
@@ -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.
",
"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.
@@ -335,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.
@@ -388,9 +372,93 @@
",
"type": "String"
},
+ {
+ "name": "externalIPv4",
+ "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 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.
+
+ 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 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.
+
+externalAddress
is a single IPv4 or IPv6 address.
+Examples:
+
+ externalAddress=198.51.100.1
+ externalAddress=2001:0db8:85a3:0000:0000:8a2e:0370:7334
+
+@deprecated Use externalIPv4
and/or externalIPv6
instead.
+ ",
+ "type": "String"
+ },
{
"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
},
@@ -443,12 +511,29 @@
"methods": [
{
"name": "gatherCandidates",
- "doc": "Start the gathering of ICE candidates.
+ "doc": "Start the ICE candidate gathering.
+
+ 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.
+
- 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.
+ 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": []
@@ -761,26 +846,40 @@ 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": [
{
"name": "streamID",
- "doc": "Stream ID of the ice connection",
+ "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",
+ "doc": "Component ID of the ICE connection
+@deprecated Use componentId
instead.
+ ",
+ "type": "int"
+ },
+ {
+ "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"
}
]
diff --git a/src/server/interface/elements.kmd.json b/src/server/interface/elements.kmd.json
index 12f786741..116e917c1 100644
--- a/src/server/interface/elements.kmd.json
+++ b/src/server/interface/elements.kmd.json
@@ -1,7 +1,7 @@
{
"name": "elements",
- "version": "6.13.1-dev",
- "kurentoVersion": "^6.7.0",
+ "version": "6.16.1-dev",
+ "kurentoVersion": "^6.16.0",
"code": {
"kmd": {
"java": {
diff --git a/tests/check/CMakeLists.txt b/tests/check/CMakeLists.txt
index ed8f2b022..077839615 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
@@ -23,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/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);
diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c
index 126b9467f..2e23adb1d 100644
--- a/tests/check/element/webrtcendpoint.c
+++ b/tests/check/element/webrtcendpoint.c
@@ -20,7 +20,9 @@
#include
#include
+#include
+#include
#include
#define KMS_VIDEO_PREFIX "video_src_"
@@ -1297,193 +1299,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,
@@ -1534,7 +1349,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);
@@ -1542,13 +1357,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",
@@ -1572,7 +1387,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);
@@ -1583,7 +1398,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);
@@ -1661,7 +1476,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!"
@@ -2089,7 +1903,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",
@@ -2100,7 +1913,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)
{
@@ -2294,26 +2106,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_OBJECT (self, "SessionId: '%s', candidate: '%s'", sess_id,
+ kms_ice_candidate_get_candidate (candidate));
+
+ if (tcp_type == KMS_ICE_TCP_CANDIDATE_TYPE_ACTIVE) {
+ return;
+ }
- 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);
+ // 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;
@@ -2321,11 +2158,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,
@@ -2339,16 +2183,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);
@@ -2357,44 +2201,30 @@ 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;
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);
-#ifdef HAVE_LIBNICE_0_1_14
- fail_unless (ret);
-#else
+ g_signal_emit_by_name (
+ second_offerer, "gather-candidates", second_offerer_sess_id, &ret);
fail_if (ret);
-#endif
gst_sdp_message_free (offer);
gst_sdp_message_free (second_offer);
@@ -2403,9 +2233,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)
@@ -2513,9 +2348,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);
+ }
}
/**
@@ -2573,7 +2411,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)
{
@@ -2586,9 +2424,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"
@@ -2609,7 +2448,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 *)
@@ -2624,6 +2463,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
*/
@@ -2635,40 +2586,29 @@ 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);
+
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;
}
diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt
index 937858956..645288e62 100644
--- a/tests/server/CMakeLists.txt
+++ b/tests/server/CMakeLists.txt
@@ -1,9 +1,8 @@
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}"
- "DEBUG_MEDIASET=TRUE"
)
list(APPEND SUPPRESSIONS
"${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp")
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);