From 46a2637ac5b44d480f08070f484b3e334ad12a1d Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Tue, 22 Feb 2022 11:24:07 +0100 Subject: [PATCH] Add support for playout-delay RTP extension (#2895) --- conf/janus.plugin.streaming.jcfg.sample.in | 6 + src/ice.c | 44 ++++- src/ice.h | 2 + src/janus.c | 12 +- src/plugins/janus_echotest.c | 39 +++- src/plugins/janus_streaming.c | 98 +++++++++- src/plugins/janus_videoroom.c | 209 ++++++++++++++++++++- src/plugins/plugin.c | 2 + src/plugins/plugin.h | 2 + 9 files changed, 397 insertions(+), 17 deletions(-) diff --git a/conf/janus.plugin.streaming.jcfg.sample.in b/conf/janus.plugin.streaming.jcfg.sample.in index 62818a1624..c786e32c96 100644 --- a/conf/janus.plugin.streaming.jcfg.sample.in +++ b/conf/janus.plugin.streaming.jcfg.sample.in @@ -74,6 +74,12 @@ # DO NOT SET THIS PROPERTY IF YOU DON'T KNOW WHAT YOU'RE DOING! # e2ee = true # +# To allow mountpoints to negotiate the playout-delay RTP extension, +# you can set the 'playoutdelay_ext' property to true: this way, any +# subscriber can customize the playout delay of incoming video streams, +# assuming the browser supports the RTP extension in the first place. +# playoutdelay_ext = true +# # The following options are only valid for the 'rtsp' type: # url = RTSP stream URL (only for restreaming RTSP) # rtsp_user = RTSP authorization username (only if type=rtsp) diff --git a/src/ice.c b/src/ice.c index 71add06a28..d9fc749b08 100644 --- a/src/ice.c +++ b/src/ice.c @@ -2715,7 +2715,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp janus_plugin_rtp rtp = { .mindex = medium->mindex, .video = video, .buffer = buf, .length = buflen }; janus_plugin_rtp_extensions_reset(&rtp.extensions); /* Parse RTP extensions before involving the plugin */ - if(pc->audiolevel_ext_id != -1) { + if(!video && pc->audiolevel_ext_id != -1) { gboolean vad = FALSE; int level = -1; if(janus_rtp_header_extension_parse_audio_level(buf, buflen, @@ -2724,7 +2724,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp rtp.extensions.audio_level_vad = vad; } } - if(pc->videoorientation_ext_id != -1) { + if(video && pc->videoorientation_ext_id != -1) { gboolean c = FALSE, f = FALSE, r1 = FALSE, r0 = FALSE; if(janus_rtp_header_extension_parse_video_orientation(buf, buflen, pc->videoorientation_ext_id, &c, &f, &r1, &r0) == 0) { @@ -2739,7 +2739,15 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp rtp.extensions.video_flipped = f; } } - if(pc->dependencydesc_ext_id != -1) { + if(video && pc->playoutdelay_ext_id != -1) { + uint16_t min = 0, max = 0; + if(janus_rtp_header_extension_parse_playout_delay(buf, buflen, + pc->playoutdelay_ext_id, &min, &max) == 0) { + rtp.extensions.min_delay = min; + rtp.extensions.max_delay = max; + } + } + if(video && pc->dependencydesc_ext_id != -1) { uint8_t dd[256]; int len = sizeof(dd); if(janus_rtp_header_extension_parse_dependency_desc(buf, buflen, @@ -3756,8 +3764,9 @@ static void janus_ice_rtp_extension_update(janus_ice_handle *handle, janus_ice_p gboolean video = (packet->type == JANUS_ICE_PACKET_VIDEO); if(handle->pc->mid_ext_id > 0 || (video && handle->pc->abs_send_time_ext_id > 0) || (video && handle->pc->transport_wide_cc_ext_id > 0) || - (!video && packet->extensions.audio_level != -1 && handle->pc->audiolevel_ext_id > 0) || - (video && packet->extensions.video_rotation != -1 && handle->pc->videoorientation_ext_id > 0) || + (!video && packet->extensions.audio_level > -1 && handle->pc->audiolevel_ext_id > 0) || + (video && packet->extensions.video_rotation > -1 && handle->pc->videoorientation_ext_id > 0) || + (video && (packet->extensions.min_delay > -1 || packet->extensions.max_delay > -1) && handle->pc->playoutdelay_ext_id > 0) || (video && packet->extensions.dd_len > 0 && handle->pc->dependencydesc_ext_id > 0)) { /* Do we need 2-byte extemsions, or are 1-byte extensions fine? */ gboolean use_2byte = (video && packet->extensions.dd_len > 16 && handle->pc->dependencydesc_ext_id > 0); @@ -3810,7 +3819,7 @@ static void janus_ice_rtp_extension_update(janus_ice_handle *handle, janus_ice_p } } /* Check if the plugin (or source) included other extensions */ - if(!video && packet->extensions.audio_level != -1 && handle->pc->audiolevel_ext_id > 0) { + if(!video && packet->extensions.audio_level > -1 && handle->pc->audiolevel_ext_id > 0) { /* Add audio-level extension */ if(!use_2byte) { *index = (handle->pc->audiolevel_ext_id << 4); @@ -3827,7 +3836,7 @@ static void janus_ice_rtp_extension_update(janus_ice_handle *handle, janus_ice_p extbufsize -= 3; } } - if(video && packet->extensions.video_rotation != -1 && handle->pc->videoorientation_ext_id > 0) { + if(video && packet->extensions.video_rotation > -1 && handle->pc->videoorientation_ext_id > 0) { /* Add video-orientation extension */ gboolean c = (packet->extensions.video_back_camera == TRUE), f = (packet->extensions.video_flipped == TRUE), r1 = FALSE, r0 = FALSE; @@ -3865,6 +3874,27 @@ static void janus_ice_rtp_extension_update(janus_ice_handle *handle, janus_ice_p extbufsize -= 3; } } + if(video && (packet->extensions.min_delay > -1 || packet->extensions.max_delay > -1) && handle->pc->playoutdelay_ext_id > 0) { + /* Add playout-delay extension */ + uint32_t min_delay = (uint32_t)packet->extensions.min_delay; + uint32_t max_delay = (uint32_t)packet->extensions.max_delay; + uint32_t pd = ((min_delay << 12) & 0x00FFF000) + (max_delay & 0x00000FFF); + uint32_t pd24 = htonl(pd) >> 8; + if(!use_2byte) { + *index = (handle->pc->playoutdelay_ext_id << 4) + 2; + memcpy(index+1, &pd24, 3); + index += 4; + extlen += 4; + extbufsize -= 4; + } else { + *index = handle->pc->playoutdelay_ext_id; + *(index+1) = 3; + memcpy(index+2, &pd24, 3); + index += 5; + extlen += 5; + extbufsize -= 5; + } + } /* Check if we need to add the mid extension */ if(handle->pc->mid_ext_id > 0) { char *mid = medium->mid; diff --git a/src/ice.h b/src/ice.h index 98aca38354..c33b73d040 100644 --- a/src/ice.h +++ b/src/ice.h @@ -444,6 +444,8 @@ struct janus_ice_peerconnection { gint audiolevel_ext_id; /*! \brief Video orientation extension ID */ gint videoorientation_ext_id; + /*! \brief Playout delay extension ID */ + gint playoutdelay_ext_id; /*! \brief Dependency descriptor extension ID */ gint dependencydesc_ext_id; /*! \brief Absolute Send Time ext ID */ diff --git a/src/janus.c b/src/janus.c index 4be4181c34..983a6e70f5 100644 --- a/src/janus.c +++ b/src/janus.c @@ -1552,6 +1552,8 @@ int janus_process_incoming_request(janus_request *request) { handle->pc->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); /* Check if the video orientation ID extension is being negotiated */ handle->pc->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); + /* Check if the playout delay ID extension is being negotiated */ + handle->pc->playoutdelay_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_PLAYOUT_DELAY); /* Check if the abs-send-time ID extension is being negotiated */ handle->pc->abs_send_time_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_ABS_SEND_TIME); /* Check if transport wide CC is supported */ @@ -1616,6 +1618,8 @@ int janus_process_incoming_request(janus_request *request) { handle->pc->audiolevel_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL); /* Check if the video orientation ID extension is being negotiated */ handle->pc->videoorientation_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); + /* Check if the playout delay ID extension is being negotiated */ + handle->pc->playoutdelay_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_PLAYOUT_DELAY); /* Check if the abs-send-time ID extension is being negotiated */ handle->pc->abs_send_time_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_ABS_SEND_TIME); /* Check if transport wide CC is supported */ @@ -3162,6 +3166,8 @@ json_t *janus_admin_peerconnection_summary(janus_ice_peerconnection *pc) { json_object_set_new(se, JANUS_RTP_EXTMAP_AUDIO_LEVEL, json_integer(pc->audiolevel_ext_id)); if(pc->videoorientation_ext_id > 0) json_object_set_new(se, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, json_integer(pc->videoorientation_ext_id)); + if(pc->playoutdelay_ext_id > 0) + json_object_set_new(se, JANUS_RTP_EXTMAP_PLAYOUT_DELAY, json_integer(pc->playoutdelay_ext_id)); if(pc->dependencydesc_ext_id > 0) json_object_set_new(se, JANUS_RTP_EXTMAP_DEPENDENCY_DESC, json_integer(pc->dependencydesc_ext_id)); json_object_set_new(w, "extensions", se); @@ -3709,7 +3715,7 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug /* Make sure we don't send the rid/repaired-rid attributes when offering ourselves */ int mindex = 0; int mid_ext_id = 0, transport_wide_cc_ext_id = 0, abs_send_time_ext_id = 0, - audiolevel_ext_id = 0, videoorientation_ext_id = 0, dependencydesc_ext_id = 0; + audiolevel_ext_id = 0, videoorientation_ext_id = 0, playoutdelay_ext_id = 0, dependencydesc_ext_id = 0; GList *temp = parsed_sdp->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; @@ -3730,6 +3736,8 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug audiolevel_ext_id = atoi(a->value); else if(strstr(a->value, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) videoorientation_ext_id = atoi(a->value); + else if(strstr(a->value, JANUS_RTP_EXTMAP_PLAYOUT_DELAY)) + playoutdelay_ext_id = atoi(a->value); else if(strstr(a->value, JANUS_RTP_EXTMAP_DEPENDENCY_DESC)) dependencydesc_ext_id = atoi(a->value); else if(strstr(a->value, JANUS_RTP_EXTMAP_RID) || @@ -3764,6 +3772,8 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug ice_handle->pc->audiolevel_ext_id = audiolevel_ext_id; if(ice_handle->pc && ice_handle->pc->videoorientation_ext_id != videoorientation_ext_id) ice_handle->pc->videoorientation_ext_id = videoorientation_ext_id; + if(ice_handle->pc && ice_handle->pc->playoutdelay_ext_id != playoutdelay_ext_id) + ice_handle->pc->playoutdelay_ext_id = playoutdelay_ext_id; if(ice_handle->pc && ice_handle->pc->dependencydesc_ext_id != dependencydesc_ext_id) ice_handle->pc->dependencydesc_ext_id = dependencydesc_ext_id; } else { diff --git a/src/plugins/janus_echotest.c b/src/plugins/janus_echotest.c index afba5fd8e0..9df32d0aa4 100644 --- a/src/plugins/janus_echotest.c +++ b/src/plugins/janus_echotest.c @@ -204,6 +204,8 @@ static struct janus_json_parameter request_parameters[] = { {"videocodec", JSON_STRING, 0}, {"videoprofile", JSON_STRING, 0}, {"opusred", JANUS_JSON_BOOL, 0}, + {"min_delay", JSON_INTEGER, 0}, + {"max_delay", JSON_INTEGER, 0}, }; /* Useful stuff */ @@ -246,6 +248,7 @@ typedef struct janus_echotest_session { gboolean e2ee; /* Whether media is encrypted, e.g., using Insertable Streams */ janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */ guint16 slowlink_count; + int16_t min_delay, max_delay; volatile gint hangingup; volatile gint destroyed; janus_refcount ref; @@ -430,6 +433,8 @@ void janus_echotest_create_session(janus_plugin_session *handle, int *error) { janus_rtp_switching_context_reset(&session->context); janus_rtp_simulcasting_context_reset(&session->sim_context); janus_vp8_simulcast_context_reset(&session->vp8_context); + session->min_delay = -1; + session->max_delay = -1; session->destroyed = 0; g_atomic_int_set(&session->hangingup, 0); g_atomic_int_set(&session->destroyed, 0); @@ -581,6 +586,10 @@ void janus_echotest_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp gboolean video = packet->video; char *buf = packet->buffer; uint16_t len = packet->length; + if(session->min_delay > -1 && session->max_delay > -1) { + packet->extensions.min_delay = session->min_delay; + packet->extensions.max_delay = session->max_delay; + } if(video && session->video_active && (session->ssrc[0] != 0 || session->rid[0] != NULL)) { /* Handle simulcast: backup the header information first */ janus_rtp_header *header = (janus_rtp_header *)buf; @@ -849,6 +858,8 @@ static void janus_echotest_hangup_media_internal(janus_plugin_session *handle) { janus_rtp_switching_context_reset(&session->context); janus_rtp_simulcasting_context_reset(&session->sim_context); janus_vp8_simulcast_context_reset(&session->vp8_context); + session->min_delay = -1; + session->max_delay = -1; g_atomic_int_set(&session->hangingup, 0); } @@ -947,6 +958,8 @@ static void *janus_echotest_handler(void *data) { json_t *videocodec = json_object_get(root, "videocodec"); json_t *videoprofile = json_object_get(root, "videoprofile"); json_t *opusred = json_object_get(root, "opusred"); + json_t *min_delay = json_object_get(root, "min_delay"); + json_t *max_delay = json_object_get(root, "max_delay"); /* Enforce request */ if(audio) { session->audio_active = json_is_true(audio); @@ -1010,6 +1023,28 @@ static void *janus_echotest_handler(void *data) { gateway->send_pli(session->handle); } } + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + session->min_delay = -1; + session->max_delay = -1; + } else { + session->min_delay = md; + if(session->min_delay > session->max_delay) + session->max_delay = session->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + session->min_delay = -1; + session->max_delay = -1; + } else { + session->max_delay = md; + if(session->max_delay < session->min_delay) + session->min_delay = session->max_delay; + } + } /* Any SDP to handle? */ if(msg_sdp) { @@ -1019,7 +1054,8 @@ static void *janus_echotest_handler(void *data) { session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL); } - if(!audio && !video && !videocodec && !videoprofile && !opusred && !bitrate && !substream && !temporal && !fallback && !record && !msg_sdp) { + if(!audio && !video && !videocodec && !videoprofile && !opusred && !bitrate && + !substream && !temporal && !fallback && !record && !min_delay && !max_delay && !msg_sdp) { JANUS_LOG(LOG_ERR, "No supported attributes found\n"); error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Message error: no supported attributes found"); @@ -1109,6 +1145,7 @@ static void *janus_echotest_handler(void *data) { JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_PLAYOUT_DELAY, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_DEPENDENCY_DESC, JANUS_SDP_OA_DONE); diff --git a/src/plugins/janus_streaming.c b/src/plugins/janus_streaming.c index 79255df718..2f30bf70ba 100644 --- a/src/plugins/janus_streaming.c +++ b/src/plugins/janus_streaming.c @@ -135,6 +135,12 @@ so neither Janus nor the Streaming plugin have access to anything. DO NOT SET THIS PROPERTY IF YOU DON'T KNOW WHAT YOU'RE DOING! e2ee = true +To allow mountpoints to negotiate the playout-delay RTP extension, +you can set the 'playoutdelay_ext' property to true: this way, any +subscriber can customize the playout delay of incoming video streams, +assuming the browser supports the RTP extension in the first place. +playoutdelay_ext = true + The following options are only valid for the 'rtsp' type: url = RTSP stream URL rtsp_user = RTSP authorization username, if needed @@ -750,7 +756,9 @@ multistream-test: { "temporal" : , "fallback" : , "spatial_layer" : , - "temporal_layer" : + "temporal_layer" : , + "min_delay" : , + "max_delay" : } \endverbatim * @@ -990,7 +998,8 @@ static struct janus_json_parameter rtp_parameters[] = { {"threads", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"srtpsuite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"srtpcrypto", JSON_STRING, 0}, - {"e2ee", JANUS_JSON_BOOL, 0} + {"e2ee", JANUS_JSON_BOOL, 0}, + {"playoutdelay_ext", JANUS_JSON_BOOL, 0} }; static struct janus_json_parameter live_parameters[] = { {"filename", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, @@ -1124,6 +1133,9 @@ static struct janus_json_parameter configure_parameters[] = { /* For VP9 SVC */ {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + /* For the playout-delay RTP extension, if negotiated */ + {"min_delay", JSON_INTEGER, 0}, + {"max_delay", JSON_INTEGER, 0}, /* Deprecated parameters: still there only for * backwards compatibility, but not for long */ {"audio", JSON_STRING, 0}, @@ -1262,6 +1274,8 @@ typedef struct janus_streaming_rtp_source { srtp_policy_t srtp_policy; /* If the media is end-to-end encrypted, we may need to know */ gboolean e2ee; + /* Whether the playout-delay extension should be negotiated or not for new subscribers */ + gboolean playoutdelay_ext; } janus_streaming_rtp_source; typedef enum janus_streaming_media { @@ -1408,7 +1422,7 @@ janus_streaming_rtp_source_stream *janus_streaming_create_rtp_source_stream( gboolean textdata, gboolean buffermsg); janus_streaming_mountpoint *janus_streaming_create_rtp_source( uint64_t id, char *id_str, char *name, char *desc, char *metadata, - GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision, gboolean e2ee); + GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision, gboolean e2ee, gboolean playoutdelay_ext); /* Helper to create a file/ondemand live source */ janus_streaming_mountpoint *janus_streaming_create_file_source( uint64_t id, char *id_str, char *name, char *desc, char *metadata, char *filename, gboolean live, @@ -1445,6 +1459,8 @@ typedef struct janus_streaming_session_stream { int spatial_layer, target_spatial_layer; gint64 last_spatial_layer[3]; int temporal_layer, target_temporal_layer; + /* Playout delays to enforce when relaying this stream, if the extension has been negotiated */ + int16_t min_delay, max_delay; } janus_streaming_session_stream; static void janus_streaming_session_stream_free(janus_streaming_session_stream *s) { if(s && s->stream) @@ -1463,6 +1479,8 @@ typedef struct janus_streaming_session { GHashTable *streams_byid; /* Map of streams this session is subscribed to, indexed by mountpoint mindex */ /* If the media is end-to-end encrypted, we may need to know */ gboolean e2ee; + /* Whether the playout-delay extension should be negotiated */ + gboolean playoutdelay_ext; janus_mutex mutex; volatile gint dataready; volatile gint stopping; @@ -1960,6 +1978,7 @@ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { janus_config_item *ssuite = janus_config_get(config, cat, janus_config_type_item, "srtpsuite"); janus_config_item *scrypto = janus_config_get(config, cat, janus_config_type_item, "srtpcrypto"); janus_config_item *e2ee = janus_config_get(config, cat, janus_config_type_item, "e2ee"); + janus_config_item *pd = janus_config_get(config, cat, janus_config_type_item, "playoutdelay_ext"); gboolean is_private = priv && priv->value && janus_is_true(priv->value); if(ssuite && ssuite->value && atoi(ssuite->value) != 32 && atoi(ssuite->value) != 80) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' mountpoint '%s', invalid SRTP suite...\n", cat->name); @@ -2342,7 +2361,8 @@ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { scrypto && scrypto->value ? (char *)scrypto->value : NULL, (threads && threads->value) ? atoi(threads->value) : 0, (rtpcollision && rtpcollision->value) ? atoi(rtpcollision->value) : 0, - (e2ee && e2ee->value) ? janus_is_true(e2ee->value) : FALSE)) == NULL) { + (e2ee && e2ee->value) ? janus_is_true(e2ee->value) : FALSE, + (pd && pd->value) ? janus_is_true(pd->value) : FALSE)) == NULL) { JANUS_LOG(LOG_ERR, "Error creating 'rtp' mountpoint '%s'...\n", cat->name); cl = cl->next; continue; @@ -2800,6 +2820,12 @@ json_t *janus_streaming_query_session(janus_plugin_session *handle) { json_object_set_new(svc, "target-temporal-layer", json_integer(s->target_temporal_layer)); json_object_set_new(info, "svc", svc); } + if(stream->type == JANUS_STREAMING_MEDIA_VIDEO && session->playoutdelay_ext) { + json_t *pd = json_object(); + json_object_set_new(pd, "min-delay", json_integer(s->min_delay)); + json_object_set_new(pd, "max-delay", json_integer(s->max_delay)); + json_object_set_new(info, "playout-delay", pd); + } json_array_append_new(media, info); temp = temp->next; } @@ -3165,6 +3191,7 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi json_t *ssuite = json_object_get(root, "srtpsuite"); json_t *scrypto = json_object_get(root, "srtpcrypto"); json_t *e2ee = json_object_get(root, "e2ee"); + json_t *pd = json_object_get(root, "playoutdelay_ext"); if(ssuite && json_integer_value(ssuite) != 32 && json_integer_value(ssuite) != 80) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, invalid SRTP suite...\n"); error_code = JANUS_STREAMING_ERROR_CANT_CREATE; @@ -3564,7 +3591,8 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi scrypto ? (char *)json_string_value(scrypto) : NULL, threads ? json_integer_value(threads) : 0, rtpcollision ? json_integer_value(rtpcollision) : 0, - e2ee ? json_is_true(e2ee) : FALSE); + e2ee ? json_is_true(e2ee) : FALSE, + pd ? json_is_true(pd) : FALSE); janus_mutex_lock(&mountpoints_mutex); g_hash_table_remove(mountpoints_temp, string_ids ? (gpointer)mpid_str : (gpointer)&mpid); janus_mutex_unlock(&mountpoints_mutex); @@ -3893,6 +3921,10 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi g_snprintf(value, BUFSIZ, "%d", mp->helper_threads); janus_config_add(config, c, janus_config_item_create("threads", value)); } + if(source->e2ee) + janus_config_add(config, c, janus_config_item_create("e2ee", "yes")); + if(source->playoutdelay_ext) + janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", "yes")); /* Iterate on all media streams */ janus_config_array *media = janus_config_array_create("media"); janus_config_add(config, c, media); @@ -4263,6 +4295,10 @@ static json_t *janus_streaming_process_synchronous_request(janus_streaming_sessi g_snprintf(value, BUFSIZ, "%d", mp->helper_threads); janus_config_add(config, c, janus_config_item_create("threads", value)); } + if(source->e2ee) + janus_config_add(config, c, janus_config_item_create("e2ee", "yes")); + if(source->playoutdelay_ext) + janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", "yes")); /* Iterate on all media streams */ janus_config_array *media = janus_config_array_create("media"); janus_config_add(config, c, media); @@ -5500,6 +5536,8 @@ static void *janus_streaming_handler(void *data) { s->send = TRUE; s->pt = -1; janus_rtp_switching_context_reset(&s->context); + s->min_delay = -1; + s->max_delay = -1; session->streams = g_list_append(session->streams, s); if(session->streams_byid == NULL) session->streams_byid = g_hash_table_new(NULL, NULL); @@ -5566,6 +5604,8 @@ static void *janus_streaming_handler(void *data) { s->send = TRUE; s->pt = stream->codecs.pt; janus_rtp_switching_context_reset(&s->context); + s->min_delay = -1; + s->max_delay = -1; if(stream && stream->simulcast) { JANUS_VALIDATE_JSON_OBJECT(root, simulcast_parameters, error_code, error_cause, TRUE, @@ -5648,6 +5688,8 @@ static void *janus_streaming_handler(void *data) { /* If this mountpoint is broadcasting end-to-end encrypted media, * add the info to the JSEP offer we'll be sending them */ session->e2ee = source->e2ee; + /* Also check if we have to offer the playout-delay extension */ + session->playoutdelay_ext = source->playoutdelay_ext; } janus_refcount_increase(&session->ref); done: @@ -5750,6 +5792,10 @@ static void *janus_streaming_handler(void *data) { janus_strlcat(sdptemp, buffer, sizeof(sdptemp)); g_snprintf(buffer, 512, "a=extmap:%d %s\r\n", 2, JANUS_RTP_EXTMAP_ABS_SEND_TIME); janus_strlcat(sdptemp, buffer, 2048); + if(session->playoutdelay_ext) { + g_snprintf(buffer, 512, "a=extmap:%d %s\r\n", 3, JANUS_RTP_EXTMAP_PLAYOUT_DELAY); + janus_strlcat(sdptemp, buffer, 2048); + } } #ifdef HAVE_SCTP else if(stream->type == JANUS_STREAMING_MEDIA_DATA && data) { @@ -5977,6 +6023,33 @@ static void *janus_streaming_handler(void *data) { s->target_temporal_layer = temporal_layer; } } + if(stream && stream->type == JANUS_STREAMING_MEDIA_VIDEO && session->playoutdelay_ext) { + /* Check if we need to specify a custom playout delay for this stream */ + json_t *min_delay = json_object_get(root, "min_delay"); + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + s->min_delay = -1; + s->max_delay = -1; + } else { + s->min_delay = md; + if(s->min_delay > s->max_delay) + s->max_delay = s->min_delay; + } + } + json_t *max_delay = json_object_get(root, "max_delay"); + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + s->min_delay = -1; + s->max_delay = -1; + } else { + s->max_delay = md; + if(s->max_delay < s->min_delay) + s->min_delay = s->max_delay; + } + } + } temp = temp->next; } } @@ -6651,7 +6724,7 @@ janus_streaming_rtp_source_stream *janus_streaming_create_rtp_source_stream( janus_streaming_mountpoint *janus_streaming_create_rtp_source( uint64_t id, char *id_str, char *name, char *desc, char *metadata, - GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision, gboolean e2ee) { + GList *media, int srtpsuite, char *srtpcrypto, int threads, int rtp_collision, gboolean e2ee, gboolean playoutdelay_ext) { char id_num[30]; if(!string_ids) { g_snprintf(id_num, sizeof(id_num), "%"SCNu64, id); @@ -6776,6 +6849,7 @@ janus_streaming_mountpoint *janus_streaming_create_rtp_source( janus_mutex_init(&live_rtp_source->rec_mutex); live_rtp_source->rtp_collision = rtp_collision; live_rtp_source->e2ee = e2ee; + live_rtp_source->playoutdelay_ext = playoutdelay_ext; live_rtp->source = live_rtp_source; live_rtp->source_destroy = (GDestroyNotify) janus_streaming_rtp_source_free; live_rtp->viewers = NULL; @@ -9133,6 +9207,10 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) packet->data->type = s->pt; janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); + if(s->min_delay > -1 && s->max_delay > -1) { + rtp.extensions.min_delay = s->min_delay; + rtp.extensions.max_delay = s->max_delay; + } if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); if(override_mark_bit && !has_marker_bit) { @@ -9203,6 +9281,10 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) /* Send the packet */ janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); + if(s->min_delay > -1 && s->max_delay > -1) { + rtp.extensions.min_delay = s->min_delay; + rtp.extensions.max_delay = s->max_delay; + } if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); /* Restore the timestamp and sequence number to what the publisher set them to */ @@ -9220,6 +9302,10 @@ static void janus_streaming_relay_rtp_packet(gpointer data, gpointer user_data) packet->data->type = s->pt; janus_plugin_rtp rtp = { .mindex = s->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); + if(s->min_delay > -1 && s->max_delay > -1) { + rtp.extensions.min_delay = s->min_delay; + rtp.extensions.max_delay = s->max_delay; + } if(gateway != NULL) gateway->relay_rtp(session->handle, &rtp); /* Restore the timestamp and sequence number to what the video source set them to */ diff --git a/src/plugins/janus_videoroom.c b/src/plugins/janus_videoroom.c index 5dc80f9410..fdd489f639 100644 --- a/src/plugins/janus_videoroom.c +++ b/src/plugins/janus_videoroom.c @@ -734,6 +734,8 @@ room-: { "audio_level_average" : "", "mid" : , "send" : , + "min_delay" : , + "max_delay" : , "descriptions" : [ // Updated descriptions for the published streams; see "publish" for syntax; optional ] @@ -1289,6 +1291,8 @@ room-: { "temporal_layer" : , "audio_level_average" : "", "audio_active_packets" : "", + "min_delay" : , + "max_delay" : , "restart" : } \endverbatim @@ -1637,6 +1641,9 @@ static struct janus_json_parameter publish_parameters[] = { /* Only needed when configuring, to make a stream active/inactive */ {"mid", JANUS_JSON_STRING, 0}, {"send", JANUS_JSON_BOOL, 0}, + /* For the playout-delay RTP extension, if negotiated */ + {"min_delay", JSON_INTEGER, 0}, + {"max_delay", JSON_INTEGER, 0}, /* Deprecated, use mid+send instead */ {"audio", JANUS_JSON_BOOL, 0}, /* Deprecated! */ {"video", JANUS_JSON_BOOL, 0}, /* Deprecated! */ @@ -1712,6 +1719,9 @@ static struct janus_json_parameter configure_parameters[] = { /* For VP9 SVC */ {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + /* For the playout-delay RTP extension, if negotiated */ + {"min_delay", JSON_INTEGER, 0}, + {"max_delay", JSON_INTEGER, 0}, /* The following is to handle a renegotiation */ {"update", JANUS_JSON_BOOL, 0}, /* Deprecated properties, use mid+send instead */ @@ -1749,7 +1759,10 @@ static struct janus_json_parameter subscriber_stream_parameters[] = { {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"spatial_layer", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, - {"temporal_layer", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE} + {"temporal_layer", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, + /* For the playout-delay RTP extension, if negotiated */ + {"min_delay", JSON_INTEGER, 0}, + {"max_delay", JSON_INTEGER, 0} }; static struct janus_json_parameter subscriber_update_parameters[] = { {"streams", JANUS_JSON_ARRAY, JANUS_JSON_PARAM_REQUIRED} @@ -2043,6 +2056,8 @@ typedef struct janus_videoroom_publisher_stream { guint8 video_orient_extmap_id; /* Video orientation extmap ID */ guint8 playout_delay_extmap_id; /* Playout delay extmap ID */ janus_sdp_mdirection audio_level_mdir, video_orient_mdir, playout_delay_mdir; + /* Playout delays to enforce when relaying this stream, if the extension has been negotiated */ + int16_t min_delay, max_delay; /* Audio level processing, if enabled */ int audio_dBov_level; /* Value in dBov of the audio level (last value from extension) */ int audio_active_packets; /* Participant's number of audio packets to accumulate */ @@ -2112,6 +2127,8 @@ typedef struct janus_videoroom_subscriber_stream { int spatial_layer, target_spatial_layer; gint64 last_spatial_layer[3]; int temporal_layer, target_temporal_layer; + /* Playout delays to enforce when relaying this stream, if the extension has been negotiated */ + int16_t min_delay, max_delay; volatile gint ready, destroyed; janus_refcount ref; } janus_videoroom_subscriber_stream; @@ -2739,6 +2756,8 @@ static janus_videoroom_subscriber_stream *janus_videoroom_subscriber_stream_add( } stream->pt = ps->pt; stream->opusfec = ps->opusfec; + stream->min_delay = -1; + stream->max_delay = -1; char mid[5]; g_snprintf(mid, sizeof(mid), "%d", stream->mindex); stream->mid = g_strdup(mid); @@ -2934,6 +2953,12 @@ static json_t *janus_videoroom_subscriber_streams_summary(janus_videoroom_subscr json_object_set_new(m, "h264-profile", json_string(stream->h264_profile)); if(stream->vcodec == JANUS_VIDEOCODEC_VP9 && stream->vp9_profile != NULL) json_object_set_new(m, "vp9-profile", json_string(stream->vp9_profile)); + if(stream->min_delay > -1 && stream->max_delay > -1) { + json_t *pd = json_object(); + json_object_set_new(pd, "min-delay", json_integer(stream->min_delay)); + json_object_set_new(pd, "max-delay", json_integer(stream->max_delay)); + json_object_set_new(m, "playout-delay", pd); + } } if(ps->simulcast) { json_t *simulcast = json_object(); @@ -3864,6 +3889,12 @@ json_t *janus_videoroom_query_session(janus_plugin_session *handle) { json_object_set_new(m, "h264-profile", json_string(ps->h264_profile)); if(ps->vcodec == JANUS_VIDEOCODEC_VP9 && ps->vp9_profile != NULL) json_object_set_new(m, "vp9-profile", json_string(ps->vp9_profile)); + if(ps->min_delay > -1 && ps->max_delay > -1) { + json_t *pd = json_object(); + json_object_set_new(pd, "min-delay", json_integer(ps->min_delay)); + json_object_set_new(pd, "max-delay", json_integer(ps->max_delay)); + json_object_set_new(m, "playout-delay", pd); + } } if(ps->simulcast) json_object_set_new(m, "simulcast", json_true()); @@ -6730,6 +6761,10 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */ packet.timestamp = ntohl(packet.data->timestamp); packet.seq_number = ntohs(packet.data->seq_number); + if(ps->min_delay > -1 && ps->max_delay > -1) { + packet.extensions.min_delay = ps->min_delay; + packet.extensions.max_delay = ps->max_delay; + } /* Go: some viewers may decide to drop the packet, but that's up to them */ janus_mutex_lock_nodebug(&ps->subscribers_mutex); g_slist_foreach(ps->subscribers, janus_videoroom_relay_rtp_packet, &packet); @@ -8012,6 +8047,8 @@ static void *janus_videoroom_handler(void *data) { json_t *temporal = json_object_get(s, "temporal_layer"); json_t *sc_temporal = json_object_get(s, "temporal"); json_t *sc_fallback = json_object_get(s, "fallback"); + json_t *min_delay = json_object_get(s, "min_delay"); + json_t *max_delay = json_object_get(s, "max_delay"); if(mid) { /* Subscribe to a specific mid */ janus_videoroom_publisher_stream *ps = g_hash_table_lookup(publisher->streams_bymid, mid); @@ -8052,6 +8089,31 @@ static void *janus_videoroom_handler(void *data) { if(temporal) stream->target_temporal_layer = json_integer_value(temporal); } + if(stream && ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + /* Override the playout-delay properties */ + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->min_delay = md; + if(stream->min_delay > stream->max_delay) + stream->max_delay = stream->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->max_delay = md; + if(stream->max_delay < stream->min_delay) + stream->min_delay = stream->max_delay; + } + } + } if(ps->type == JANUS_VIDEOROOM_MEDIA_DATA) { data_added = TRUE; data_stream = stream; @@ -8090,6 +8152,31 @@ static void *janus_videoroom_handler(void *data) { if(temporal) stream->target_temporal_layer = json_integer_value(temporal); } + if(stream && ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + /* Override the playout-delay properties */ + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->min_delay = md; + if(stream->min_delay > stream->max_delay) + stream->max_delay = stream->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->max_delay = md; + if(stream->max_delay < stream->min_delay) + stream->min_delay = stream->max_delay; + } + } + } if(ps->type == JANUS_VIDEOROOM_MEDIA_DATA) { data_added = TRUE; data_stream = stream; @@ -8250,6 +8337,8 @@ static void *janus_videoroom_handler(void *data) { json_t *update = json_object_get(root, "update"); json_t *user_audio_active_packets = json_object_get(root, "audio_active_packets"); json_t *user_audio_level_average = json_object_get(root, "audio_level_average"); + json_t *min_delay = json_object_get(root, "min_delay"); + json_t *max_delay = json_object_get(root, "max_delay"); /* Audio, video and data are deprecated properties */ json_t *audio = json_object_get(root, "audio"); json_t *video = json_object_get(root, "video"); @@ -8307,7 +8396,7 @@ static void *janus_videoroom_handler(void *data) { } } /* Update the audio/video/data flags, if set (and just configuring) */ - if(audio || video || data || (mid && send)) { + if(audio || video || data || (mid && send) || min_delay || max_delay) { janus_mutex_lock(&participant->streams_mutex); GList *temp = participant->streams; while(temp) { @@ -8358,6 +8447,30 @@ static void *janus_videoroom_handler(void *data) { /* Send a PLI */ janus_videoroom_reqpli(ps, "Keyframe request"); } + if(ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + ps->min_delay = -1; + ps->max_delay = -1; + } else { + ps->min_delay = md; + if(ps->min_delay > ps->max_delay) + ps->max_delay = ps->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + ps->min_delay = -1; + ps->max_delay = -1; + } else { + ps->max_delay = md; + if(ps->max_delay < ps->min_delay) + ps->min_delay = ps->max_delay; + } + } + } temp = temp->next; } janus_mutex_unlock(&participant->streams_mutex); @@ -8727,6 +8840,8 @@ static void *janus_videoroom_handler(void *data) { json_t *temporal = json_object_get(s, "temporal_layer"); json_t *sc_temporal = json_object_get(s, "temporal"); json_t *sc_fallback = json_object_get(s, "fallback"); + json_t *min_delay = json_object_get(s, "min_delay"); + json_t *max_delay = json_object_get(s, "max_delay"); if(mid != NULL) { janus_mutex_lock(&publisher->streams_mutex); janus_videoroom_publisher_stream *ps = g_hash_table_lookup(publisher->streams_bymid, mid); @@ -8757,6 +8872,31 @@ static void *janus_videoroom_handler(void *data) { if(temporal) stream->target_temporal_layer = json_integer_value(temporal); } + if(ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + /* Override the playout-delay properties */ + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->min_delay = md; + if(stream->min_delay > stream->max_delay) + stream->max_delay = stream->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->max_delay = md; + if(stream->max_delay < stream->min_delay) + stream->min_delay = stream->max_delay; + } + } + } } } else { janus_mutex_lock(&publisher->streams_mutex); @@ -8786,6 +8926,31 @@ static void *janus_videoroom_handler(void *data) { if(temporal) stream->target_temporal_layer = json_integer_value(temporal); } + if(ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + /* Override the playout-delay properties */ + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->min_delay = md; + if(stream->min_delay > stream->max_delay) + stream->max_delay = stream->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->max_delay = md; + if(stream->max_delay < stream->min_delay) + stream->min_delay = stream->max_delay; + } + } + } } temp = temp->next; } @@ -9069,6 +9234,8 @@ static void *janus_videoroom_handler(void *data) { goto error; } json_t *sc_fallback = json_object_get(root, "fallback"); + json_t *min_delay = json_object_get(root, "min_delay"); + json_t *max_delay = json_object_get(root, "max_delay"); /* Update the audio/video/data flags, if set */ janus_mutex_lock(&subscriber->streams_mutex); GList *temp = subscriber->streams; @@ -9207,6 +9374,30 @@ static void *janus_videoroom_handler(void *data) { stream->target_temporal_layer = temporal_layer; } } + if(stream->type == JANUS_VIDEOROOM_MEDIA_VIDEO) { + if(min_delay) { + int16_t md = json_integer_value(min_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->min_delay = md; + if(stream->min_delay > stream->max_delay) + stream->max_delay = stream->min_delay; + } + } + if(max_delay) { + int16_t md = json_integer_value(max_delay); + if(md < 0) { + stream->min_delay = -1; + stream->max_delay = -1; + } else { + stream->max_delay = md; + if(stream->max_delay < stream->min_delay) + stream->min_delay = stream->max_delay; + } + } + } temp = temp->next; } event = json_object(); @@ -9836,6 +10027,8 @@ static void *janus_videoroom_handler(void *data) { ps->acodec = participant->acodec; ps->vcodec = participant->vcodec; ps->pt = -1; + ps->min_delay = -1; + ps->max_delay = -1; g_atomic_int_set(&ps->destroyed, 0); janus_refcount_init(&ps->ref, janus_videoroom_publisher_stream_free); janus_refcount_increase(&ps->ref); /* This is for the mid-indexed hashtable */ @@ -10392,6 +10585,10 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) if(gateway != NULL) { janus_plugin_rtp rtp = { .mindex = stream->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; + if(stream->min_delay > -1 && stream->max_delay > -1) { + rtp.extensions.min_delay = stream->min_delay; + rtp.extensions.max_delay = stream->max_delay; + } gateway->relay_rtp(session->handle, &rtp); } if(override_mark_bit && !has_marker_bit) { @@ -10458,6 +10655,10 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) if(gateway != NULL) { janus_plugin_rtp rtp = { .mindex = stream->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; + if(stream->min_delay > -1 && stream->max_delay > -1) { + rtp.extensions.min_delay = stream->min_delay; + rtp.extensions.max_delay = stream->max_delay; + } gateway->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ @@ -10474,6 +10675,10 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) if(gateway != NULL) { janus_plugin_rtp rtp = { .mindex = stream->mindex, .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length, .extensions = packet->extensions }; + if(stream->min_delay > -1 && stream->max_delay > -1) { + rtp.extensions.min_delay = stream->min_delay; + rtp.extensions.max_delay = stream->max_delay; + } gateway->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ diff --git a/src/plugins/plugin.c b/src/plugins/plugin.c index 561a897bda..ac3f018cef 100644 --- a/src/plugins/plugin.c +++ b/src/plugins/plugin.c @@ -44,6 +44,8 @@ void janus_plugin_rtp_extensions_reset(janus_plugin_rtp_extensions *extensions) extensions->video_rotation = -1; extensions->video_back_camera = FALSE; extensions->video_flipped = FALSE; + extensions->min_delay = -1; + extensions->max_delay = -1; extensions->dd_len = 0; memset(extensions->dd_content, 0, sizeof(extensions->dd_content)); } diff --git a/src/plugins/plugin.h b/src/plugins/plugin.h index b740b22004..dc9c385bdc 100644 --- a/src/plugins/plugin.h +++ b/src/plugins/plugin.h @@ -569,6 +569,8 @@ struct janus_plugin_rtp_extensions { /*! \brief Whether the video orientation extension says it's flipped horizontally * @note Will be ignored if no rotation value is set */ gboolean video_flipped; + /*! \brief Min and max playout delay, if available; -1 means no extension */ + int16_t min_delay, max_delay; /*! \brief Length of Dependency Descriptor data, if available */ uint8_t dd_len; /*! \brief Dependency Descriptor content */