diff --git a/README.md b/README.md index 771d27b554..3394ba7faa 100644 --- a/README.md +++ b/README.md @@ -281,8 +281,8 @@ or on the command line: -T, --ice-tcp Whether to enable ICE-TCP or not (warning: only works with ICE Lite) (default=off) - -q, --max-nack-queue=number Maximum size of the NACK queue (in ms) per user - for retransmissions + -Q, --min-nack-queue=number Minimum size of the NACK queue (in ms) per user + for retransmissions, no matter the RTT -t, --no-media-timer=number Time (in s) that should pass with no media (audio or video) being received before Janus notifies you about this diff --git a/conf/janus.jcfg.sample.in b/conf/janus.jcfg.sample.in index d607f9e4db..891761bb65 100644 --- a/conf/janus.jcfg.sample.in +++ b/conf/janus.jcfg.sample.in @@ -131,8 +131,8 @@ certificates: { } # Media-related stuff: you can configure whether if you want -# to enable IPv6 support, the maximum size of the NACK queue (in -# milliseconds, defaults to 500ms) for retransmissions, the range of +# to enable IPv6 support, the minimum size of the NACK queue (in ms, +# defaults to 200ms) for retransmissions no matter the RTT, the range of # ports to use for RTP and RTCP (by default, no range is envisaged), the # starting MTU for DTLS (1200 by default, it adapts automatically), # how much time, in seconds, should pass with no media (audio or @@ -148,7 +148,7 @@ certificates: { # you should pick a reasonable trade-off (usually 2*max expected RTT). media: { #ipv6 = true - #max_nack_queue = 500 + #min_nack_queue = 500 #rtp_port_range = "20000-40000" #dtls_mtu = 1200 #no_media_timer = 1 diff --git a/html/admin.js b/html/admin.js index 8de8b89a61..9e4121099b 100644 --- a/html/admin.js +++ b/html/admin.js @@ -339,20 +339,20 @@ function updateSettings() { setLogLevel(result); }); }); - } else if(k === 'max_nack_queue') { - $('#'+k).append(''); + } else if(k === 'min_nack_queue') { + $('#'+k).append(''); $('#'+k + "_button").click(function() { - bootbox.prompt("Set the new desired max NACK queue (a positive integer, currently " + settings["max_nack_queue"] + ")", function(result) { + bootbox.prompt("Set the new desired min NACK queue (a positive integer, currently " + settings["min_nack_queue"] + ")", function(result) { if(isNaN(result)) { - bootbox.alert("Invalid max NACK queue (should be a positive integer)"); + bootbox.alert("Invalid min NACK queue (should be a positive integer)"); return; } result = parseInt(result); if(result < 0) { - bootbox.alert("Invalid max NACK queue (should be a positive integer)"); + bootbox.alert("Invalid min NACK queue (should be a positive integer)"); return; } - setMaxNackQueue(result); + setMinNackQueue(result); }); }); } else if(k === 'no_media_timer') { @@ -510,8 +510,8 @@ function setLibniceDebug(enable) { sendSettingsRequest(request); } -function setMaxNackQueue(queue) { - var request = { "janus": "set_max_nack_queue", "max_nack_queue": queue, "transaction": randomString(12), "admin_secret": secret }; +function setMinNackQueue(queue) { + var request = { "janus": "set_min_nack_queue", "min_nack_queue": queue, "transaction": randomString(12), "admin_secret": secret }; sendSettingsRequest(request); } diff --git a/ice.c b/ice.c index ad9136cc67..44e98b8f86 100644 --- a/ice.c +++ b/ice.c @@ -459,21 +459,22 @@ static void janus_ice_free_queued_packet(janus_ice_queued_packet *pkt) { g_free(pkt); } -/* Maximum value, in milliseconds, for the NACK queue/retransmissions (default=500ms) */ -#define DEFAULT_MAX_NACK_QUEUE 500 +/* Minimum and maximum value, in milliseconds, for the NACK queue/retransmissions (default=200ms/1000ms) */ +#define DEFAULT_MIN_NACK_QUEUE 200 +#define DEFAULT_MAX_NACK_QUEUE 1000 /* Maximum ignore count after retransmission (200ms) */ #define MAX_NACK_IGNORE 200000 -static uint max_nack_queue = DEFAULT_MAX_NACK_QUEUE; -void janus_set_max_nack_queue(uint mnq) { - max_nack_queue = mnq; - if(max_nack_queue == 0) +static uint16_t min_nack_queue = DEFAULT_MIN_NACK_QUEUE; +void janus_set_min_nack_queue(uint16_t mnq) { + min_nack_queue = mnq < DEFAULT_MAX_NACK_QUEUE ? mnq : DEFAULT_MAX_NACK_QUEUE; + if(min_nack_queue == 0) JANUS_LOG(LOG_VERB, "Disabling NACK queue\n"); else - JANUS_LOG(LOG_VERB, "Setting max NACK queue to %dms\n", max_nack_queue); + JANUS_LOG(LOG_VERB, "Setting min NACK queue to %dms\n", min_nack_queue); } -uint janus_get_max_nack_queue(void) { - return max_nack_queue; +uint16_t janus_get_min_nack_queue(void) { + return min_nack_queue; } /* Helper to clean old NACK packets in the buffer when they exceed the queue time limit */ static void janus_cleanup_nack_buffer(gint64 now, janus_ice_stream *stream, gboolean audio, gboolean video) { @@ -481,7 +482,7 @@ static void janus_cleanup_nack_buffer(gint64 now, janus_ice_stream *stream, gboo janus_ice_component *component = stream->component; if(audio && component->audio_retransmit_buffer) { janus_rtp_packet *p = (janus_rtp_packet *)g_queue_peek_head(component->audio_retransmit_buffer); - while(p && (!now || (now - p->created >= (gint64)max_nack_queue*1000))) { + while(p && (!now || (now - p->created >= (gint64)stream->nack_queue_ms*1000))) { /* Packet is too old, get rid of it */ g_queue_pop_head(component->audio_retransmit_buffer); /* Remove from hashtable too */ @@ -495,7 +496,7 @@ static void janus_cleanup_nack_buffer(gint64 now, janus_ice_stream *stream, gboo } if(video && component->video_retransmit_buffer) { janus_rtp_packet *p = (janus_rtp_packet *)g_queue_peek_head(component->video_retransmit_buffer); - while(p && (!now || (now - p->created >= (gint64)max_nack_queue*1000))) { + while(p && (!now || (now - p->created >= (gint64)stream->nack_queue_ms*1000))) { /* Packet is too old, get rid of it */ g_queue_pop_head(component->video_retransmit_buffer); /* Remove from hashtable too */ @@ -2746,10 +2747,28 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp /* Let's process this RTCP (compound?) packet, and update the RTCP context for this stream in case */ rtcp_context *rtcp_ctx = video ? stream->video_rtcp_ctx[vindex] : stream->audio_rtcp_ctx; - if (janus_rtcp_parse(rtcp_ctx, buf, buflen) < 0) { + uint32_t rtt = rtcp_ctx ? rtcp_ctx->rtt : 0; + if(janus_rtcp_parse(rtcp_ctx, buf, buflen) < 0) { /* Drop the packet if the parsing function returns with an error */ return; } + if(rtcp_ctx && rtcp_ctx->rtt != rtt) { + /* Check the current RTT, to see if we need to update the size of the queue: we take + * the highest RTT (audio or video) and add 100ms just to be conservative */ + uint32_t audio_rtt = janus_rtcp_context_get_rtt(stream->audio_rtcp_ctx), + video_rtt = janus_rtcp_context_get_rtt(stream->video_rtcp_ctx[0]); + uint16_t nack_queue_ms = (audio_rtt > video_rtt ? audio_rtt : video_rtt) + 100; + if(nack_queue_ms > DEFAULT_MAX_NACK_QUEUE) + nack_queue_ms = DEFAULT_MAX_NACK_QUEUE; + else if(nack_queue_ms < min_nack_queue) + nack_queue_ms = min_nack_queue; + uint16_t mavg = rtt ? ((7*stream->nack_queue_ms + nack_queue_ms)/8) : nack_queue_ms; + if(mavg > DEFAULT_MAX_NACK_QUEUE) + mavg = DEFAULT_MAX_NACK_QUEUE; + else if(mavg < min_nack_queue) + mavg = min_nack_queue; + stream->nack_queue_ms = mavg; + } JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Got %s RTCP (%d bytes)\n", handle->handle_id, video ? "video" : "audio", buflen); /* Now let's see if there are any NACKs to handle */ @@ -3344,6 +3363,7 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi stream->audio_payload_type = -1; stream->video_payload_type = -1; stream->video_rtx_payload_type = -1; + stream->nack_queue_ms = min_nack_queue; /* FIXME By default, if we're being called we're DTLS clients, but this may be changed by ICE... */ stream->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS; if(audio) { @@ -4105,7 +4125,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu } /* Before encrypting, check if we need to copy the unencrypted payload (e.g., for rtx/90000) */ janus_rtp_packet *p = NULL; - if(max_nack_queue > 0 && !pkt->retransmission && pkt->type == JANUS_ICE_PACKET_VIDEO && component->do_video_nacks && + if(stream->nack_queue_ms > 0 && !pkt->retransmission && pkt->type == JANUS_ICE_PACKET_VIDEO && component->do_video_nacks && janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_RFC4588_RTX)) { /* Save the packet for retransmissions that may be needed later: start by * making room for two more bytes to store the original sequence number */ @@ -4212,7 +4232,7 @@ static gboolean janus_ice_outgoing_traffic_handle(janus_ice_handle *handle, janu if(rtcp_ctx) g_atomic_int_inc(&rtcp_ctx->sent_packets_since_last_rr); } - if(max_nack_queue > 0 && !pkt->retransmission) { + if(stream->nack_queue_ms > 0 && !pkt->retransmission) { /* Save the packet for retransmissions that may be needed later */ if((pkt->type == JANUS_ICE_PACKET_AUDIO && !component->do_audio_nacks) || (pkt->type == JANUS_ICE_PACKET_VIDEO && !component->do_video_nacks)) { diff --git a/ice.h b/ice.h index f91a4386a1..883a012bb7 100644 --- a/ice.h +++ b/ice.h @@ -122,12 +122,12 @@ gboolean janus_ice_is_full_trickle_enabled(void); /*! \brief Method to check whether IPv6 candidates are enabled/supported or not (still WIP) * @returns true if IPv6 candidates are enabled/supported, false otherwise */ gboolean janus_ice_is_ipv6_enabled(void); -/*! \brief Method to modify the max NACK value (i.e., the number of packets per handle to store for retransmissions) - * @param[in] mnq The new max NACK value */ -void janus_set_max_nack_queue(uint mnq); -/*! \brief Method to get the current max NACK value (i.e., the number of packets per handle to store for retransmissions) - * @returns The current max NACK value */ -uint janus_get_max_nack_queue(void); +/*! \brief Method to modify the min NACK value (i.e., the minimum time window of packets per handle to store for retransmissions) + * @param[in] mnq The new min NACK value */ +void janus_set_min_nack_queue(uint16_t mnq); +/*! \brief Method to get the current min NACK value (i.e., the minimum time window of packets per handle to store for retransmissions) + * @returns The current min NACK value */ +uint16_t janus_get_min_nack_queue(void); /*! \brief Method to modify the no-media event timer (i.e., the number of seconds where no media arrives before Janus notifies this) * @param[in] timer The new timer value, in seconds */ void janus_set_no_media_timer(uint timer); @@ -386,6 +386,8 @@ struct janus_ice_stream { janus_rtcp_context *audio_rtcp_ctx; /*! \brief RTCP context(s) for the video stream (may be simulcasting) */ janus_rtcp_context *video_rtcp_ctx[3]; + /*! \brief Size of the NACK queue (in ms), dynamically updated per the RTT */ + uint16_t nack_queue_ms; /*! \brief Map(s) of the NACKed packets (to track retransmissions and avoid duplicates) */ GHashTable *rtx_nacked[3]; /*! \brief First received audio NTP timestamp */ diff --git a/janus.1 b/janus.1 index dc63062345..0af05aac9c 100644 --- a/janus.1 +++ b/janus.1 @@ -80,8 +80,8 @@ Whether to enable the ICE Lite mode or not (default=off) .BR \-T ", " \-\-ice-tcp Whether to enable ICE-TCP or not (warning: only works with ICE Lite) (default=off) .TP -.BR \-q ", " \-\-max-nack-queue=\fInumber\fR -Maximum size of the NACK queue per user for retransmissions +.BR \-Q ", " \-\-min-nack-queue=\fInumber\fR +Minimum size of the NACK queue (in ms) per user for retransmissions, no matter the RTT .TP .BR \-t ", " \-\-no-media-timer=\fInumber\fR Time (in s) that should pass with no media (audio or video) being received before Janus notifies you about this diff --git a/janus.c b/janus.c index d9777e77aa..d3574dfeda 100644 --- a/janus.c +++ b/janus.c @@ -132,7 +132,7 @@ static struct janus_json_parameter colors_parameters[] = { {"colors", JANUS_JSON_BOOL, JANUS_JSON_PARAM_REQUIRED} }; static struct janus_json_parameter mnq_parameters[] = { - {"max_nack_queue", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE} + {"min_nack_queue", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE} }; static struct janus_json_parameter nmt_parameters[] = { {"no_media_timer", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE} @@ -296,6 +296,7 @@ static json_t *janus_info(const char *transaction) { json_object_set_new(info, "ice-lite", janus_ice_is_ice_lite_enabled() ? json_true() : json_false()); json_object_set_new(info, "ice-tcp", janus_ice_is_ice_tcp_enabled() ? json_true() : json_false()); json_object_set_new(info, "full-trickle", janus_ice_is_full_trickle_enabled() ? json_true() : json_false()); + json_object_set_new(info, "min-nack-queue", json_integer(janus_get_min_nack_queue())); json_object_set_new(info, "twcc-period", json_integer(janus_get_twcc_period())); if(janus_ice_get_stun_server() != NULL) { char server[255]; @@ -1817,7 +1818,7 @@ int janus_process_incoming_admin_request(janus_request *request) { json_object_set_new(status, "locking_debug", lock_debug ? json_true() : json_false()); json_object_set_new(status, "refcount_debug", refcount_debug ? json_true() : json_false()); json_object_set_new(status, "libnice_debug", janus_ice_is_ice_debugging_enabled() ? json_true() : json_false()); - json_object_set_new(status, "max_nack_queue", json_integer(janus_get_max_nack_queue())); + json_object_set_new(status, "min_nack_queue", json_integer(janus_get_min_nack_queue())); json_object_set_new(status, "no_media_timer", json_integer(janus_get_no_media_timer())); json_object_set_new(status, "slowlink_threshold", json_integer(janus_get_slowlink_threshold())); json_object_set_new(reply, "status", status); @@ -1963,8 +1964,8 @@ int janus_process_incoming_admin_request(janus_request *request) { /* Send the success reply */ ret = janus_process_success(request, reply); goto jsondone; - } else if(!strcasecmp(message_text, "set_max_nack_queue")) { - /* Change the current value for the max NACK queue */ + } else if(!strcasecmp(message_text, "set_min_nack_queue")) { + /* Change the current value for the min NACK queue */ JANUS_VALIDATE_JSON_OBJECT(root, mnq_parameters, error_code, error_cause, FALSE, JANUS_ERROR_MISSING_MANDATORY_ELEMENT, JANUS_ERROR_INVALID_ELEMENT_TYPE); @@ -1972,16 +1973,12 @@ int janus_process_incoming_admin_request(janus_request *request) { ret = janus_process_error_string(request, session_id, transaction_text, error_code, error_cause); goto jsondone; } - json_t *mnq = json_object_get(root, "max_nack_queue"); + json_t *mnq = json_object_get(root, "min_nack_queue"); int mnq_num = json_integer_value(mnq); - if(mnq_num < 0 || (mnq_num > 0 && mnq_num < 200)) { - ret = janus_process_error(request, session_id, transaction_text, JANUS_ERROR_INVALID_ELEMENT_TYPE, "Invalid element type (max_nack_queue, if provided, should be greater than 200)"); - goto jsondone; - } - janus_set_max_nack_queue(mnq_num); + janus_set_min_nack_queue(mnq_num); /* Prepare JSON reply */ json_t *reply = janus_create_message("success", 0, transaction_text); - json_object_set_new(reply, "max_nack_queue", json_integer(janus_get_max_nack_queue())); + json_object_set_new(reply, "min_nack_queue", json_integer(janus_get_min_nack_queue())); /* Send the success reply */ ret = janus_process_success(request, reply); goto jsondone; @@ -2776,6 +2773,7 @@ json_t *janus_admin_stream_summary(janus_ice_stream *stream) { if(stream->transport_wide_cc_ext_id > 0) json_object_set_new(bwe, "twcc-ext-id", json_integer(stream->transport_wide_cc_ext_id)); json_object_set_new(s, "bwe", bwe); + json_object_set_new(s, "nack-queue-ms", json_integer(stream->nack_queue_ms)); json_t *components = json_array(); if(stream->component) { json_t *c = janus_admin_component_summary(stream->component); @@ -4121,10 +4119,10 @@ gint main(int argc, char *argv[]) if(args_info.ipv6_candidates_given) { janus_config_add(config, config_media, janus_config_item_create("ipv6", "true")); } - if(args_info.max_nack_queue_given) { + if(args_info.min_nack_queue_given) { char mnq[20]; - g_snprintf(mnq, 20, "%d", args_info.max_nack_queue_arg); - janus_config_add(config, config_media, janus_config_item_create("max_nack_queue", mnq)); + g_snprintf(mnq, 20, "%d", args_info.min_nack_queue_arg); + janus_config_add(config, config_media, janus_config_item_create("min_nack_queue", mnq)); } if(args_info.no_media_timer_given) { char nmt[20]; @@ -4486,15 +4484,13 @@ gint main(int argc, char *argv[]) } } /* NACK related stuff */ - item = janus_config_get(config, config_media, janus_config_type_item, "max_nack_queue"); + item = janus_config_get(config, config_media, janus_config_type_item, "min_nack_queue"); if(item && item->value) { int mnq = atoi(item->value); if(mnq < 0) { - JANUS_LOG(LOG_WARN, "Ignoring max_nack_queue value as it's not a positive integer\n"); - } else if(mnq > 0 && mnq < 200) { - JANUS_LOG(LOG_WARN, "Ignoring max_nack_queue value as it's less than 200\n"); + JANUS_LOG(LOG_WARN, "Ignoring min_nack_queue value as it's not a positive integer\n"); } else { - janus_set_max_nack_queue(mnq); + janus_set_min_nack_queue(mnq); } } /* no-media timer */ diff --git a/janus.ggo b/janus.ggo index bacf0cd812..0bfb4c88e7 100644 --- a/janus.ggo +++ b/janus.ggo @@ -20,7 +20,7 @@ option "libnice-debug" l "Whether to enable libnice debugging or not" flag off option "full-trickle" f "Do full-trickle instead of half-trickle" flag off option "ice-lite" I "Whether to enable the ICE Lite mode or not" flag off option "ice-tcp" T "Whether to enable ICE-TCP or not (warning: only works with ICE Lite)" flag off -option "max-nack-queue" q "Maximum size of the NACK queue (in ms) per user for retransmissions" int typestr="number" optional +option "min-nack-queue" Q "Minimum size of the NACK queue (in ms) per user for retransmissions, no matter the RTT" int typestr="number" optional option "no-media-timer" t "Time (in s) that should pass with no media (audio or video) being received before Janus notifies you about this" int typestr="number" optional option "slowlink-threshold" W "Number of lost packets (per s) that should trigger a 'slowlink' Janus API event to users" int typestr="number" optional option "rtp-port-range" r "Port range to use for RTP/RTCP" string typestr="min-max" optional diff --git a/mainpage.dox b/mainpage.dox index 830e867132..b15e5e3d0d 100644 --- a/mainpage.dox +++ b/mainpage.dox @@ -2271,7 +2271,7 @@ const token = getJanusToken('janus', ['janus.plugin.videoroom']), * the reference counters in Janus on the fly (useful if you're experiencing * memory leaks in the Janus structures and want to investigate them); * - \c set_libnice_debug: selectively enable/disable libnice debugging; - * - \c set_max_nack_queue: change the value of the max NACK queue window; + * - \c set_min_nack_queue: change the value of the min NACK queue window; * - \c set_no_media_timer: change the value of the no-media timer property; * - \c set_slowlink_threshold: change the value of the slowlink-threshold property. *