[Core] JB: fixes and adjustments - #2337
Conversation
|
Unit-tests compilation failed: https://public-artifacts.signalwire.cloud/drone/signalwire/freeswitch/1617/unit-tests-build-result.txt |
|
Scan-build compilation failed: https://public-artifacts.signalwire.cloud/drone/signalwire/freeswitch/1617/scan-build-result.txt |
@jchavanton , you should fix the build. |
Thanks ! |
|
After several days of A/B testing of the elastic jitter buffer
One of the reason why we faced difficulties (that were identified during the ramp-up A/B testing) was that the default jitter buffer is resetting under various conditions some of which are a bit obscure (expecting the unexpected), however even if these resets are damaging the flow of packets they tend to restore the conditions to normal. Adjustments were made to fine tune the reset on the elastic jitter buffer as well. |
4de890e to
e0775c2
Compare
|
Scan-build found bugs: https://public-artifacts.signalwire.cloud/drone/signalwire/freeswitch/1647/index.html |
|
Scan-build found bugs: https://public-artifacts.signalwire.cloud/drone/signalwire/freeswitch/1648/index.html |
|
@dragos-oancea I made a rebase and commit clean up, looks like it is time to review. My take away on the testing is that this is statistically significant, enough to convince my that the elastic jitter buffer is indeed reducing the impact of degradation caused by jitter without introducing new problems. I think, we can already observe the sample distribution is not that random because A and B are always not that far from each other. Anyway I can still dig on poor performing calls and investigate the buffer behavior that was seen, to corner case even more unexpected conditions. |
|
@andywolk Hi Andrey, will you or someone else be able to help with the review. Should I update FS documentation to reflect the new feature and make sure users are aware about it ? |
|
Today I am completing the A/B testing, it lasted 30 days and 10M minutes of very long calls (real users) were compared, we have built enough confidence in the value of this improvement. Considering that the calls have less latency, less missing packets and higher average call duration. |
|
@andywolk I can not make it to cluecon this year, but maybe we could look at merging that MR ? |
…alwire#2337 Cherry-picked fixes from signalwire#2337 (Julien Chavanton): 1. Sequence rollover bug in check_jb_size() - add MAX_DROPOUT guard to distinguish old packets from seq 65535->0 wraparound 2. decrement_seq() ordering - set last_target_seq after decrement 3. Buffering skip gate - do not block elastic mode with the complete_frames < frame_len check 4. Consecutive miss safety valve - after MAX_CONSECUTIVE_MISS (100) consecutive missed frames, disable elastic mode and reset JB
Track net buffer expansion via expand_frame_len (incremented on PLC expand, decremented on acceleration). Reset the jitter buffer when expansion exceeds max_frame_len to prevent unbounded growth during sustained packet loss. Cherry-picked from signalwire#2337 (Julien Chavanton).
d700712 to
0388398
Compare
Introduce packet_stats_t (in_count, in_plc, in_rx, count) and packet_stats_io_info_t (codec/ssrc/addr/callid per direction) on the session. Wire counter increments from the RTP path, the bridge, and the state machine; expose a per-leg JB stats export hook for v2 hangup metrics. Exposes the in_rx counter (packets received from peer, pre-JB) that the damage model joins against bridge/JB writes to compute silence loss and concealment rates. Touches: switch_core.h, switch_core_pvt.h, switch_core_media.h, switch_frame.h (received_ts), switch_rtp.h, switch_core_media.c, switch_core_session.c, switch_core_state_machine.c, switch_ivr_bridge.c, switch_rtp.c.
Track FEC, PLC, and frame counters on the opus decoder. Export them as channel variables (opus_decoder_frames/plc/fec) at decoder teardown so v2 hangup metrics can record the codec-truth concealment count, independent of any bridge-side SFF_PLC accounting. Add gap-tolerant run-length classification: PLC frames in runs ≤3 (≤60ms) are inaudible spectral interpolation; runs >3 degenerate into audible comfort noise. Split exported as plc_short / plc_long / plc_long_starts. A run is gap-tolerant within 200ms so glitchy PLC-real-PLC patterns still count as one audible event.
…alwire#2337 Cherry-picked fixes from signalwire#2337 (Julien Chavanton): 1. Sequence rollover bug in check_jb_size() - add MAX_DROPOUT guard to distinguish old packets from seq 65535->0 wraparound 2. decrement_seq() ordering - set last_target_seq after decrement 3. Buffering skip gate - do not block elastic mode with the complete_frames < frame_len check 4. Consecutive miss safety valve - after MAX_CONSECUTIVE_MISS (100) consecutive missed frames, disable elastic mode and reset JB
Track net buffer expansion via expand_frame_len (incremented on PLC expand, decremented on acceleration). Reset the jitter buffer when expansion exceeds max_frame_len to prevent unbounded growth during sustained packet loss. Cherry-picked from signalwire#2337 (Julien Chavanton).
…e PR signalwire#2337 Consolidate inline channel variable exports from the jitter buffer hot path into switch_jb_export_stats(), called at hangup before hangup hooks run (via switch_core_media_export_jb_stats) and as a safety net before every switch_jb_destroy() in both switch_rtp_destroy() and the KILL_JB path in read_rtp_packet(). This prevents a race condition where hangup hooks read stale/missing JB stats because the jitter buffer was already destroyed or the inline exports hadn't run recently enough. Adapted from upstream commit 0388398 — excluded fast_acceleration, forced_acceleration, and packets_in_buffer fields which don't exist in our switch_jb struct. Cherry-picked from signalwire#2337 (Julien Chavanton).
…signalwire#2337 1. Add switch_rtp_ready() guard in the JB poll loop to prevent polling after the session is being destroyed. 2. Add rtp_jitter_buffer_accelerate channel variable check to the tries > 20 condition, allowing JB reset to be skipped when acceleration is explicitly enabled.
Add elastic algorithm activity tracking and enhanced statistics export from upstream commit af83338: - New stats fields: grew, shrunk, grow_to_max, at_min_ms, at_max_ms, edge_since, reset_setup, reset_runtime - New jb fields: media_started, packets_total - Track frame_len edge transitions in jb_frame_inc_line() - Split reset count into setup vs runtime in switch_jb_reset() - Flush at-edge timer on set_frames() - Initialize edge_since on JB creation - Export all new metrics in switch_jb_export_stats() with a comprehensive summary log line - Set media_started and increment packets_total on each put_packet Cherry-picked from signalwire#2337 (Julien Chavanton).
…SignalWire PR signalwire#2337 (#539) * TEL-6872: Cherry-pick jitter buffer bug fixes from SignalWire PR signalwire#2337 Cherry-picked fixes from signalwire#2337 (Julien Chavanton): 1. Sequence rollover bug in check_jb_size() - add MAX_DROPOUT guard to distinguish old packets from seq 65535->0 wraparound 2. decrement_seq() ordering - set last_target_seq after decrement 3. Buffering skip gate - do not block elastic mode with the complete_frames < frame_len check 4. Consecutive miss safety valve - after MAX_CONSECUTIVE_MISS (100) consecutive missed frames, disable elastic mode and reset JB * TEL-6872: Add unbounded expand protection for elastic jitter buffer Track net buffer expansion via expand_frame_len (incremented on PLC expand, decremented on acceleration). Reset the jitter buffer when expansion exceeds max_frame_len to prevent unbounded growth during sustained packet loss. Cherry-picked from signalwire#2337 (Julien Chavanton). * TEL-6872: Cherry-pick JB statistics race condition fix from SignalWire PR signalwire#2337 Consolidate inline channel variable exports from the jitter buffer hot path into switch_jb_export_stats(), called at hangup before hangup hooks run (via switch_core_media_export_jb_stats) and as a safety net before every switch_jb_destroy() in both switch_rtp_destroy() and the KILL_JB path in read_rtp_packet(). This prevents a race condition where hangup hooks read stale/missing JB stats because the jitter buffer was already destroyed or the inline exports hadn't run recently enough. Adapted from upstream commit 0388398 — excluded fast_acceleration, forced_acceleration, and packets_in_buffer fields which don't exist in our switch_jb struct. Cherry-picked from signalwire#2337 (Julien Chavanton). * TEL-6872: Fix JB review findings - frame_len reset, missing log, expand boundary [1] Reset jb->frame_len to jb->min_frame_len when disabling elastic mode after consecutive miss safety valve triggers. Without this, the JB stays at expanded size after elastic mode is disabled. [2] Add SWITCH_LOG_ALERT debug log when elastic mode is disabled after MAX_CONSECUTIVE_MISS, matching upstream. [5] Add expand_frame_len >= max_frame_len boundary check (third else-if branch) to catch the exact boundary case and reset via reset_error. * TEL-6872: Add missing switch_rtp.c functional changes from upstream PR signalwire#2337 1. Add switch_rtp_ready() guard in the JB poll loop to prevent polling after the session is being destroyed. 2. Add rtp_jitter_buffer_accelerate channel variable check to the tries > 20 condition, allowing JB reset to be skipped when acceleration is explicitly enabled. * TEL-6872: Cherry-pick JB more metrics from SignalWire PR signalwire#2337 Add elastic algorithm activity tracking and enhanced statistics export from upstream commit af83338: - New stats fields: grew, shrunk, grow_to_max, at_min_ms, at_max_ms, edge_since, reset_setup, reset_runtime - New jb fields: media_started, packets_total - Track frame_len edge transitions in jb_frame_inc_line() - Split reset count into setup vs runtime in switch_jb_reset() - Flush at-edge timer on set_frames() - Initialize edge_since on JB creation - Export all new metrics in switch_jb_export_stats() with a comprehensive summary log line - Set media_started and increment packets_total on each put_packet Cherry-picked from signalwire#2337 (Julien Chavanton). * TELCORE-248: address review findings on elastic JB cherry-pick Fix double-decrement in decrement_seq() — last_target_seq was over-decremented by one, shifting PLC sequence numbers an extra packet behind. Fix lock-order inversion in switch_jb_export_stats() — snapshot JB fields under mutex, unlock before channel writes to avoid holding JB mutex while acquiring channel-side locks from hangup/destroy/KILL_JB contexts. Cache rtp_jitter_buffer_accelerate channel var at function entry instead of per-iteration in the retry loop, and restore an unconditional hard retry bound (tries > 100) so the read loop cannot spin indefinitely in bad-jitter conditions. --------- Co-authored-by: Ryan Colobong <ryanc@76xl6d3.office.telnyx.com>
Comprehensive rework of the elastic jitter buffer covering tuning, metrics, and a new arrival-time jitter estimator: - Two-tier EWMA+EWMV jitter estimator: fast (~320ms) and slow (~10s) tiers exported as jitter_mean_ms/sigma_ms (+ _slow_ms variants). Slow tier and clock-drift state survive switch_jb_reset. - Clock-drift detection from RTP timestamp sampling, logged as ppm. - Categorized reset accounting: reset_too_big, reset_too_expanded (with dropped-packet count), reset_missing_frames, plus buffering_skip and expand_ms. - Expand floor and "keep elastic on miss" so brief gaps don't collapse the EJB to non-elastic mode. - Export switch_jb_is_elastic() and switch_jb_export_stats() so hangup metrics can pull JB state per leg. - Headline counters surfaced via switch_core_media_export_jb_stats feed the v2 damage model (silence loss vs concealment). Includes earlier statistics fixes, duplicate overflow-block removal, small tuning adjustments, and follow-up EJB auto-disable fix.
…ed_pkt) Expose the RTCP receiver-report loss counters as channel variables so hangup metrics and applications can measure true inbound packet loss (carrier→FS) independently of JB behavior: rtp_audio_in_cum_lost - cumulative packets never received rtp_audio_in_expected_pkt - packets expected (high_ext_seq - base + 1) Ratio cum_lost / expected_pkt is the honest inbound loss rate. Prior JB damage models could only observe silence and concealment; adding these lets us separate carrier-side loss from JB-side loss and stop misattributing the two. Populated only when rtcp-audio-interval-msec is set on the sip profile (without RTCP the sequence-tracking path in rtcp_stats early-returns). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Superseded by #3118 — reorganized into 4 logical commits, revised description, canary-validated A/B. |








[Core] JB: if the jitter buffer size is above the its max size, we force accelerate
[Core] JB: proper handling of seq roll-over
[Core] JB: fix seq decrement
[Core] JB: add buffering skip counter
[Core] JB: reset when expanding above the maximum size
[Core] JB: disable skip buffering when acceleration is active
These fixes and the adjustment were thoroughly tested, they were found while testing the packet relay metrics. #2292
A/B testing with 100 sessions and no artificial jitter, to make sure everything is working well under normal conditions.
EJB : more elastic jitter buffer

The packet relay metrics are still not ready for merge but I am able to use them to precisely monitor the jitter buffer in production.
Here is one report of real traffic facing jitter, I selected it randomly from production traffic.
We can use it as a source of validation evidence.
Configuration:
End of call report:
{ "rp": { "call_id_in": "rt342cma1dgelvphc5pb", "call_id_out": "c454bbd8-4e26-4049-85bb-20569a0b2990", "report": { "in": { "ssrc": "0x95262C82", "remote_socket": "172.11.61.245:18550", "local_socket": "172.11.61.245:35930", "codec": "opus", "count": 43181, // ~15 minutes call "plc": 732 // 1.6% PLC }, "out": { "ssrc": "0xA95A90EE", "remote_socket": "172.11.50.19:34402", "local_socket": "172.11.61.245:13842", "codec": "PCMU", "count": 43181, "max_ms": 1060, // this is the maximum time spent by any packet inside FS, we can see close to matching the JB max size. "avg_ms": 74.57 // this is the average time spent by any packet inside FS } } }, "jb": { "in_call_id": "rt342cma1dgelvphc5pb", "out_call_id": "c454bbd8-4e26-4049-85bb-20569a0b2990", "jb": { "size_max_ms": 1220, // we can see that the maximum jitter buffer size was respected "size_est_ms": 20, "acceleration_ms": 13680, "expand_ms": 13580, "fast_acceleration_ms": 0, "forced_acceleration_ms": 60, // at one point the jitter buffer was above its max size "jitter_max_ms": 140, // very high jitter was seen during that call "jitter_est_ms": 8, // near the end there was not that much jitter "reset": 7, "reset_too_big": 0 } } }