Skip to content

Keep the fixed egress SSRC when an audio player feeds the media - #2195

Open
jleal52 wants to merge 1 commit into
sipwise:masterfrom
jleal52:fixed-egress-ssrc-audio-player
Open

jleal52 wants to merge 1 commit into
sipwise:masterfrom
jleal52:fixed-egress-ssrc-audio-player

Conversation

@jleal52

@jleal52 jleal52 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Needs #2237 merged first (or together). With this change the audio player sends under the shared fixed egress entry, which makes the send-timer RTCP lock inversion fixed there easy to hit: without it the extended inject tests deadlocked in 1 of 3 runs (in "SRTP under the sender key with a fixed egress SSRC ..."), with it in none.

Problem

With fixed egress SSRC forwarded media keeps one SSRC towards the recipient, but an audio player on the same media (e.g. the mixer of an audio-player=force conference) sends under its own random SSRC. A WebRTC receiver without signalled SSRCs binds to the newest SSRC and does not go back to one it has already replaced: after leaving the conference the browser receives packets but plays nothing.

Change

  • media_player_set_sink(): the player sends under the fixed egress SSRC. On every (re)start it continues from the egress ext_seq and zeroes seq_diff (the player numbers its packets as mp->seq + seq_diff and advances seq_diff itself).
  • media_player_add_packet(): sets fixed_rebase on the egress SSRC entry; media_packet_rtp_out() re-bases forwarded media on ext_seq when the player stops.
  • media_packet_rtp_out(): no fixed-SSRC sequence bookkeeping while audio_player_is_active() on the sink. Those packets are decoded into the mix buffer; re-basing seq_diff on each of them corrupted the player's output numbering.
  • auto-daemon-tests-inject.pl: under a fixed egress SSRC the injection now reaches A under that SSRC, so the fixed-egress subtests tell B's packets from the player's by B's timestamps (which pass through unchanged; the player runs its own clock), check that the injection does arrive under the fixed SSRC, and check the sequence continuous across the injection too (it was only checked without injection).

Receiver sees one SSRC with continuous sequence numbers: forwarded → mixed → forwarded. This is the fixed-egress-SSRC side of 4fad5b8: without a fixed SSRC the sender's packets keep their own numbers and the receiver sees a forward gap per injection; with it, the stream is re-based at every change of source (as auto-daemon-tests-inject.pl already expects), and the player is now one more source under the same SSRC. Resuming at the sender's numbers instead could land behind the player's last number, a backward step that SRTP replay protection punishes.

Known residual: a supplemental (telephone-event) packet forwarded while the player runs goes out with an arbitrary sequence number.

Rebased on current master

Testing

make asan-check passes on this branch alone, and so does daemon-tests-ssrc with RTPENGINE_EXTENDED_TESTS=1. daemon-tests-inject with RTPENGINE_EXTENDED_TESTS=1 passes the fixed-egress subtests in every run; the only failures seen are the timing-dependent "ten consecutive expected PCM frames" checks (e.g. "standalone playback stopped after injection"), which fail intermittently on unmodified master as well. Originally verified with packet captures in a WebRTC deployment (Chrome agents ⇄ rtpengine ⇄ SIP trunk, fixed egress SSRC on the answers towards the browser): call, consult, three-way conference with audio-player=force, remove a member, re-conference in the same call, hold/resume, switch, blind and attended transfer. Sequence numbers towards the browser are continuous in all cases.

With `fixed egress SSRC`, forwarded media keeps one SSRC towards the
recipient, but an audio player on the same media (e.g. the mixer of an
`audio-player=force` conference) sent under its own random SSRC. A WebRTC
receiver without signalled SSRCs binds to the newest one and does not go
back: audio went silent after leaving a conference.

- media_player_set_sink(): send under the fixed egress SSRC; on every
  (re)start continue from the egress ext_seq and zero seq_diff. The player
  numbers its packets as mp->seq + seq_diff and advances seq_diff itself.
- media_player_add_packet(): set fixed_rebase on the egress entry so that
  forwarded media re-bases its numbering on ext_seq when it resumes.
- media_packet_rtp_out(): no fixed-SSRC sequence bookkeeping while an audio
  player is active on the sink. Those packets are decoded into the mix
  buffer; re-basing seq_diff on each of them corrupted the player's output.

The decoders that feed the audio player no longer move seq_diff since
4fad5b8 ("a stream the audio player consumes keeps the sender's
numbers"), which this relies on: with a fixed egress SSRC that offset is
the one the player sends under.

auto-daemon-tests-inject.pl: under a fixed egress SSRC the injection now
reaches A under that SSRC, so B's packets are told from the player's by
B's timestamps, and the sequence is checked continuous across the
injection too.

Known residual: a supplemental (telephone-event) packet forwarded while the
player runs goes out with an arbitrary sequence number.
@rfuchs

rfuchs commented Nov 13, 2026

Copy link
Copy Markdown
Member

Known residual: a supplemental (telephone-event) packet forwarded while the player runs goes out with an arbitrary sequence number.

This seems a bit like a deal breaker to me. This will land on my desk to fix when inevitably somebody complains about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants