diff --git a/html/janus.js b/html/janus.js index c14f9370a7..2848fd471e 100644 --- a/html/janus.js +++ b/html/janus.js @@ -2433,9 +2433,9 @@ function Janus(gatewayCallbacks) { if(!parameters) parameters = {}; parameters.encodings = [ - { rid: "high", active: true, priority: "high", maxBitrate: 1000000 }, - { rid: "medium", active: true, priority: "medium", maxBitrate: 300000 }, - { rid: "low", active: true, priority: "low", maxBitrate: 100000 } + { rid: "l", active: true, maxBitrate: 100000 }, + { rid: "m", active: true, maxBitrate: 300000 }, + { rid: "h", active: true, maxBitrate: 1000000 } ]; sender.setParameters(parameters); } diff --git a/ice.c b/ice.c index e5c4ddfd3b..24d4b699f4 100644 --- a/ice.c +++ b/ice.c @@ -2160,59 +2160,76 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp || stream->video_ssrc_peer[2] == packet_ssrc || stream->video_ssrc_peer_rtx[2] == packet_ssrc) ? 1 : 0); if(!video && stream->audio_ssrc_peer != packet_ssrc) { - /* FIXME In case it happens, we should check what it is */ - if(stream->audio_ssrc_peer == 0 || stream->video_ssrc_peer[0] == 0) { - /* Apparently we were not told the peer SSRCs, try the RTP mid extension (or payload types) */ - gboolean found = FALSE; - guint16 pt = header->type; - if(handle->stream->mid_ext_id > 0) { - char sdes_item[16]; - if(janus_rtp_header_extension_parse_mid(buf, len, handle->stream->mid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { - if(handle->audio_mid && !strcmp(handle->audio_mid, sdes_item)) { - /* It's audio */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is audio! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); - video = 0; - stream->audio_ssrc_peer = packet_ssrc; - found = TRUE; - } else if(handle->video_mid && !strcmp(handle->video_mid, sdes_item)) { - /* It's video */ - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is video! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); - video = 1; - stream->video_ssrc_peer[0] = packet_ssrc; - found = TRUE; - } - } - } - if(!found && stream->audio_ssrc_peer == 0 && stream->audio_payload_types) { - GList *pts = stream->audio_payload_types; - while(pts) { - guint16 audio_pt = GPOINTER_TO_UINT(pts->data); - if(pt == audio_pt) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is audio! (payload type %"SCNu16")\n", handle->handle_id, packet_ssrc, pt); - video = 0; - stream->audio_ssrc_peer = packet_ssrc; - found = TRUE; - break; - } - pts = pts->next; - } - } - if(!found && stream->video_ssrc_peer[0] == 0 && stream->video_payload_types) { - GList *pts = stream->video_payload_types; - while(pts) { - guint16 video_pt = GPOINTER_TO_UINT(pts->data); - if(pt == video_pt) { - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is video! (payload type %"SCNu16")\n", handle->handle_id, packet_ssrc, pt); - video = 1; + /* Apparently we were not told the peer SSRCs, try the RTP mid extension (or payload types) */ + gboolean found = FALSE; + if(handle->stream->mid_ext_id > 0) { + char sdes_item[16]; + if(janus_rtp_header_extension_parse_mid(buf, len, handle->stream->mid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + if(handle->audio_mid && !strcmp(handle->audio_mid, sdes_item)) { + /* It's audio */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is audio! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); + video = 0; + stream->audio_ssrc_peer = packet_ssrc; + found = TRUE; + } else if(handle->video_mid && !strcmp(handle->video_mid, sdes_item)) { + /* It's video */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Unadvertized SSRC (%"SCNu32") is video! (mid %s)\n", handle->handle_id, packet_ssrc, sdes_item); + video = 1; + /* Check if simulcasting is involved */ + if(stream->rid[0] == NULL || stream->rid_ext_id < 1) { stream->video_ssrc_peer[0] = packet_ssrc; found = TRUE; - break; + } else { + if(janus_rtp_header_extension_parse_rid(buf, len, stream->rid_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + /* Try the RTP stream ID */ + if(stream->rid[0] != NULL && !strcmp(stream->rid[0], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting: rid=%s\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer[0] = packet_ssrc; + vindex = 0; + found = TRUE; + } else if(stream->rid[1] != NULL && !strcmp(stream->rid[1], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #1: rid=%s\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer[1] = packet_ssrc; + vindex = 1; + found = TRUE; + } else if(stream->rid[2] != NULL && !strcmp(stream->rid[2], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #2: rid=%s\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer[2] = packet_ssrc; + vindex = 2; + found = TRUE; + } else { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- Simulcasting: unknown rid %s..?\n", handle->handle_id, sdes_item); + } + } else if(stream->ridrtx_ext_id > 0 && + janus_rtp_header_extension_parse_rid(buf, len, stream->ridrtx_ext_id, sdes_item, sizeof(sdes_item)) == 0) { + /* Try the repaired RTP stream ID */ + if(stream->rid[0] != NULL && !strcmp(stream->rid[0], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting: rid=%s (rtx)\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer_rtx[0] = packet_ssrc; + vindex = 0; + rtx = 1; + found = TRUE; + } else if(stream->rid[1] != NULL && !strcmp(stream->rid[1], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #1: rid=%s (rtx)\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer_rtx[1] = packet_ssrc; + vindex = 1; + rtx = 1; + found = TRUE; + } else if(stream->rid[2] != NULL && !strcmp(stream->rid[2], sdes_item)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] -- Simulcasting #2: rid=%s (rtx)\n", handle->handle_id, sdes_item); + stream->video_ssrc_peer_rtx[2] = packet_ssrc; + vindex = 2; + rtx = 1; + found = TRUE; + } else { + JANUS_LOG(LOG_WARN, "[%"SCNu64"] -- Simulcasting: unknown rid %s..?\n", handle->handle_id, sdes_item); + } + } } - pts = pts->next; } } } - if(!video && stream->audio_ssrc_peer != packet_ssrc) { + if(!found) { JANUS_LOG(LOG_WARN, "[%"SCNu64"] Not video and not audio? dropping (SSRC %"SCNu32")...\n", handle->handle_id, packet_ssrc); return; } @@ -2289,7 +2306,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp header->type = stream->video_payload_type; packet_ssrc = stream->video_ssrc_peer[vindex]; header->ssrc = htonl(packet_ssrc); - if (plen > 0) { + if(plen > 0) { memcpy(&header->seq_number, payload, 2); /* Finally, remove the original sequence number from the payload: rather than moving * the whole payload back two bytes, we shift the header forward (less bytes to move) */ @@ -2300,6 +2317,10 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp buf += 2; payload +=2; header = (janus_rtp_header *)buf; + if(stream->rid_ext_id > 1 && stream->ridrtx_ext_id > 1) { + /* Replace the 'repaired' extension ID as well with the 'regular' one */ + janus_rtp_header_extension_replace_id(buf, buflen, stream->ridrtx_ext_id, stream->rid_ext_id); + } } } /* Check if we need to handle transport wide cc */ @@ -2471,7 +2492,7 @@ static void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint comp /* If this is video, check if this is a keyframe: if so, we empty our NACK queue */ if(video && stream->video_is_keyframe) { if(stream->video_is_keyframe(payload, plen)) { - if(component->last_seqs_video[vindex] && (int16_t)(new_seqn - rtcp_ctx->max_seq_nr) > 0) { + if(rtcp_ctx && (int16_t)(new_seqn - rtcp_ctx->max_seq_nr) > 0) { JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Keyframe received with a highest sequence number, resetting NACK queue\n", handle->handle_id); janus_seq_list_free(&component->last_seqs_video[vindex]); } diff --git a/ice.h b/ice.h index 3a47f9024d..c645742d28 100644 --- a/ice.h +++ b/ice.h @@ -352,6 +352,8 @@ struct janus_ice_stream { guint32 video_ssrc_peer_rtx[3], video_ssrc_peer_rtx_new[3], video_ssrc_peer_rtx_orig[3]; /*! \brief Array of RTP Stream IDs (for Firefox simulcasting, if enabled) */ char *rid[3]; + /*! \brief Whether we should use the legacy simulcast syntax (a=simulcast:recv rid=..) or the proper one (a=simulcast:recv ..) */ + gboolean legacy_rid; /*! \brief RTP switching context(s) in case of renegotiations (audio+video and/or simulcast) */ janus_rtp_switching_context rtp_ctx[3]; /*! \brief List of payload types we can expect for audio */ @@ -388,6 +390,8 @@ struct janus_ice_stream { guint32 video_last_ts; /*! \brief SDES mid RTP extension ID */ gint mid_ext_id; + /*! \brief RTP Stream extension ID, and the related rtx one */ + gint rid_ext_id, ridrtx_ext_id; /*! \brief Whether we do transport wide cc for video */ gboolean do_transport_wide_cc; /*! \brief Transport wide cc rtp ext ID */ diff --git a/janus.c b/janus.c index d1e1769412..d986ec8ba5 100644 --- a/janus.c +++ b/janus.c @@ -1243,6 +1243,9 @@ int janus_process_incoming_request(janus_request *request) { } else { /* Check if the mid RTP extension is being negotiated */ handle->stream->mid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_MID); + /* Check if the RTP Stream ID extension is being negotiated */ + handle->stream->rid_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_RID); + handle->stream->ridrtx_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_REPAIRED_RID); /* Check if transport wide CC is supported */ int transport_wide_cc_ext_id = janus_rtp_header_extension_get_id(jsep_sdp, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC); handle->stream->do_transport_wide_cc = transport_wide_cc_ext_id > 0 ? TRUE : FALSE; @@ -1328,14 +1331,29 @@ int janus_process_incoming_request(janus_request *request) { json_t *body_jsep = NULL; if(jsep_sdp_stripped) { body_jsep = json_pack("{ssss}", "type", jsep_type, "sdp", jsep_sdp_stripped); - /* Check if VP8 simulcasting is enabled */ + /* Check if simulcasting is enabled */ if(janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_HAS_VIDEO)) { - if(handle->stream && handle->stream->video_ssrc_peer[1]) { + if(handle->stream && (handle->stream->rid[0] || handle->stream->video_ssrc_peer[1])) { json_t *simulcast = json_object(); - json_object_set_new(simulcast, "ssrc-0", json_integer(handle->stream->video_ssrc_peer[0])); - json_object_set_new(simulcast, "ssrc-1", json_integer(handle->stream->video_ssrc_peer[1])); - if(handle->stream->video_ssrc_peer[2]) - json_object_set_new(simulcast, "ssrc-2", json_integer(handle->stream->video_ssrc_peer[2])); + /* If we have rids, pass those, otherwise pass the SSRCs */ + if(handle->stream->rid[0]) { + json_t *rids = json_array(); + json_array_append_new(rids, json_string(handle->stream->rid[0])); + if(handle->stream->rid[1]) + json_array_append_new(rids, json_string(handle->stream->rid[1])); + if(handle->stream->rid[2]) + json_array_append_new(rids, json_string(handle->stream->rid[2])); + json_object_set_new(simulcast, "rids", rids); + json_object_set_new(simulcast, "rid-ext", json_integer(handle->stream->rid_ext_id)); + } else { + json_t *ssrcs = json_array(); + json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[0])); + if(handle->stream->video_ssrc_peer[1]) + json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[1])); + if(handle->stream->video_ssrc_peer[2]) + json_array_append_new(ssrcs, json_integer(handle->stream->video_ssrc_peer[2])); + json_object_set_new(simulcast, "ssrcs", ssrcs); + } json_object_set_new(body_jsep, "simulcast", simulcast); } } @@ -2350,16 +2368,22 @@ json_t *janus_admin_stream_summary(janus_ice_stream *stream) { json_object_set_new(ss, "video-peer-sim-1-rtx", json_integer(stream->video_ssrc_peer_rtx[1])); if(stream->video_ssrc_peer_rtx[2]) json_object_set_new(ss, "video-peer-sim-2-rtx", json_integer(stream->video_ssrc_peer_rtx[2])); + json_object_set_new(s, "ssrc", ss); if(stream->rid[0]) { + json_t *sr = json_object(); json_t *rid = json_array(); json_array_append_new(rid, json_string(stream->rid[0])); if(stream->rid[1]) json_array_append_new(rid, json_string(stream->rid[1])); if(stream->rid[1]) json_array_append_new(rid, json_string(stream->rid[2])); - json_object_set_new(ss, "rid", rid); + json_object_set_new(sr, "rid", rid); + json_object_set_new(sr, "rid-ext-id", json_integer(stream->rid_ext_id)); + json_object_set_new(sr, "ridrtx-ext-id", json_integer(stream->ridrtx_ext_id)); + if(stream->legacy_rid) + json_object_set_new(sr, "rid-syntax", json_string("legacy")); + json_object_set_new(s, "rid-simulcast", sr); } - json_object_set_new(s, "ssrc", ss); json_t *sd = json_object(); json_object_set_new(sd, "audio-send", stream->audio_send ? json_true() : json_false()); json_object_set_new(sd, "audio-recv", stream->audio_recv ? json_true() : json_false()); @@ -2941,7 +2965,7 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug } } } - /* Make sure we don't send the mid attribute when offering ourselves */ + /* Make sure we don't send the mid/rid/repaired-rid attributes when offering ourselves */ GList *temp = parsed_sdp->m_lines; while(temp) { janus_sdp_mline *m = (janus_sdp_mline *)temp->data; @@ -2949,7 +2973,8 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug while(tempA) { janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; if(a->name && a->value && (strstr(a->value, JANUS_RTP_EXTMAP_MID) || - strstr(a->value, JANUS_RTP_EXTMAP_RTP_STREAM_ID))) { + strstr(a->value, JANUS_RTP_EXTMAP_RID) || + strstr(a->value, JANUS_RTP_EXTMAP_REPAIRED_RID))) { m->attributes = g_list_remove(m->attributes, a); tempA = m->attributes; janus_sdp_attribute_destroy(a); @@ -2959,6 +2984,41 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug } temp = temp->next; } + } else { + /* Check if the answer does contain the mid/rid/repaired-rid attributes */ + gboolean do_mid = FALSE, do_rid = FALSE, do_repaired_rid = FALSE; + GList *temp = parsed_sdp->m_lines; + while(temp) { + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + GList *tempA = m->attributes; + while(tempA) { + janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; + if(a->name && a->value) { + if(strstr(a->value, JANUS_RTP_EXTMAP_MID)) + do_mid = TRUE; + else if(strstr(a->value, JANUS_RTP_EXTMAP_RID)) + do_rid = TRUE; + else if(strstr(a->value, JANUS_RTP_EXTMAP_REPAIRED_RID)) + do_repaired_rid = TRUE; + } + tempA = tempA->next; + } + temp = temp->next; + } + if(!do_mid) + ice_handle->stream->mid_ext_id = 0; + if(!do_rid) { + ice_handle->stream->rid_ext_id = 0; + ice_handle->stream->ridrtx_ext_id = 0; + g_free(ice_handle->stream->rid[0]); + ice_handle->stream->rid[0] = NULL; + g_free(ice_handle->stream->rid[1]); + ice_handle->stream->rid[1] = NULL; + g_free(ice_handle->stream->rid[2]); + ice_handle->stream->rid[2] = NULL; + } + if(!do_repaired_rid) + ice_handle->stream->ridrtx_ext_id = 0; } if(!updating && !janus_ice_is_full_trickle_enabled()) { /* Wait for candidates-done callback */ diff --git a/plugins/janus_echotest.c b/plugins/janus_echotest.c index ff1a9dc1bd..1fd37efb1c 100644 --- a/plugins/janus_echotest.c +++ b/plugins/janus_echotest.c @@ -211,9 +211,8 @@ typedef struct janus_echotest_session { uint32_t bitrate, peer_bitrate; janus_rtp_switching_context context; uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ + char *rid[3]; /* Only needed if simulcasting is rid-based */ janus_rtp_simulcasting_context sim_context; - int rtpmapid_extmap_id; /* Only needed for debugging in case Firefox's RID-based simulcasting is involved */ - char *rid[3]; /* Only needed for debugging in case Firefox's RID-based simulcasting is involved */ janus_vp8_simulcast_context vp8_context; janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */ janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */ @@ -457,7 +456,7 @@ json_t *janus_echotest_query_session(janus_plugin_session *handle) { json_object_set_new(info, "video_codec", json_string(janus_videocodec_name(session->vcodec))); json_object_set_new(info, "bitrate", json_integer(session->bitrate)); json_object_set_new(info, "peer-bitrate", json_integer(session->peer_bitrate)); - if(session->ssrc[0] != 0) { + if(session->ssrc[0] != 0 || session->rid[0] != NULL) { json_object_set_new(info, "simulcast", json_true()); json_object_set_new(info, "substream", json_integer(session->sim_context.substream)); json_object_set_new(info, "substream-target", json_integer(session->sim_context.substream_target)); @@ -536,19 +535,7 @@ void janus_echotest_incoming_rtp(janus_plugin_session *handle, int video, char * } if(g_atomic_int_get(&session->destroyed)) return; - if(video && session->video_active && session->rtpmapid_extmap_id != -1) { - /* FIXME Just a way to debug Firefox simulcasting */ - janus_rtp_header *header = (janus_rtp_header *)buf; - uint16_t seq_number = ntohs(header->seq_number); - uint32_t timestamp = ntohl(header->timestamp); - uint32_t ssrc = ntohl(header->ssrc); - char sdes_item[16]; - if(janus_rtp_header_extension_parse_rtp_stream_id(buf, len, session->rtpmapid_extmap_id, sdes_item, sizeof(sdes_item)) == 0) { - JANUS_LOG(LOG_DBG, "%"SCNu32"/%"SCNu16"/%"SCNu32"/%d: RTP stream ID extension: %s\n", - ssrc, seq_number, timestamp, header->padding, sdes_item); - } - } - if(video && session->video_active && session->ssrc[0] != 0) { + 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; uint32_t seq_number = ntohs(header->seq_number); @@ -556,7 +543,7 @@ void janus_echotest_incoming_rtp(janus_plugin_session *handle, int video, char * uint32_t ssrc = ntohl(header->ssrc); /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&session->sim_context, - buf, len, session->ssrc, session->vcodec, &session->context); + buf, len, session->ssrc, session->rid, session->vcodec, &session->context); /* Do we need to drop this? */ if(!relay) return; @@ -630,18 +617,9 @@ void janus_echotest_incoming_rtcp(janus_plugin_session *handle, int video, char if(bitrate > 0) { /* If a REMB arrived, make sure we cap it to our configuration, and send it as a video RTCP */ session->peer_bitrate = bitrate; - if(session->bitrate > 0) { - char rtcpbuf[32]; - int numssrc = 1; - if(session->ssrc[1]) - numssrc++; - if(session->ssrc[2]) - numssrc++; - int remblen = janus_rtcp_remb_ssrcs((char *)(&rtcpbuf), sizeof(rtcpbuf), session->bitrate, numssrc); - gateway->relay_rtcp(handle, 1, rtcpbuf, remblen); - } else { - gateway->relay_rtcp(handle, 1, buf, len); - } + if(session->bitrate > 0) + janus_rtcp_cap_remb(buf, len, session->bitrate); + gateway->relay_rtcp(handle, 1, buf, len); return; } gateway->relay_rtcp(handle, video, buf, len); @@ -784,6 +762,12 @@ static void janus_echotest_hangup_media_internal(janus_plugin_session *handle) { session->vcodec = JANUS_VIDEOCODEC_NONE; session->bitrate = 0; session->peer_bitrate = 0; + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[0]); + session->rid[0] = NULL; + } janus_rtp_switching_context_reset(&session->context); janus_rtp_simulcasting_context_reset(&session->sim_context); janus_vp8_simulcast_context_reset(&session->vp8_context); @@ -840,9 +824,9 @@ static void *janus_echotest_handler(void *data) { json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); if(msg_simulcast) { JANUS_LOG(LOG_VERB, "EchoTest client is going to do simulcasting\n"); - session->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); - session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); - session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); + int rid_ext_id = -1; + janus_rtp_simulcasting_prepare(msg_simulcast, &rid_ext_id, session->ssrc, session->rid); + session->sim_context.rid_ext_id = rid_ext_id; session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ } @@ -930,17 +914,10 @@ static void *janus_echotest_handler(void *data) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu32"\n", session->bitrate); if(session->bitrate > 0) { - /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ - char rtcpbuf[32]; - int numssrc = 1; - if(session->ssrc[1]) - numssrc++; - if(session->ssrc[2]) - numssrc++; - int remblen = janus_rtcp_remb_ssrcs((char *)(&rtcpbuf), sizeof(rtcpbuf), session->bitrate, numssrc); + char buf[24]; + janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); - gateway->relay_rtcp(session->handle, 1, rtcpbuf, remblen); - /* FIXME How should we handle a subsequent "no limit" bitrate? */ + gateway->relay_rtcp(session->handle, 1, buf, 24); } } if(substream) { @@ -1021,8 +998,7 @@ static void *janus_echotest_handler(void *data) { g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str); goto error; } - /* Check if we need to negotiate the rtp-stream-id extension */ - session->rtpmapid_extmap_id = -1; + /* Check if we need to negotiate Opus FEC */ gboolean opus_fec = FALSE; GList *temp = offer->m_lines; while(temp) { @@ -1034,10 +1010,7 @@ static void *janus_echotest_handler(void *data) { while(ma) { janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; if(a->value) { - if(strstr(a->value, JANUS_RTP_EXTMAP_RTP_STREAM_ID)) { - session->rtpmapid_extmap_id = atoi(a->value); - break; - } else if(m->type == JANUS_SDP_AUDIO && !strcasecmp(a->name, "fmtp") && + if(m->type == JANUS_SDP_AUDIO && !strcasecmp(a->name, "fmtp") && strstr(a->value, "useinbandfec=1")) { opus_fec = TRUE; } @@ -1052,7 +1025,8 @@ static void *janus_echotest_handler(void *data) { JANUS_SDP_OA_AUDIO_FMTP, opus_fec ? "useinbandfec=1" : NULL, JANUS_SDP_OA_VIDEO_CODEC, json_string_value(videocodec), JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RTP_STREAM_ID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, JANUS_SDP_OA_DONE); /* If we ended up sendonly, switch to inactive (as we don't really send anything ourselves) */ @@ -1073,9 +1047,12 @@ static void *janus_echotest_handler(void *data) { session->has_video = session->vcodec != JANUS_VIDEOCODEC_NONE; if(session->vcodec != JANUS_VIDEOCODEC_VP8 && session->vcodec != JANUS_VIDEOCODEC_H264) { /* VP8 r H.264 were not negotiated, if simulcasting was enabled then disable it here */ - session->ssrc[0] = 0; - session->ssrc[1] = 0; - session->ssrc[2] = 0; + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[0]); + session->rid[0] = NULL; + } } /* Done */ char *sdp = janus_sdp_write(answer); @@ -1183,11 +1160,8 @@ static void *janus_echotest_handler(void *data) { json_object_set_new(info, "audio_active", session->audio_active ? json_true() : json_false()); json_object_set_new(info, "video_active", session->video_active ? json_true() : json_false()); json_object_set_new(info, "bitrate", json_integer(session->bitrate)); - if(session->ssrc[0] && session->ssrc[1]) { + if(session->ssrc[0] || session->rid[0]) { json_t *simulcast = json_object(); - json_object_set_new(simulcast, "ssrc-0", json_integer(session->ssrc[0])); - json_object_set_new(simulcast, "ssrc-1", json_integer(session->ssrc[1])); - json_object_set_new(simulcast, "ssrc-2", json_integer(session->ssrc[2])); json_object_set_new(simulcast, "substream", json_integer(session->sim_context.substream)); json_object_set_new(simulcast, "temporal-layer", json_integer(session->sim_context.templayer)); json_object_set_new(info, "simulcast", simulcast); diff --git a/plugins/janus_nosip.c b/plugins/janus_nosip.c index 3cdb04844d..898600d223 100644 --- a/plugins/janus_nosip.c +++ b/plugins/janus_nosip.c @@ -303,6 +303,7 @@ typedef struct janus_nosip_media { int local_video_rtp_port, remote_video_rtp_port; int local_video_rtcp_port, remote_video_rtcp_port; guint32 video_ssrc, video_ssrc_peer; + guint32 simulcast_ssrc; int video_pt; const char *video_pt_name; srtp_t video_srtp_in, video_srtp_out; @@ -830,6 +831,7 @@ void janus_nosip_create_session(janus_plugin_session *handle, int *error) { session->media.remote_video_rtcp_port = 0; session->media.video_ssrc = 0; session->media.video_ssrc_peer = 0; + session->media.simulcast_ssrc = 0; session->media.video_pt = -1; session->media.video_pt_name = NULL; session->media.video_send = TRUE; @@ -970,6 +972,15 @@ void janus_nosip_incoming_rtp(janus_plugin_session *handle, int video, char *buf /* Dropping packet, peer doesn't want to receive it */ return; } + if(video && session->media.simulcast_ssrc) { + /* The user is simulcasting: drop everything except the base layer */ + janus_rtp_header *header = (janus_rtp_header *)buf; + uint32_t ssrc = ntohl(header->ssrc); + if(ssrc != session->media.simulcast_ssrc) { + JANUS_LOG(LOG_DBG, "Dropping packet (not base simulcast substream)\n"); + return; + } + } if((video && session->media.video_ssrc == 0) || (!video && session->media.audio_ssrc == 0)) { rtp_header *header = (rtp_header *)buf; if(video) { @@ -1124,6 +1135,7 @@ static void janus_nosip_hangup_media_internal(janus_plugin_session *handle) { return; if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1)) return; + session->media.simulcast_ssrc = 0; /* Notify the thread that it's time to go */ if(session->media.pipefd[1] > 0) { int code = 1; @@ -1354,6 +1366,14 @@ static void *janus_nosip_handler(void *data) { json_object_set_new(info, "sdp", json_string(sdp)); gateway->notify_event(&janus_nosip_plugin, session->handle, info); } + /* If the user negotiated simulcasting, just stick with the base substream */ + json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); + if(msg_simulcast) { + JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); + json_t *s = json_object_get(msg_simulcast, "ssrcs"); + if(s && json_array_size(s) > 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + } /* Send the barebone SDP back */ result = json_object(); json_object_set_new(result, "event", json_string("generated")); @@ -2032,6 +2052,7 @@ static void janus_nosip_media_cleanup(janus_nosip_session *session) { session->media.local_video_rtp_port = 0; session->media.local_video_rtcp_port = 0; session->media.video_ssrc = 0; + session->media.simulcast_ssrc = 0; if(session->media.pipefd[0] > 0) { close(session->media.pipefd[0]); session->media.pipefd[0] = -1; diff --git a/plugins/janus_recordplay.c b/plugins/janus_recordplay.c index d8ea2157f5..ed6dd2d40b 100644 --- a/plugins/janus_recordplay.c +++ b/plugins/janus_recordplay.c @@ -425,6 +425,8 @@ typedef struct janus_recordplay_session { gint video_fir_seq; janus_rtp_switching_context context; uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ + char *rid[3]; /* Only needed if simulcasting is rid-based */ + uint32_t rec_vssrc; /* SSRC we'll put in the recording for video, in case simulcasting is involved) */ janus_rtp_simulcasting_context sim_context; janus_vp8_simulcast_context vp8_context; volatile gint hangingup; @@ -1141,7 +1143,7 @@ void janus_recordplay_incoming_rtp(janus_plugin_session *handle, int video, char return; if(!session->recorder || !session->recording) return; - if(video && session->ssrc[0] != 0) { + if(video && (session->ssrc[0] != 0 || session->rid[0] != NULL)) { /* Handle simulcast: backup the header information first */ janus_rtp_header *header = (janus_rtp_header *)buf; uint32_t seq_number = ntohs(header->seq_number); @@ -1149,7 +1151,7 @@ void janus_recordplay_incoming_rtp(janus_plugin_session *handle, int video, char uint32_t ssrc = ntohl(header->ssrc); /* Process this packet: don't save if it's not the SSRC/layer we wanted to handle */ gboolean save = janus_rtp_simulcasting_context_process_rtp(&session->sim_context, - buf, len, session->ssrc, session->recording->vcodec, &session->context); + buf, len, session->ssrc, session->rid, session->recording->vcodec, &session->context); /* Do we need to drop this? */ if(!save) return; @@ -1169,7 +1171,9 @@ void janus_recordplay_incoming_rtp(janus_plugin_session *handle, int video, char janus_vp8_simulcast_descriptor_update(payload, plen, &session->vp8_context, session->sim_context.changed_substream); } /* Save the frame if we're recording (and make sure the SSRC never changes even if the substream does) */ - header->ssrc = htonl(session->ssrc[0]); + if(session->rec_vssrc == 0) + session->rec_vssrc = g_random_int(); + header->ssrc = htonl(session->rec_vssrc); janus_recorder_save_frame(session->vrc, buf, len); /* Restore header or core statistics will be messed up */ header->ssrc = htonl(ssrc); @@ -1323,6 +1327,12 @@ static void janus_recordplay_hangup_media_internal(janus_plugin_session *handle) janus_refcount_decrease(&session->recording->ref); session->recording = NULL; } + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[i]); + session->rid[i] = NULL; + } g_atomic_int_set(&session->hangingup, 0); } @@ -1548,7 +1558,8 @@ static void *janus_recordplay_handler(void *data) { JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY, JANUS_SDP_OA_DATA, FALSE, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RTP_STREAM_ID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, JANUS_SDP_OA_DONE); g_free(answer->s_name); @@ -1567,16 +1578,19 @@ static void *janus_recordplay_handler(void *data) { json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); if(msg_simulcast) { JANUS_LOG(LOG_VERB, "Recording client negotiated simulcasting\n"); - session->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); - session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); - session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); + int rid_ext_id = -1; + janus_rtp_simulcasting_prepare(msg_simulcast, &rid_ext_id, session->ssrc, session->rid); + session->sim_context.rid_ext_id = rid_ext_id; session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ if(rec->vcodec != JANUS_VIDEOCODEC_VP8 && rec->vcodec != JANUS_VIDEOCODEC_H264) { /* VP8 r H.264 were not negotiated, if simulcasting was enabled then disable it here */ - session->ssrc[0] = 0; - session->ssrc[1] = 0; - session->ssrc[2] = 0; + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[0]); + session->rid[0] = NULL; + } } } /* Done! */ diff --git a/plugins/janus_sip.c b/plugins/janus_sip.c index 6855c85ee8..f7d7b824ed 100644 --- a/plugins/janus_sip.c +++ b/plugins/janus_sip.c @@ -2529,7 +2529,9 @@ static void *janus_sip_handler(void *data) { json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); if(msg_simulcast) { JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); - session->media.simulcast_ssrc = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); + json_t *s = json_object_get(msg_simulcast, "ssrcs"); + if(s && json_array_size(s) > 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); } /* Check if there are new credentials to authenticate the INVITE */ if(authuser) { @@ -2685,7 +2687,9 @@ static void *janus_sip_handler(void *data) { json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); if(msg_simulcast) { JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); - session->media.simulcast_ssrc = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); + json_t *s = json_object_get(msg_simulcast, "ssrcs"); + if(s && json_array_size(s) > 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { diff --git a/plugins/janus_sipre.c b/plugins/janus_sipre.c index a9e4ea67e7..57ae46bf58 100644 --- a/plugins/janus_sipre.c +++ b/plugins/janus_sipre.c @@ -480,6 +480,7 @@ typedef struct janus_sipre_media { int local_video_rtp_port, remote_video_rtp_port; int local_video_rtcp_port, remote_video_rtcp_port; guint32 video_ssrc, video_ssrc_peer; + guint32 simulcast_ssrc; int video_pt; const char *video_pt_name; srtp_t video_srtp_in, video_srtp_out; @@ -1285,6 +1286,7 @@ void janus_sipre_create_session(janus_plugin_session *handle, int *error) { session->media.remote_video_rtcp_port = 0; session->media.video_ssrc = 0; session->media.video_ssrc_peer = 0; + session->media.simulcast_ssrc = 0; session->media.video_pt = -1; session->media.video_pt_name = NULL; session->media.video_send = TRUE; @@ -1438,6 +1440,15 @@ void janus_sipre_incoming_rtp(janus_plugin_session *handle, int video, char *buf /* Dropping packet, peer doesn't want to receive it */ return; } + if(video && session->media.simulcast_ssrc) { + /* The user is simulcasting: drop everything except the base layer */ + janus_rtp_header *header = (janus_rtp_header *)buf; + uint32_t ssrc = ntohl(header->ssrc); + if(ssrc != session->media.simulcast_ssrc) { + JANUS_LOG(LOG_DBG, "Dropping packet (not base simulcast substream)\n"); + return; + } + } if((video && session->media.video_ssrc == 0) || (!video && session->media.audio_ssrc == 0)) { rtp_header *header = (rtp_header *)buf; if(video) { @@ -1600,6 +1611,7 @@ static void janus_sipre_hangup_media_internal(janus_plugin_session *handle) { return; if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1)) return; + session->media.simulcast_ssrc = 0; /* Do cleanup if media thread has not been created */ if(!session->media.ready && !session->relayer_thread) { janus_sipre_media_cleanup(session); @@ -2095,6 +2107,14 @@ static void *janus_sipre_handler(void *data) { json_object_set_new(info, "sdp", json_string(sdp)); gateway->notify_event(&janus_sipre_plugin, session->handle, info); } + /* If the user negotiated simulcasting, just stick with the base substream */ + json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); + if(msg_simulcast) { + JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); + json_t *s = json_object_get(msg_simulcast, "ssrcs"); + if(s && json_array_size(s) > 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + } /* Check if there are new credentials to authenticate the INVITE */ if(authuser) { JANUS_LOG(LOG_VERB, "Updating credentials (authuser) for authenticating the INVITE\n"); @@ -2228,6 +2248,14 @@ static void *janus_sipre_handler(void *data) { janus_sdp_destroy(session->sdp); session->sdp = parsed_sdp; JANUS_LOG(LOG_VERB, "Prepared SDP for 200 OK:\n%s", sdp); + /* If the user negotiated simulcasting, just stick with the base substream */ + json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); + if(msg_simulcast) { + JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n"); + json_t *s = json_object_get(msg_simulcast, "ssrcs"); + if(s && json_array_size(s) > 0) + session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0)); + } /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); @@ -3178,6 +3206,7 @@ static void janus_sipre_media_cleanup(janus_sipre_session *session) { session->media.local_video_rtp_port = 0; session->media.local_video_rtcp_port = 0; session->media.video_ssrc = 0; + session->media.simulcast_ssrc = 0; if(session->media.pipefd[0] > 0) { close(session->media.pipefd[0]); session->media.pipefd[0] = -1; diff --git a/plugins/janus_videocall.c b/plugins/janus_videocall.c index faca6ea246..d1ec5967b4 100644 --- a/plugins/janus_videocall.c +++ b/plugins/janus_videocall.c @@ -375,8 +375,8 @@ typedef struct janus_videocall_session { struct janus_videocall_session *peer; janus_rtp_switching_context context; uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ + char *rid[3]; /* Only needed if simulcasting is rid-based */ janus_rtp_simulcasting_context sim_context; - int rtpmapid_extmap_id; /* Only needed for debugging in case Firefox's RID-based simulcasting is involved */ janus_vp8_simulcast_context vp8_context; janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */ janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */ @@ -629,10 +629,10 @@ json_t *janus_videocall_query_session(janus_plugin_session *handle) { json_object_set_new(info, "bitrate", json_integer(session->bitrate)); json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count)); } - if(session->ssrc[0] != 0) { + if(session->ssrc[0] != 0 || session->rid[0] != NULL) { json_object_set_new(info, "simulcast", json_true()); } - if(peer && peer->ssrc[0] != 0) { + if(peer && (peer->ssrc[0] != 0 || peer->rid[0] != NULL)) { json_object_set_new(info, "simulcast-peer", json_true()); json_object_set_new(info, "substream", json_integer(session->sim_context.substream)); json_object_set_new(info, "substream-target", json_integer(session->sim_context.substream_target)); @@ -709,19 +709,7 @@ void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char } if(g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&peer->destroyed)) return; - if(video && session->video_active && session->rtpmapid_extmap_id != -1) { - /* FIXME Just a way to debug Firefox simulcasting */ - janus_rtp_header *header = (janus_rtp_header *)buf; - uint32_t seq_number = ntohs(header->seq_number); - uint32_t timestamp = ntohl(header->timestamp); - uint32_t ssrc = ntohl(header->ssrc); - char sdes_item[16]; - if(janus_rtp_header_extension_parse_rtp_stream_id(buf, len, session->rtpmapid_extmap_id, sdes_item, sizeof(sdes_item)) == 0) { - JANUS_LOG(LOG_DBG, "%"SCNu32"/%"SCNu16"/%"SCNu32"/%d: RTP stream ID extension: %s\n", - ssrc, seq_number, timestamp, header->padding, sdes_item); - } - } - if(video && session->video_active && session->ssrc[0] != 0) { + 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; uint32_t seq_number = ntohs(header->seq_number); @@ -730,7 +718,7 @@ void janus_videocall_incoming_rtp(janus_plugin_session *handle, int video, char /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle * The caveat is that the targets in OUR simulcast context are the PEER's targets */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&peer->sim_context, - buf, len, session->ssrc, session->vcodec, &peer->context); + buf, len, session->ssrc, session->rid, session->vcodec, &peer->context); /* Do we need to drop this? */ if(!relay) return; @@ -962,6 +950,12 @@ void janus_videocall_hangup_media(janus_plugin_session *handle) { session->acodec = JANUS_AUDIOCODEC_NONE; session->vcodec = JANUS_VIDEOCODEC_NONE; session->bitrate = 0; + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[i]); + session->rid[i] = NULL; + } janus_rtp_switching_context_reset(&session->context); janus_rtp_simulcasting_context_reset(&session->sim_context); janus_vp8_simulcast_context_reset(&session->vp8_context); @@ -1204,11 +1198,11 @@ static void *janus_videocall_handler(void *data) { JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp); /* Check if this user will simulcast */ json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); - if(msg_simulcast && janus_get_codec_pt(msg_sdp, "vp8") > 0) { + if(msg_simulcast) { JANUS_LOG(LOG_VERB, "VideoCall caller (%s) is going to do simulcasting\n", session->username); - session->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); - session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); - session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); + int rid_ext_id = -1; + janus_rtp_simulcasting_prepare(msg_simulcast, &rid_ext_id, session->ssrc, session->rid); + session->sim_context.rid_ext_id = rid_ext_id; } /* Send SDP to our peer */ json_t *call = json_object(); @@ -1273,13 +1267,16 @@ static void *janus_videocall_handler(void *data) { session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); } else { - session->ssrc[0] = 0; - session->ssrc[1] = 0; - session->ssrc[2] = 0; - if(peer) { - peer->ssrc[0] = 0; - peer->ssrc[1] = 0; - peer->ssrc[2] = 0; + int i=0; + for(i=0; i<3; i++) { + session->ssrc[i] = 0; + g_free(session->rid[0]); + session->rid[0] = NULL; + if(peer) { + peer->ssrc[i] = 0; + g_free(peer->rid[0]); + peer->rid[0] = NULL; + } } } /* Check which codecs we ended up using */ @@ -1325,11 +1322,11 @@ static void *janus_videocall_handler(void *data) { gateway->notify_event(&janus_videocall_plugin, session->handle, info); } /* Is simulcasting involved on either side? */ - if(session->ssrc[0] && session->ssrc[1]) { + if(session->ssrc[0] || session->rid[0]) { peer->sim_context.substream_target = 2; /* Let's aim for the highest quality */ peer->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ } - if(peer->ssrc[0] && peer->ssrc[1]) { + if(peer->ssrc[0] || peer->rid[0]) { session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ } diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c index 68b5ffb502..7a5c60c1a7 100644 --- a/plugins/janus_videoroom.c +++ b/plugins/janus_videoroom.c @@ -1419,8 +1419,8 @@ typedef struct janus_videoroom_publisher { guint32 video_ssrc; /* Video SSRC of this publisher */ gboolean do_opusfec; /* Whether this publisher is sending inband Opus FEC */ uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ - int rtpmapid_extmap_id; /* Only needed for debugging in case Firefox's RID-based simulcasting is involved */ - char *rid[3]; /* Only needed for debugging in case Firefox's RID-based simulcasting is involved */ + char *rid[3]; /* Only needed if simulcasting is rid-based */ + int rid_extmap_id; /* rid extmap ID */ guint8 audio_level_extmap_id; /* Audio level extmap ID */ guint8 video_orient_extmap_id; /* Video orientation extmap ID */ guint8 playout_delay_extmap_id; /* Playout delay extmap ID */ @@ -1805,6 +1805,7 @@ static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_publishe forward->simulcast = TRUE; janus_rtp_switching_context_reset(&forward->context); janus_rtp_simulcasting_context_reset(&forward->sim_context); + forward->sim_context.rid_ext_id = p->rid_extmap_id; forward->sim_context.substream_target = 2; forward->sim_context.templayer_target = 2; } @@ -2448,7 +2449,7 @@ json_t *janus_videoroom_query_session(janus_plugin_session *handle) { json_object_set_new(media, "data", participant->data ? json_true() : json_false()); json_object_set_new(info, "media", media); json_object_set_new(info, "bitrate", json_integer(participant->bitrate)); - if(participant->ssrc[0] != 0) + if(participant->ssrc[0] != 0 || participant->rid[0] != NULL) json_object_set_new(info, "simulcast", json_true()); if(participant->arc || participant->vrc || participant->drc) { json_t *recording = json_object(); @@ -2487,7 +2488,7 @@ json_t *janus_videoroom_query_session(janus_plugin_session *handle) { json_object_set_new(media, "data", participant->data ? json_true() : json_false()); json_object_set_new(media, "data-offered", participant->data_offered ? json_true() : json_false()); json_object_set_new(info, "media", media); - if(feed && feed->ssrc[0] != 0) { + if(feed && (feed->ssrc[0] != 0 || feed->rid[0] != NULL)) { json_t *simulcast = json_object(); json_object_set_new(simulcast, "substream", json_integer(participant->sim_context.substream)); json_object_set_new(simulcast, "substream-target", json_integer(participant->sim_context.substream_target)); @@ -3949,7 +3950,7 @@ void janus_videoroom_setup_media(janus_plugin_session *handle) { json_object_set_new(pl, "audio_codec", json_string(janus_audiocodec_name(participant->acodec))); if(participant->video) json_object_set_new(pl, "video_codec", json_string(janus_videocodec_name(participant->vcodec))); - if(participant->ssrc[0]) + if(participant->ssrc[0] || participant->rid[0]) json_object_set_new(pl, "simulcast", json_true()); if(participant->audio_level_extmap_id > 0) json_object_set_new(pl, "talking", participant->talking ? json_true() : json_false()); @@ -4056,7 +4057,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char janus_rtp_header *rtp = (janus_rtp_header *)buf; int sc = video ? 0 : -1; /* Check if we're simulcasting, and if so, keep track of the "layer" */ - if(video && participant->ssrc[0] != 0) { + if(video && (participant->ssrc[0] != 0 || participant->rid[0] != NULL)) { uint32_t ssrc = ntohl(rtp->ssrc); if(ssrc == participant->ssrc[0]) sc = 0; @@ -4064,6 +4065,22 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char sc = 1; else if(ssrc == participant->ssrc[2]) sc = 2; + else if(participant->rid_extmap_id > 0) { + /* We may not know the SSRC yet, try the rid RTP extension */ + char sdes_item[16]; + if(janus_rtp_header_extension_parse_rid(buf, len, participant->rid_extmap_id, sdes_item, sizeof(sdes_item)) == 0) { + if(participant->rid[0] != NULL && !strcmp(participant->rid[0], sdes_item)) { + participant->ssrc[0] = ssrc; + sc = 0; + } else if(participant->rid[0] != NULL && !strcmp(participant->rid[0], sdes_item)) { + participant->ssrc[1] = ssrc; + sc = 1; + } else if(participant->rid[0] != NULL && !strcmp(participant->rid[0], sdes_item)) { + participant->ssrc[2] = ssrc; + sc = 2; + } + } + } } /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */ janus_mutex_lock(&participant->rtp_forwarders_mutex); @@ -4094,11 +4111,11 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char } else if(video && rtp_forward->simulcast) { /* This is video and we're simulcasting, check if we need to forward this frame */ if(!janus_rtp_simulcasting_context_process_rtp(&rtp_forward->sim_context, - buf, len, participant->ssrc, participant->vcodec, &rtp_forward->context)) + buf, len, participant->ssrc, participant->rid, participant->vcodec, &rtp_forward->context)) continue; janus_rtp_header_update(rtp, &rtp_forward->context, TRUE, 4500); - /* By default we use the main SSRC (it may be overwritten later) */ - rtp->ssrc = htonl(participant->ssrc[0]); + /* By default we use a fixed SSRC (it may be overwritten later) */ + rtp->ssrc = htonl(participant->user_id & 0xffffffff); } /* Check if payload type and/or SSRC need to be overwritten for this forwarder */ if(rtp_forward->payload_type > 0) @@ -4143,19 +4160,19 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char /* Set the payload type of the publisher */ rtp->type = video ? participant->video_pt : participant->audio_pt; /* Save the frame if we're recording */ - if(!video || participant->ssrc[0] == 0) { + if(!video || (participant->ssrc[0] == 0 && participant->rid[0] == NULL)) { janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len); } else { /* We're simulcasting, save the best video quality */ gboolean save = janus_rtp_simulcasting_context_process_rtp(&participant->rec_simctx, - buf, len, participant->ssrc, participant->vcodec, &participant->rec_ctx); + buf, len, participant->ssrc, participant->rid, participant->vcodec, &participant->rec_ctx); if(save) { uint32_t seq_number = ntohs(rtp->seq_number); uint32_t timestamp = ntohl(rtp->timestamp); uint32_t ssrc = ntohl(rtp->ssrc); janus_rtp_header_update(rtp, &participant->rec_ctx, TRUE, 4500); - /* We use the main SSRC for the whole recording */ - rtp->ssrc = htonl(participant->ssrc[0]); + /* We use a fixed SSRC for the whole recording */ + rtp->ssrc = htonl(participant->user_id & 0xffffffff); janus_recorder_save_frame(participant->vrc, buf, len); /* Restore the header, as it will be needed by subscribers */ rtp->ssrc = htonl(ssrc); @@ -4578,6 +4595,12 @@ static void janus_videoroom_hangup_media_internal(janus_plugin_session *handle) participant->remb_latest = 0; participant->fir_latest = 0; participant->fir_seq = 0; + int i=0; + for(i=0; i<3; i++) { + participant->ssrc[i] = 0; + g_free(participant->rid[i]); + participant->rid[i] = NULL; + } GSList *subscribers = participant->subscribers; participant->subscribers = NULL; janus_mutex_unlock(&participant->subscribers_mutex); @@ -4865,7 +4888,7 @@ static void *janus_videoroom_handler(void *data) { json_object_set_new(pl, "audio_codec", json_string(janus_audiocodec_name(p->acodec))); if(p->video) json_object_set_new(pl, "video_codec", json_string(janus_videocodec_name(p->vcodec))); - if(p->ssrc[0]) + if(p->ssrc[0] || p->rid[0]) json_object_set_new(pl, "simulcast", json_true()); if(p->audio_level_extmap_id > 0) json_object_set_new(pl, "talking", p->talking ? json_true() : json_false()); @@ -5009,6 +5032,7 @@ static void *janus_videoroom_handler(void *data) { janus_refcount_increase(&subscriber->ref); /* The publisher references the new subscriber too */ /* Check if a simulcasting-related request is involved */ janus_rtp_simulcasting_context_reset(&subscriber->sim_context); + subscriber->sim_context.rid_ext_id = publisher->rid_extmap_id; subscriber->sim_context.substream_target = sc_substream ? json_integer_value(sc_substream) : 2; subscriber->sim_context.templayer_target = sc_temporal ? json_integer_value(sc_temporal) : 2; janus_vp8_simulcast_context_reset(&subscriber->vp8_context); @@ -5449,7 +5473,7 @@ static void *janus_videoroom_handler(void *data) { if(data && publisher->data && subscriber->data_offered) subscriber->data = json_is_true(data); /* Check if a simulcasting-related request is involved */ - if(sc_substream && publisher->ssrc[0] != 0) { + if(sc_substream && (publisher->ssrc[0] != 0 || publisher->rid[0] != NULL)) { subscriber->sim_context.substream_target = json_integer_value(sc_substream); JANUS_LOG(LOG_VERB, "Setting video SSRC to let through (simulcast): %"SCNu32" (index %d, was %d)\n", publisher->ssrc[subscriber->sim_context.substream], @@ -5469,7 +5493,7 @@ static void *janus_videoroom_handler(void *data) { } } if(subscriber->feed && subscriber->feed->vcodec == JANUS_VIDEOCODEC_VP8 && - sc_temporal && publisher->ssrc[0] != 0) { + sc_temporal && (publisher->ssrc[0] != 0 || publisher->rid[0] != NULL)) { subscriber->sim_context.templayer_target = json_integer_value(sc_temporal); JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n", subscriber->sim_context.templayer_target, subscriber->sim_context.templayer); @@ -5917,7 +5941,8 @@ static void *janus_videoroom_handler(void *data) { JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(participant->vcodec), JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RTP_STREAM_ID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->audiolevel_ext ? JANUS_RTP_EXTMAP_AUDIO_LEVEL : NULL, JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->videoorient_ext ? JANUS_RTP_EXTMAP_VIDEO_ORIENTATION : NULL, JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->playoutdelay_ext ? JANUS_RTP_EXTMAP_PLAYOUT_DELAY : NULL, @@ -6021,14 +6046,16 @@ static void *janus_videoroom_handler(void *data) { if(msg_simulcast && (participant->vcodec == JANUS_VIDEOCODEC_VP8 || participant->vcodec == JANUS_VIDEOCODEC_H264)) { JANUS_LOG(LOG_VERB, "Publisher is going to do simulcasting\n"); - participant->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); - participant->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); - participant->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); + janus_rtp_simulcasting_prepare(msg_simulcast, &participant->rid_extmap_id, + participant->ssrc, participant->rid); } else { /* No simulcasting involved */ - participant->ssrc[0] = 0; - participant->ssrc[1] = 0; - participant->ssrc[2] = 0; + int i=0; + for(i=0; i<3; i++) { + participant->ssrc[i] = 0; + g_free(participant->rid[0]); + participant->rid[0] = NULL; + } } } janus_sdp_destroy(offer); @@ -6249,7 +6276,7 @@ static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) return; /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */ gboolean relay = janus_rtp_simulcasting_context_process_rtp(&subscriber->sim_context, - (char *)packet->data, packet->length, packet->ssrc, subscriber->feed->vcodec, &subscriber->context); + (char *)packet->data, packet->length, packet->ssrc, NULL, subscriber->feed->vcodec, &subscriber->context); /* Do we need to drop this? */ if(!relay) return; diff --git a/plugins/plugin.h b/plugins/plugin.h index 0d3d00e313..6ba06b3008 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -169,7 +169,7 @@ janus_plugin *create(void) { * Janus instance or it will crash. * */ -#define JANUS_PLUGIN_API_VERSION 10 +#define JANUS_PLUGIN_API_VERSION 11 /*! \brief Initialization of all plugin properties to NULL * diff --git a/rtp.c b/rtp.c index 2984068a65..b908c4260b 100644 --- a/rtp.c +++ b/rtp.c @@ -105,8 +105,12 @@ const char *janus_rtp_header_extension_get_from_id(const char *sdp, int id) { return JANUS_RTP_EXTMAP_ABS_SEND_TIME; if(strstr(extension, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC)) return JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC; - if(strstr(extension, JANUS_RTP_EXTMAP_RTP_STREAM_ID)) - return JANUS_RTP_EXTMAP_RTP_STREAM_ID; + if(strstr(extension, JANUS_RTP_EXTMAP_MID)) + return JANUS_RTP_EXTMAP_MID; + if(strstr(extension, JANUS_RTP_EXTMAP_RID)) + return JANUS_RTP_EXTMAP_RID; + if(strstr(extension, JANUS_RTP_EXTMAP_REPAIRED_RID)) + return JANUS_RTP_EXTMAP_REPAIRED_RID; JANUS_LOG(LOG_ERR, "Unsupported extension '%s'\n", extension); return NULL; } @@ -242,12 +246,13 @@ int janus_rtp_header_extension_parse_mid(char *buf, int len, int id, return 0; } -int janus_rtp_header_extension_parse_rtp_stream_id(char *buf, int len, int id, +int janus_rtp_header_extension_parse_rid(char *buf, int len, int id, char *sdes_item, int sdes_len) { char *ext = NULL; if(janus_rtp_header_extension_find(buf, len, id, NULL, NULL, &ext) < 0) return -1; - /* a=extmap:3/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */ + /* a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */ + /* a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id */ if(ext == NULL) return -2; int val_len = (*ext & 0x0F) + 1; @@ -284,6 +289,49 @@ int janus_rtp_header_extension_parse_transport_wide_cc(char *buf, int len, int i return 0; } +int janus_rtp_header_extension_replace_id(char *buf, int len, int id, int new_id) { + if(!buf || len < 12) + return -1; + janus_rtp_header *rtp = (janus_rtp_header *)buf; + if (rtp->version != 2) { + return -2; + } + int hlen = 12; + if(rtp->csrccount) /* Skip CSRC if needed */ + hlen += rtp->csrccount*4; + if(rtp->extension) { + janus_rtp_header_extension *ext = (janus_rtp_header_extension *)(buf+hlen); + int extlen = ntohs(ext->length)*4; + hlen += 4; + if(len > (hlen + extlen)) { + /* 1-Byte extension */ + if(ntohs(ext->type) == 0xBEDE) { + const uint8_t padding = 0x00, reserved = 0xF; + uint8_t extid = 0, idlen = 0; + int i = 0; + while(i < extlen) { + extid = buf[hlen+i] >> 4; + if(extid == reserved) { + break; + } else if(extid == padding) { + i++; + continue; + } + idlen = (buf[hlen+i] & 0xF)+1; + if(extid == id) { + /* Found! */ + buf[hlen+i] = (new_id << 4) + (idlen - 1); + return 0; + } + i += 1 + idlen; + } + } + hlen += extlen; + } + } + return -3; +} + /* RTP context related methods */ void janus_rtp_switching_context_reset(janus_rtp_switching_context *context) { if(context == NULL) @@ -815,16 +863,70 @@ void janus_rtp_simulcasting_context_reset(janus_rtp_simulcasting_context *contex return; /* Reset the context values */ memset(context, 0, sizeof(*context)); + context->rid_ext_id = -1; context->substream = -1; context->templayer = -1; } +void janus_rtp_simulcasting_prepare(json_t *simulcast, int *rid_ext_id, uint32_t *ssrcs, char **rids) { + if(simulcast == NULL) + return; + json_t *r = json_object_get(simulcast, "rids"); + json_t *s = json_object_get(simulcast, "ssrcs"); + if(r && json_array_size(r) > 0) { + JANUS_LOG(LOG_VERB, " -- Simulcasting is rid based\n"); + size_t i = 0; + for(i=0; i 0) { + JANUS_LOG(LOG_VERB, " -- Simulcasting is SSRC based\n"); + size_t i = 0; + for(i=0; issrc); + if(ssrc != ssrcs[0] && ssrc != ssrcs[1] && ssrc != ssrcs[2]) { + /* We don't recognize this SSRC, check if rid can help us */ + if(context->rid_ext_id < 1 || rids == NULL) + return FALSE; + char sdes_item[16]; + if(janus_rtp_header_extension_parse_rid(buf, len, context->rid_ext_id, sdes_item, sizeof(sdes_item)) != 0) + return FALSE; + if(rids[0] != NULL && !strcmp(rids[0], sdes_item)) { + JANUS_LOG(LOG_VERB, "Simulcasting: rid=%s --> ssrc=%"SCNu32"\n", sdes_item, ssrc); + *(ssrcs) = ssrc; + } else if(rids[1] != NULL && !strcmp(rids[1], sdes_item)) { + JANUS_LOG(LOG_VERB, "Simulcasting: rid=%s --> ssrc=%"SCNu32"\n", sdes_item, ssrc); + *(ssrcs+1) = ssrc; + } else if(rids[2] != NULL && !strcmp(rids[2], sdes_item)) { + JANUS_LOG(LOG_VERB, "Simulcasting: rid=%s --> ssrc=%"SCNu32"\n", sdes_item, ssrc); + *(ssrcs+2) = ssrc; + } else { + JANUS_LOG(LOG_WARN, "Simulcasting: unknown rid '%s'...\n", sdes_item); + return FALSE; + } + } /* Reset the flags */ context->changed_substream = FALSE; context->changed_temporal = FALSE; diff --git a/rtp.h b/rtp.h index 1cb41664f7..fbad5d41ef 100644 --- a/rtp.h +++ b/rtp.h @@ -25,6 +25,7 @@ #include #include #include +#include #define RTP_HEADER_SIZE 12 @@ -81,8 +82,10 @@ typedef struct janus_rtp_header_extension { #define JANUS_RTP_EXTMAP_PLAYOUT_DELAY "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" /*! \brief a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid */ #define JANUS_RTP_EXTMAP_MID "urn:ietf:params:rtp-hdrext:sdes:mid" -/*! \brief a=extmap:3/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */ -#define JANUS_RTP_EXTMAP_RTP_STREAM_ID "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" +/*! \brief a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id */ +#define JANUS_RTP_EXTMAP_RID "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" +/*! \brief a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id */ +#define JANUS_RTP_EXTMAP_REPAIRED_RID "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id" /*! \brief Helper method to demultiplex RTP from other protocols * @param[in] buf Buffer to inspect @@ -156,7 +159,7 @@ int janus_rtp_header_extension_parse_mid(char *buf, int len, int id, * @param[out] sdes_item Buffer where the RTP stream ID will be written * @param[in] sdes_len Size of the input/output buffer * @returns 0 if found, -1 otherwise */ -int janus_rtp_header_extension_parse_rtp_stream_id(char *buf, int len, int id, +int janus_rtp_header_extension_parse_rid(char *buf, int len, int id, char *sdes_item, int sdes_len); /*! \brief Helper to parse a transport wide sequence number (https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01) @@ -168,6 +171,15 @@ int janus_rtp_header_extension_parse_rtp_stream_id(char *buf, int len, int id, int janus_rtp_header_extension_parse_transport_wide_cc(char *buf, int len, int id, uint16_t *transSeqNum); +/*! \brief Helper to replace the ID of an RTP extension with a different one (e.g., + * to turn a repaired-rtp-stream-id into a rtp-stream-id after a successful rtx) + * @param[in] buf The packet data + * @param[in] len The packet data length in bytes + * @param[in] id The extension ID to look for and replace + * @param[in] new_id The new value for the extension ID + * @returns 0 if found, a negative integer otherwise */ +int janus_rtp_header_extension_replace_id(char *buf, int len, int id, int new_id); + /*! \brief RTP context, in order to make sure SSRC changes result in coherent seq/ts increases */ typedef struct janus_rtp_switching_context { uint32_t a_last_ssrc, a_last_ts, a_base_ts, a_base_ts_prev, a_prev_ts, a_target_ts, a_start_ts, @@ -238,6 +250,8 @@ int janus_videocodec_pt(janus_videocodec vcodec); /*! \brief Helper struct for processing and tracking simulcast streams */ typedef struct janus_rtp_simulcasting_context { + /*! \brief RTP Stream extension ID, if any */ + gint rid_ext_id; /*! \brief Which simulcast substream we should forward back */ int substream; /*! \brief As above, but to handle transitions (e.g., wait for keyframe, or get this if available) */ @@ -260,17 +274,28 @@ typedef struct janus_rtp_simulcasting_context { * @param[in] context The context to (re)set */ void janus_rtp_simulcasting_context_reset(janus_rtp_simulcasting_context *context); +/*! \brief Helper method to prepare the simulcasting info (rids and/or SSRCs) from + * the simulcast object the core passes to plugins for new PeerConnections + * @param[in] simulcast JSON object containing SSRCs and rids + * @param[in] rid_ext_id The rid RTP extension ID to set, if any + * @param[in] ssrcs The list of simulcast SSRCs to update, if any + * @param[in] rids The list of rids to update, if any (items will be allocated) */ +void janus_rtp_simulcasting_prepare(json_t *simulcast, int *rid_ext_id, uint32_t *ssrcs, char **rids); + /*! \brief Process an RTP packet, and decide whether this should be relayed or not, updating the context accordingly * \note Calling this method resets the \c changed_substream , \c changed_temporal and \c need_pli * properties, and updates them according to the decisions made after processinf the packet * @param[in] context The simulcasting context to use * @param[in] buf The RTP packet to process * @param[in] len The length of the RTP packet (header, extension and payload) - * @param[in] ssrcs The simulcast SSRCs to refer to + * @param[in] ssrcs The simulcast SSRCs to refer to (may be updated if rids are involved) + * @param[in] rids The simulcast rids to refer to, if any + * @param[in] rid_ext_id The rid RTP extension id to check, if any * @param[in] vcodec Video codec of the RTP payload * @param[in] sc RTP switching context to refer to, if any (only needed for VP8 and dropping temporal layers) * @returns TRUE if the packet should be relayed, FALSE if it should be dropped instead */ gboolean janus_rtp_simulcasting_context_process_rtp(janus_rtp_simulcasting_context *context, - char *buf, int len, uint32_t *ssrcs, janus_videocodec vcodec, janus_rtp_switching_context *sc); + char *buf, int len, uint32_t *ssrcs, char **rids, + janus_videocodec vcodec, janus_rtp_switching_context *sc); #endif diff --git a/sdp.c b/sdp.c index 4f87cde4ed..8038faa12e 100644 --- a/sdp.c +++ b/sdp.c @@ -384,8 +384,8 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean update) tempA = m->attributes; while(tempA) { janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data; - if(a->name && !strcasecmp(a->name, "rid")) { - /* This attribute is used by Firefox for simulcasting */ + if(a->name && !strcasecmp(a->name, "rid") && a->value) { + /* This attribute is used for simulcasting */ char rid[16]; if(sscanf(a->value, "%15s send", rid) != 1) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Failed to parse rid attribute...\n", handle->handle_id); @@ -401,6 +401,9 @@ int janus_sdp_process(void *ice_handle, janus_sdp *remote_sdp, gboolean update) JANUS_LOG(LOG_WARN, "[%"SCNu64"] Too many RTP Stream IDs, ignoring '%s'...\n", handle->handle_id, rid); } } + } else if(a->name && !strcasecmp(a->name, "simulcast") && a->value) { + /* Firefox and Chrome signal simulcast support differently */ + stream->legacy_rid = strstr(a->value, "rid=") ? TRUE : FALSE; } tempA = tempA->next; } @@ -827,6 +830,10 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid return -2; if(!video) return -3; + if(stream->rid[0] != NULL) { + /* Simulcasting is rid-based, don't parse SSRCs for now */ + return 0; + } gboolean fid = strstr(group_attr, "FID") != NULL; gboolean sim = strstr(group_attr, "SIM") != NULL; guint64 ssrc = 0; @@ -850,7 +857,7 @@ int janus_sdp_parse_ssrc_group(void *ice_stream, const char *group_attr, int vid stream->video_ssrc_peer_new[0] = ssrc; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[0]); } else { - /* We already have a video SSRC: check if RID is involved, and we'll keep track of this for simulcasting */ + /* We already have a video SSRC: check if rid is involved, and we'll keep track of this for simulcasting */ if(stream->rid[0]) { if(stream->video_ssrc_peer_new[1] == 0) { stream->video_ssrc_peer_new[1] = ssrc; @@ -920,27 +927,9 @@ int janus_sdp_parse_ssrc(void *ice_stream, const char *ssrc_attr, int video) { if(ssrc == 0 || ssrc > G_MAXUINT32) return -3; if(video) { - if(stream->video_ssrc_peer_new[0] == ssrc || stream->video_ssrc_peer_new[1] == ssrc - || stream->video_ssrc_peer_new[2] == ssrc) { - JANUS_LOG(LOG_HUGE, "[%"SCNu64"] Already parsed this SSRC: %"SCNu64"\n", handle->handle_id, ssrc); - return 0; - } if(stream->video_ssrc_peer_new[0] == 0) { stream->video_ssrc_peer_new[0] = ssrc; JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC: %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[0]); - } else { - /* We already have a video SSRC: check if RID is involved, and we'll keep track of this for simulcasting */ - if(stream->rid[0]) { - if(stream->video_ssrc_peer_new[1] == 0) { - stream->video_ssrc_peer_new[1] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-1): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[1]); - } else if(stream->video_ssrc_peer_new[2] == 0) { - stream->video_ssrc_peer_new[2] = ssrc; - JANUS_LOG(LOG_VERB, "[%"SCNu64"] Peer video SSRC (sim-2): %"SCNu32"\n", handle->handle_id, stream->video_ssrc_peer_new[2]); - } else { - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Don't know what to do with video SSRC: %"SCNu64"\n", handle->handle_id, ssrc); - } - } } } else { if(stream->audio_ssrc_peer_new == 0) { @@ -1028,6 +1017,7 @@ int janus_sdp_anonymize(janus_sdp *anon) { || !strcasecmp(a->name, "msid") || !strcasecmp(a->name, "msid-semantic") || !strcasecmp(a->name, "rid") + || !strcasecmp(a->name, "simulcast") || !strcasecmp(a->name, "rtcp") || !strcasecmp(a->name, "rtcp-mux") || !strcasecmp(a->name, "rtcp-rsize") @@ -1387,7 +1377,11 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) { g_strlcat(rids, stream->rid[i], sizeof(rids)); } } - a = janus_sdp_attribute_create("simulcast", " recv rid=%s", rids); + if(stream->legacy_rid) { + a = janus_sdp_attribute_create("simulcast", " recv rid=%s", rids); + } else { + a = janus_sdp_attribute_create("simulcast", " recv %s", rids); + } m->attributes = g_list_append(m->attributes, a); } if(!janus_ice_is_full_trickle_enabled()) {