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()) {