diff --git a/daemon/call.c b/daemon/call.c index 6b7311a41..f7e487ac2 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -3137,6 +3137,11 @@ static void media_init_from_flags(struct call_media *media, sdp_ng_flags *flags) if (flags->recrypt) MEDIA_SET(media, RECRYPT); + + if (flags->fixed_egress_ssrc) { + while (!media->fixed_egress_ssrc) + media->fixed_egress_ssrc = ssl_random(); + } } __attribute__((nonnull(1, 2))) diff --git a/daemon/call_flags.c b/daemon/call_flags.c index 1bda1580d..75851bde0 100644 --- a/daemon/call_flags.c +++ b/daemon/call_flags.c @@ -973,6 +973,10 @@ const char *call_ng_flags_flags(str *s, unsigned int idx, helper_arg arg) { case CSH_LOOKUP("strict source"): out->strict_source = true; break; + case CSH_LOOKUP("fixed-egress-SSRC"): + case CSH_LOOKUP("fixed egress SSRC"): + out->fixed_egress_ssrc = true; + break; case CSH_LOOKUP("strip-extmap"): case CSH_LOOKUP("strip extmap"): return call_ng_flags_str_ht(STR_PTR("all"), 0, &out->rtpext_strip); diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 8fb16a402..393598a99 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -689,6 +689,21 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char *addr) /* place return output SDP */ ctx->ngbuf->sdp_out = sdp_out.s; ctx->parser_ctx.parser->dict_add_str(output, "sdp", &sdp_out); + + /* report the SSRCs picked for media sent towards the recipient of this SDP, + * one entry per m= section */ + if (flags.fixed_egress_ssrc && to_ml->medias) { + parser_arg ssrcs = parser->dict_add_list(output, "egress SSRC"); + for (unsigned int i = 0; i < to_ml->medias->len; i++) { + struct call_media *media = to_ml->medias->pdata[i]; + if (!media || !media->fixed_egress_ssrc) + continue; + parser_arg ent = parser->list_add_dict(ssrcs); + parser->dict_add_int(ent, "index", media->index); + parser->dict_add_str(ent, "type", &media->type); + parser->dict_add_int(ent, "SSRC", media->fixed_egress_ssrc); + } + } if (flags.supports_rollback) { parser_arg supported = parser->dict_add_list(output, "supported"); parser->list_add_string(supported, "rollback"); diff --git a/daemon/codec.c b/daemon/codec.c index 055c18c7e..c4a794075 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -2210,6 +2210,13 @@ static int handler_func_passthrough(struct codec_handler *h, struct media_packet ML_CLEAR(mp->media->monologue, DTMF_INJECTION_ACTIVE); + // substitute a fixed egress SSRC. done here rather than in the SSRC + // passthrough handler as that one is only built with transcoding support. + if (mp->rtp && mp->media_out->fixed_egress_ssrc && mp->ssrc_out) { + mp->rtp->ssrc = htonl(mp->ssrc_out->h.ssrc); + mp->rtp->seq_num = htons(ntohs(mp->rtp->seq_num) + mp->ssrc_out->seq_diff); + } + __buffer_delay_raw(h->delay_buffer, h, codec_add_raw_packet, mp, h->source_pt.clock_rate); return 0; diff --git a/daemon/media_socket.c b/daemon/media_socket.c index db5898291..508ea3581 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1858,7 +1858,8 @@ static const char *kernelize_one(kernelize_state *s, } - if (MEDIA_ISSET(media, ECHO) || sink_handler->attrs.transcoding) + if (MEDIA_ISSET(media, ECHO) || sink_handler->attrs.transcoding + || sink->media->fixed_egress_ssrc) redi->output.ssrc_subst = 1; __re_address_translate_ep(&redi->output.dst_addr, &sink->endpoint); @@ -3048,15 +3049,39 @@ static void media_packet_rtp_out(struct packet_handler_ctx *phc, struct sink_han const char *unkern = NULL; + uint32_t fixed_ssrc = phc->mp.media_out->fixed_egress_ssrc; + + /* A fixed egress SSRC is just a mapping from the ingress SSRC, so record it + * where every consumer already looks for one. */ + if (fixed_ssrc && phc->mp.ssrc_in) + phc->mp.ssrc_in->ssrc_map_out = fixed_ssrc; + if (G_LIKELY(!phc->rtcp && phc->mp.rtp)) { unkern = __stream_ssrc_out(phc->out_srtp, phc->mp.rtp->ssrc, phc->mp.ssrc_in, &phc->mp.ssrc_out, &phc->mp.media_out->ssrc_hash_out, - sh->attrs.transcoding ? true : false); + sh->attrs.transcoding || fixed_ssrc); + + /* a fixed egress SSRC outlives the ingress SSRC, so carry the + * sequence numbering across a change of source */ + if (fixed_ssrc && phc->mp.ssrc_out) { + struct ssrc_entry_call *so = phc->mp.ssrc_out; + uint32_t in_ssrc = ntohl(phc->mp.rtp->ssrc); + uint16_t seq = ntohs(phc->mp.rtp->seq_num); + if (so->fixed_in_ssrc_set && so->fixed_in_ssrc != in_ssrc) { + /* the egress sequence counter lives in memory shared with + * the kernel module, so it stays current even while the + * stream is offloaded and userspace sees no packets */ + uint16_t last = atomic_get_na(&so->stats->ext_seq); + so->seq_diff = last + 1 - seq; + } + so->fixed_in_ssrc = in_ssrc; + so->fixed_in_ssrc_set = true; + } } else if (phc->rtcp && phc->mp.rtcp) { unkern = __stream_ssrc_out(phc->out_srtp, phc->mp.rtcp->ssrc, phc->mp.ssrc_in, &phc->mp.ssrc_out, &phc->mp.media_out->ssrc_hash_out, - sh->attrs.transcoding ? true : false); + sh->attrs.transcoding || fixed_ssrc); } if (unkern) diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 470c9532f..7a7afbfc0 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -1376,6 +1376,18 @@ Spaces in each string may be replaced by hyphens. negotiated are removed from forwarded RTP. Once set, the flag remains in effect for the lifetime of the call. +* `fixed egress SSRC` + + Pick an SSRC for each media at signalling time and use it for all RTP sent + towards that media, instead of passing through the SSRC that the other side + happens to be sending. The chosen values are returned in the response, and + they stay in place for the lifetime of the call, so a source that changes + its own SSRC mid-call is still forwarded under the same one, with the + sequence numbering carried across the change. + + Intended for feeding media into a system that has to bind a receiver to an + SSRC up front, such as a WebRTC selective forwarding unit. + * `strict source` Normally, *rtpengine* attempts to learn the correct endpoint address for every stream during @@ -1821,6 +1833,10 @@ SDP body that the SIP proxy should insert into the SIP message. If `supports` requested a supported extension, the response can also contain a `supported` list. +With `fixed egress SSRC` set, the response also contains `egress SSRC`, a list with +one entry per `m=` section of the returned SDP, in the same order. Each entry gives +the `index` and `type` of the media along with the `SSRC` chosen for it. + Example response: { "result": "ok", "sdp": "v=0\r\no=..." } diff --git a/include/call.h b/include/call.h index b044c4514..b2d6fecff 100644 --- a/include/call.h +++ b/include/call.h @@ -562,6 +562,9 @@ struct call_media { struct ssrc_hash ssrc_hash_in; struct ssrc_hash ssrc_hash_out; + /* fixed SSRC to use for RTP sent towards this media, 0 if unset */ + uint32_t fixed_egress_ssrc; + struct codec_store codecs; struct codec_store offered_codecs; sdp_attr_q generic_attributes; /* sdp_attr_new() */ diff --git a/include/call_flags.h b/include/call_flags.h index 09ab29d37..afbeae753 100644 --- a/include/call_flags.h +++ b/include/call_flags.h @@ -327,7 +327,9 @@ RTPE_NG_FLAGS_STR_CASE_HT_PARAMS /* prevents double MoH holds */ moh_double_hold:1, /* process RTP header extensions even if none were negotiated */ - force_strip_extmap:1; + force_strip_extmap:1, + /* pick a fixed egress SSRC per media and report it */ + fixed_egress_ssrc:1; }; diff --git a/include/ssrc.h b/include/ssrc.h index 1ad5876d0..66087ba1b 100644 --- a/include/ssrc.h +++ b/include/ssrc.h @@ -93,6 +93,11 @@ struct ssrc_entry_call { uint32_t jitter, transit; // output only uint16_t seq_diff; + + /* for a fixed egress SSRC: the last ingress SSRC seen, so a change of + * source can be spotted and the sequence numbering carried across it */ + uint32_t fixed_in_ssrc; + bool fixed_in_ssrc_set; }; struct ssrc_time_item {