MT#55283 Add a fixed egress SSRC per media

A controlling agent feeding media into a WebRTC SFU needs to know the SSRC
of the incoming stream before it can bind a receiver to it, and the SFU
needs that SSRC to stay put for the life of the call.

With `fixed egress SSRC` set, rtpengine picks an SSRC for each media at
signalling time and returns the chosen values in the offer or answer
response, one entry per m= section along with its index and media type.

The value is recorded as the ingress entry's `ssrc_map_out`, which is
already the mapping every consumer reads: the egress entry is keyed off it,
RTCP rewriting uses it, and the kernel module is handed it as `ssrc_out`.

Because one egress entry then stands in for every ingress SSRC, it outlives
a change of source, so `seq_diff` carries across on its own. All that is
left is re-basing it once at the changeover, taking the last sequence
number from the counter shared with the kernel module so that it is still
right while the stream is offloaded.

The substitution itself goes in the plain passthrough handler rather than
the SSRC passthrough one, so that it applies to builds without transcoding
support as well. Those builds compile the kernel side regardless, so
leaving the userspace half out would have meant the original SSRC being
forwarded until the stream was offloaded and the fixed one afterwards.

Change-Id: Iad5508c53d29d1e7d54c825da1ab56069cdd89b7
master
dnygate 1 day ago committed by Richard Fuchs
parent 579097e812
commit 7674d8529c

@ -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)))

@ -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);

@ -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");

@ -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;

@ -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)

@ -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=..." }

@ -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() */

@ -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;
};

@ -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 {

Loading…
Cancel
Save