diff --git a/conf/janus.plugin.streaming.jcfg.sample.in b/conf/janus.plugin.streaming.jcfg.sample.in index 7eb058bea5..deebf9be3a 100644 --- a/conf/janus.plugin.streaming.jcfg.sample.in +++ b/conf/janus.plugin.streaming.jcfg.sample.in @@ -78,8 +78,16 @@ # url = RTSP stream URL (only for restreaming RTSP) # rtsp_user = RTSP authorization username (only if type=rtsp) # rtsp_pwd = RTSP authorization password (only if type=rtsp) -# rtspiface = network interface or IP address to bind to, if any (binds to all otherwise), when receiving RTSP streams # rtsp_failcheck = whether an error should be returned if connecting to the RTSP server fails (default=true) +# rtspiface = network interface or IP address to bind to, if any (binds to all otherwise), when receiving RTSP streams +# rtsp_reconnect_delay = after n seconds passed and no media assumed, the RTSP server has gone and schedule a reconnect (default=5s) +# rtsp_session_timeout = by default the streaming plugin will check the RTSP connection with an OPTIONS query, +# the value of the timeout comes from the RTSP session initializer and by default +# this session timeout is the half of this value In some cases this value can be too high (for example more than one minute) +# because of the media server. In that case this plugin will calculate the timeout with this +# formula: timeout = min(session_timeout, rtsp_session_timeout / 2). (default=0s) +# rtsp_timeout = communication timeout (CURLOPT_TIMEOUT) for cURL call gathering the RTSP information (default=10s) +# rtsp_conn_timeout = connection timeout for cURL (CURLOPT_CONNECTTIMEOUT) call gathering the RTSP information (default=5s) # # Notice that, for 'rtsp' mountpoints, normally the plugin uses the exact # SDP rtpmap and fmtp attributes the remote camera or RTSP server sent. @@ -249,4 +257,8 @@ file-ondemand-sample: { #rtsp_user = "username" #rtsp_pwd = "password" #secret = "adminpwd" + #reconnect_delay = 5 + #session_timeout = 0 + # rtsp_timeout = 10 + # rtsp_conn_timeout = 5 #} diff --git a/plugins/janus_streaming.c b/plugins/janus_streaming.c index 2f09777bc3..b23ac74c6a 100644 --- a/plugins/janus_streaming.c +++ b/plugins/janus_streaming.c @@ -140,6 +140,14 @@ rtsp_user = RTSP authorization username, if needed rtsp_pwd = RTSP authorization password, if needed rtsp_failcheck = whether an error should be returned if connecting to the RTSP server fails (default=true) rtspiface = network interface IP address or device name to listen on when receiving RTSP streams +rtsp_reconnect_delay = after n seconds passed and no media assumed, the RTSP server has gone and schedule a reconnect (default=5s) +rtsp_session_timeout = by default the streaming plugin will check the RTSP connection with an OPTIONS query, + the value of the timeout comes from the RTSP session initializer and by default + this session timeout is the half of this value In some cases this value can be too high (for example more than one minute) + because of the media server. In that case this plugin will calculate the timeout with this + formula: timeout = min(session_timeout, rtsp_session_timeout / 2). (default=0s) +rtsp_timeout = communication timeout (CURLOPT_TIMEOUT) for cURL call gathering the RTSP information (default=10s) +rtsp_conn_timeout = connection timeout for cURL (CURLOPT_CONNECTTIMEOUT) call gathering the RTSP information (default=5s) \endverbatim * * \section streamapi Streaming API @@ -721,6 +729,11 @@ rtspiface = network interface IP address or device name to listen on when receiv #include "../utils.h" #include "../ip-utils.h" +/* Default settings */ +#define JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT 0 /* Overwrite the RTSP session timeout. If set to zero, the RTSP timeout is derived from a session. */ +#define JANUS_STREAMING_DEFAULT_RECONNECT_DELAY 5 /* Reconnecting delay in seconds. */ +#define JANUS_STREAMING_DEFAULT_CURL_TIMEOUT 10L /* Communication timeout for cURL. */ +#define JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT 5L /* Connection timeout for cURL. */ /* Plugin information */ #define JANUS_STREAMING_VERSION 8 @@ -855,6 +868,10 @@ static struct janus_json_parameter rtsp_parameters[] = { {"url", JSON_STRING, 0}, {"rtsp_user", JSON_STRING, 0}, {"rtsp_pwd", JSON_STRING, 0}, + {"rtsp_reconnect_delay", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + {"rtsp_session_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + {"rtsp_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + {"rtsp_conn_timeout", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"audiortpmap", JSON_STRING, 0}, {"audiofmtp", JSON_STRING, 0}, {"audiopt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, @@ -1066,10 +1083,14 @@ typedef struct janus_streaming_rtp_source { janus_streaming_buffer *curldata; char *rtsp_url; char *rtsp_username, *rtsp_password; - int ka_timeout; + gint64 ka_timeout; char *rtsp_ahost, *rtsp_vhost; gboolean reconnecting; gint64 reconnect_timer; + gint64 reconnect_delay; + gint64 session_timeout; + int rtsp_timeout; + int rtsp_conn_timeout; janus_mutex rtsp_mutex; #endif janus_streaming_rtp_keyframe keyframe; @@ -1188,9 +1209,9 @@ janus_streaming_mountpoint *janus_streaming_create_rtsp_source( gboolean doaudio, int audiopt, char *artpmap, char *afmtp, gboolean dovideo, int videopt, char *vrtpmap, char *vfmtp, gboolean bufferkf, const janus_network_address *iface, int threads, + gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout, gboolean error_on_failure); - typedef struct janus_streaming_message { janus_plugin_session *handle; char *transaction; @@ -2101,6 +2122,10 @@ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { janus_config_item *iface = janus_config_get(config, cat, janus_config_type_item, "rtspiface"); janus_config_item *failerr = janus_config_get(config, cat, janus_config_type_item, "rtsp_failcheck"); janus_config_item *threads = janus_config_get(config, cat, janus_config_type_item, "threads"); + janus_config_item *reconnect_delay = janus_config_get(config, cat, janus_config_type_item, "rtsp_reconnect_delay"); + janus_config_item *session_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_session_timeout"); + janus_config_item *rtsp_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_timeout"); + janus_config_item *rtsp_conn_timeout = janus_config_get(config, cat, janus_config_type_item, "rtsp_conn_timeout"); janus_network_address iface_value; if(file == NULL || file->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'rtsp' mountpoint '%s', missing mandatory information...\n", cat->name); @@ -2153,6 +2178,10 @@ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { bufferkf, iface && iface->value ? &iface_value : NULL, (threads && threads->value) ? atoi(threads->value) : 0, + ((reconnect_delay && reconnect_delay->value) ? atoi(reconnect_delay->value) : JANUS_STREAMING_DEFAULT_RECONNECT_DELAY) * G_USEC_PER_SEC, + ((session_timeout && session_timeout->value) ? atoi(session_timeout->value) : JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT) * G_USEC_PER_SEC, + ((rtsp_timeout && rtsp_timeout->value) ? atoi(rtsp_timeout->value) : JANUS_STREAMING_DEFAULT_CURL_TIMEOUT), + ((rtsp_conn_timeout && rtsp_conn_timeout->value) ? atoi(rtsp_conn_timeout->value) : JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT), error_on_failure)) == NULL) { JANUS_LOG(LOG_ERR, "Error creating 'rtsp' mountpoint '%s'...\n", cat->name); cl = cl->next; @@ -3162,6 +3191,10 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi json_t *iface = json_object_get(root, "rtspiface"); json_t *threads = json_object_get(root, "threads"); json_t *failerr = json_object_get(root, "rtsp_failcheck"); + json_t *reconnect_delay = json_object_get(root, "rtsp_reconnect_delay"); + json_t *session_timeout = json_object_get(root, "rtsp_session_timeout"); + json_t *rtsp_timeout = json_object_get(root, "rtsp_timeout"); + json_t *rtsp_conn_timeout = json_object_get(root, "rtsp_conn_timeout"); if(failerr == NULL) /* For an old typo, we support the legacy syntax too */ failerr = json_object_get(root, "rtsp_check"); gboolean doaudio = audio ? json_is_true(audio) : FALSE; @@ -3205,6 +3238,10 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi (char *)json_string_value(videortpmap), (char *)json_string_value(videofmtp), videobufferkf ? json_is_true(videobufferkf) : FALSE, &multicast_iface, (threads ? json_integer_value(threads) : 0), + ((reconnect_delay ? json_integer_value(reconnect_delay) : JANUS_STREAMING_DEFAULT_RECONNECT_DELAY) * G_USEC_PER_SEC), + ((session_timeout ? json_integer_value(session_timeout) : JANUS_STREAMING_DEFAULT_SESSION_TIMEOUT) * G_USEC_PER_SEC), + (rtsp_timeout ? json_integer_value(rtsp_timeout) : JANUS_STREAMING_DEFAULT_CURL_TIMEOUT), + (rtsp_conn_timeout ? json_integer_value(rtsp_conn_timeout) : JANUS_STREAMING_DEFAULT_CURL_CONNECT_TIMEOUT), error_on_failure); janus_mutex_lock(&mountpoints_mutex); g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid); @@ -5598,7 +5635,7 @@ static int janus_streaming_allocate_port_pair(const char *name, const char *medi static int janus_streaming_get_fd_port(int fd) { struct sockaddr_in6 server = { 0 }; socklen_t len = sizeof(server); - if(getsockname(fd, &server, &len) == -1) { + if(getsockname(fd, (struct sockaddr *)&server, &len) == -1) { return -1; } @@ -6328,6 +6365,11 @@ static int janus_streaming_rtsp_parse_sdp(const char *buffer, const char *name, return 0; } +/* Helper function to calculating the minimum value if 'a' is bigger than zero */ +static inline gint64 janus_streaming_min_if(gint64 a, gint64 b) { + return a > 0 ? (a > b ? b : a) : b; +} + /* Static helper to connect to an RTSP server, considering we might do this either * when creating a new mountpoint, or when reconnecting after some failure */ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp) { @@ -6350,8 +6392,8 @@ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_URL, source->rtsp_url); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, source->rtsp_timeout); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, source->rtsp_conn_timeout); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); #if CURL_AT_LEAST_VERSION(7, 66, 0) @@ -6403,7 +6445,6 @@ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp } JANUS_LOG(LOG_VERB, "DESCRIBE answer:%s\n", curldata->buffer); /* Parse the SDP we just got to figure out the negotiated media */ - int ka_timeout = 0; int vpt = -1; char vrtpmap[2048]; vrtpmap[0] = '\0'; @@ -6574,8 +6615,8 @@ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp } else if(is_session) { if(!strcasecmp(name, "timeout")) { /* Take note of the timeout, for keep-alives */ - ka_timeout = atoi(value); - JANUS_LOG(LOG_VERB, " -- RTSP session timeout (video): %d\n", ka_timeout); + source->ka_timeout = janus_streaming_min_if(source->session_timeout, atoi(value) / 2 * G_USEC_PER_SEC); + JANUS_LOG(LOG_VERB, " -- RTSP session timeout (video): %"SCNi64" ms\n", source->ka_timeout / 1000); } } } @@ -6747,8 +6788,8 @@ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp } else if(is_session) { if(!strcasecmp(name, "timeout")) { /* Take note of the timeout, for keep-alives */ - ka_timeout = atoi(value); - JANUS_LOG(LOG_VERB, " -- RTSP session timeout (audio): %d\n", ka_timeout); + source->ka_timeout = janus_streaming_min_if(source->session_timeout, atoi(value) / 2 * G_USEC_PER_SEC); + JANUS_LOG(LOG_VERB, " -- RTSP session timeout (audio): %"SCNi64" ms\n", source->ka_timeout / 1000); } } } @@ -6850,7 +6891,6 @@ static int janus_streaming_rtsp_connect_to_server(janus_streaming_mountpoint *mp source->rtsp_vhost = g_strdup(vhost); source->curl = curl; source->curldata = curldata; - source->ka_timeout = ka_timeout; return 0; } @@ -6951,6 +6991,7 @@ janus_streaming_mountpoint *janus_streaming_create_rtsp_source( gboolean doaudio, int acodec, char *artpmap, char *afmtp, gboolean dovideo, int vcodec, char *vrtpmap, char *vfmtp, gboolean bufferkf, const janus_network_address *iface, int threads, + gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout, gboolean error_on_failure) { char id_num[30]; if(!string_ids) { @@ -6961,6 +7002,23 @@ janus_streaming_mountpoint *janus_streaming_create_rtsp_source( JANUS_LOG(LOG_ERR, "Can't add 'rtsp' stream, missing url...\n"); return NULL; } + if(reconnect_delay < 0) { + JANUS_LOG(LOG_ERR, "rtsp_reconnect_delay can't be smaller than zero.\n"); + return NULL; + } + if(session_timeout < 0) { + JANUS_LOG(LOG_ERR, "rtsp_session_timeout can't be smaller than zero.\n"); + return NULL; + } + if(rtsp_timeout < 0) { + JANUS_LOG(LOG_ERR, "rtsp_timeout can't be smaller than zero.\n"); + return NULL; + } + if(rtsp_conn_timeout < 0) { + JANUS_LOG(LOG_ERR, "rtsp_conn_timeout can't be smaller than zero.\n"); + return NULL; + } + JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled"); /* Create an RTP source for the media we'll get */ @@ -7025,6 +7083,11 @@ janus_streaming_mountpoint *janus_streaming_create_rtsp_source( live_rtsp_source->keyframe.latest_keyframe = NULL; live_rtsp_source->keyframe.temp_keyframe = NULL; live_rtsp_source->keyframe.temp_ts = 0; + live_rtsp_source->ka_timeout = session_timeout; + live_rtsp_source->reconnect_delay = reconnect_delay; + live_rtsp_source->session_timeout = session_timeout; + live_rtsp_source->rtsp_timeout = rtsp_timeout; + live_rtsp_source->rtsp_conn_timeout = rtsp_conn_timeout; janus_mutex_init(&live_rtsp_source->keyframe.mutex); live_rtsp_source->reconnect_timer = 0; janus_mutex_init(&live_rtsp_source->rtsp_mutex); @@ -7130,6 +7193,7 @@ janus_streaming_mountpoint *janus_streaming_create_rtsp_source( gboolean doaudio, int acodec, char *audiortpmap, char *audiofmtp, gboolean dovideo, int vcodec, char *videortpmap, char *videofmtp, gboolean bufferkf, const janus_network_address *iface, int threads, + gint64 reconnect_delay, gint64 session_timeout, int rtsp_timeout, int rtsp_conn_timeout, gboolean error_on_failure) { JANUS_LOG(LOG_ERR, "RTSP need libcurl\n"); return NULL; @@ -7503,7 +7567,7 @@ static void *janus_streaming_relay_thread(void *data) { gint64 now = janus_get_monotonic_time(), before = now, ka_timeout = 0; if(source->rtsp) { source->reconnect_timer = now; - ka_timeout = ((gint64)source->ka_timeout*G_USEC_PER_SEC)/2; + ka_timeout = source->ka_timeout; } #endif /* Loop */ @@ -7519,8 +7583,8 @@ static void *janus_streaming_relay_thread(void *data) { continue; } now = janus_get_monotonic_time(); - if(!source->reconnecting && (now - source->reconnect_timer > 5*G_USEC_PER_SEC)) { - /* 5 seconds passed and no media? Assume the RTSP server has gone and schedule a reconnect */ + if(!source->reconnecting && (now - source->reconnect_timer > source->reconnect_delay)) { + /* Assume the RTSP server has gone and schedule a reconnect */ JANUS_LOG(LOG_WARN, "[%s] %"SCNi64"s passed with no media, trying to reconnect the RTSP stream\n", name, (now - source->reconnect_timer)/G_USEC_PER_SEC); audio_fd = -1; @@ -7582,7 +7646,7 @@ static void *janus_streaming_relay_thread(void *data) { data_fd = source->data_fd; audio_rtcp_fd = source->audio_rtcp_fd; video_rtcp_fd = source->video_rtcp_fd; - ka_timeout = ((gint64)source->ka_timeout*G_USEC_PER_SEC)/2; + ka_timeout = source->ka_timeout; } } source->reconnect_timer = janus_get_monotonic_time(); @@ -7592,7 +7656,7 @@ static void *janus_streaming_relay_thread(void *data) { } if(audio_fd < 0 && video_fd[0] < 0 && video_fd[1] < 0 && video_fd[2] < 0 && data_fd < 0) { /* No socket, we may be in the process of reconnecting, or waiting to reconnect */ - g_usleep(5000000); + g_usleep(source->reconnect_delay); continue; } /* We may also need to occasionally send a OPTIONS request as a keep-alive */ @@ -8485,7 +8549,6 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) return; } - static void janus_streaming_relay_rtcp_packet(gpointer data, gpointer user_data) { janus_streaming_rtp_relay_packet *packet = (janus_streaming_rtp_relay_packet *)user_data; if(!packet || !packet->data || packet->length < 1) {