Bug#1150408: asterisk: 2016_opus_plc.patch discards ALL inbound audio after an RTP source change, until seqno wraparound (intermittent self-healing one-way audio)
Vladimir Mirchev
v.mirchev at gmail.com
Fri Oct 9 10:43:00 BST 2026
Package: asterisk
Version: 1:22.5.2~dfsg+~cs6.15.60671435-1
Severity: important
Tags: patch upstream
(Observed on the Ubuntu 26.04 sync of this package, which is unmodified
from Debian; the defect is in the Debian patch series and applies to the
Debian package equally.)
SUMMARY
=======
debian/patches/2016_opus_plc.patch (out-of-tree Opus/PLC support) adds
RTP-seqno-based lost-frame tracking to ast_translate() in
main/translate.c. Its "late packet" handling cannot cope with a mid-call
RTP sequence-number reset (SSRC/source change by a carrier SBC, a routine
event): after such a reset it silently discards EVERY subsequent inbound
frame on that translation path — for 30 seconds up to ~11 minutes — then
"self-heals" and repeats on the next source change.
The failure is completely silent (no log output at any verbosity/debug
level), which makes it look like a network/NAT problem and is, we
believe, a widely-misdiagnosed cause of "intermittent one-way audio that
fixes itself" reports on Debian/Ubuntu Asterisk installations.
The defect exists dormant in older releases too (e.g. asterisk 16.x
packages carried the same patch), but only manifests since upstream
Asterisk (>= 18-era) began stamping received frames with the wire RTP
sequence number (res_rtp_asterisk.c: "rtp->f.seqno = seqno;"), which
arms the patch's gate. This explains why upgrading from asterisk 16 to
20/22 packages surfaces the bug with unchanged configuration.
ROOT CAUSE
==========
From 2016_opus_plc.patch (as applied to main/translate.c, ast_translate()):
if ((path->f.seqno <= rtp_seqno_max_value) && (path->f.seqno !=
f->seqno)) {
if (f->seqno < path->f.seqno) { /* seqno overrun situation */
frames_missing = rtp_seqno_max_value + f->seqno -
path->f.seqno - 1;
} else {
frames_missing = f->seqno - path->f.seqno - 1;
}
/* Out-of-order packet - more precise: late packet */
if ((rtp_seqno_max_value + 1) / 2 < frames_missing) {
if (consume) {
ast_frfree(f);
}
...
return NULL; /* <-- silent discard */
}
...
Two independent failure triggers:
1. Mid-call source change: the carrier's SBC re-anchors media (new SSRC,
new random seqno). If the new seqno lands "behind" the stored
path->f.seqno, frames_missing computes > 32768 and the frame is
discarded as "late". Crucially, path->f.seqno is only updated in
framein() — a code path the discard never reaches — so the state
NEVER advances and every subsequent packet is judged "late" as well.
The lock only clears when the live seqno (incrementing at 50/s for
20 ms audio) numerically passes the stale stored value:
gap/50 seconds = 30 s ... ~11 min of total inbound audio loss.
2. Fresh translation paths start with path->f.seqno == 0 (the pvt is
calloc'd; the patch's own comment assumes an init value of 0x10000
that does not exist on all alloc paths). Any new path whose first
frame carries seqno > 32768 (~50% probability) enters the same
eternal-late state from the very first packet.
Both were observed and instrumented in production-like conditions
(details below).
IMPACT / OBSERVED BEHAVIOUR
===========================
- Intermittent one-way (or both-way, when both legs are hit) audio in
bridged/conference calls, lasting 30 s to 11 min, self-healing,
correlating with the carrier SBC's in-dialog re-INVITEs/media
handovers. Roughly 1 in 3 conference calls affected with our carrier.
- tcpdump shows inbound RTP arriving with speech content and 0 loss;
Asterisk transmits payload of pure alaw digital silence (0xd5) to the
peer(s). rx counters keep incrementing; "rtp set debug" shows packets
received; nothing at all is logged.
- Affects any call path that involves a translation (e.g. alaw <->
slin for ConfBridge/MixMonitor/etc.), i.e. nearly everything.
DIAGNOSIS EVIDENCE (summary)
============================
We instrumented every silent frame-drop site in the read path of a
production-identical staging system (probes in res_rtp_asterisk.c,
chan_pjsip.c, main/channel.c, main/rtp_engine.c, main/translate.c).
During live faults, only the translate.c late-packet discard fired — at
exactly the inbound packet rate (50/s), thousands of consecutive frames,
on precisely the channel whose party became inaudible, while all other
layers delivered valid voice frames.
REPRODUCTION
============
1. Debian/Ubuntu asterisk 20.x/22.x package, any PJSIP trunk, alaw,
a call path involving transcoding (ConfBridge is convenient).
2. Mid-call, change the RTP source seqno space (a carrier SBC media
handover does this; in a lab, re-anchor the sender or switch SSRC
with a discontinuous sequence number).
3. If the new seqno is below the previous stream's, all inbound audio
on that leg goes silent; "rtp set debug" keeps showing received
packets; nothing is logged. Audio returns by itself once the seqno
passes the stale stored value.
PROPOSED FIX (tested)
=====================
Treat a sustained run of "late" verdicts as a stream reset and resync,
instead of discarding forever. Genuinely late single packets (real
reordering) are still dropped, preserving the patch's PLC intent. A
reset costs 3 frames (60 ms) instead of minutes of silence.
Deployed and validated on the affected system: faults eliminated across
test batches; each real source-switch now logs exactly one resync event
and is inaudible to callers.
--- a/include/asterisk/translate.h
+++ b/include/asterisk/translate.h
@@ (struct ast_trans_pvt, after 'struct ast_frame f;')
struct ast_frame f; /*!< used in frameout. */
+ int seqno_late_streak; /*!< consecutive late-packet verdicts;
>=3 => stream reset, resync */
--- a/main/translate.c
+++ b/main/translate.c
@@ ast_translate(), the late-packet branch added by 2016_opus_plc.patch
/* Out-of-order packet - more precise: late packet */
if ((rtp_seqno_max_value + 1) / 2 < frames_missing) {
- if (consume) {
- ast_frfree(f);
- }
- /*
- * Do not pass late packets to any transcoding module,
because that
- * confuses the state of any library (packets
inter-depend). With
- * the next packet, this one is going to be treated as lost
packet.
- */
- return NULL;
+ /*
+ * A sustained run of "late" verdicts is not reordering —
it is a
+ * stream reset (SSRC/source change, e.g. by a carrier
SBC). The
+ * previous behaviour discarded EVERY frame until seqno
wraparound
+ * (30 s – 11 min of one-way silence, completely unlogged).
+ * Resync after 3 consecutive late verdicts; genuinely late
+ * single packets are still dropped as before.
+ */
+ if (++path->seqno_late_streak >= 3) {
+ ast_debug(1, "Translation path seqno resync after %d
late frames (f->seqno=%d path->f.seqno=%d)\n",
+ path->seqno_late_streak, f->seqno, (int)path->f.seqno);
+ path->seqno_late_streak = 0;
+ frames_missing = 0; /* stream reset: translate
normally */
+ } else {
+ if (consume) {
+ ast_frfree(f);
+ }
+ /*
+ * Do not pass late packets to any transcoding module,
because that
+ * confuses the state of any library (packets
inter-depend). With
+ * the next packet, this one is going to be treated as
lost packet.
+ */
+ return NULL;
+ }
+ } else {
+ path->seqno_late_streak = 0;
}
Notes for the maintainer:
- struct ast_trans_pvt is calloc'd, so the new field needs no explicit
init. (The threshold could be a #define; 3 was chosen to still drop
up to two genuinely-reordered stragglers.)
- Independent of the resync, the fresh-path case (trigger 2) suggests
path->f.seqno should be initialised to 0x10000 ("no seqno seen yet")
on every allocation path, as the patch's own comment assumes.
- An alternative, more conservative remedy is to drop the late-packet
discard entirely for non-buffering translators, but the resync above
is minimal and preserves existing behaviour for real reordering.
PRIOR REPORTS OF THE SAME DEFECT (this report provides their root cause)
========================================================================
- Ubuntu bug #2053070 "opus PLC patches cause audio loss" (2024-02-13,
still open/triaged): concluded the patch is "broken and should be
removed" but without a code-level cause or reproducible test case.
https://bugs.launchpad.net/ubuntu/+source/asterisk/+bug/2053070
- Upstream asterisk/asterisk#507 "MixMonitor doesn't record after Blind
Transfer" (2023-12, Debian 12/Asterisk 18): same defect surfacing as
one-way recordings after a transfer (a transfer churns translation
paths, hitting the fresh-path trigger). Reporter confirmed on
2024-02-13: dropping 2015_opus.patch + 2016_opus_plc.patch fixed it.
https://github.com/asterisk/asterisk/issues/507
- Debian bug #1060275 "Codec translation notice" (2024-01): the log-spam
facet ("N lost frame(s)" NOTICEs on plain GSM/G.722/alaw translations
with opus not even loaded). The response was to comment the NOTICE
out ("disabled by default to not flood log in production"), which made
the remaining audio-loss defect completely silent and much harder to
diagnose — as our investigation experienced first-hand.
Unlike the above, this report identifies the exact code defect, both
triggers, and provides a minimal tested fix that keeps the PLC feature
(rather than removing the patches wholesale).
ADDITIONAL CONTEXT
==================
- Not related to upstream GitHub issue #2059 / commit f0c1c1a7b
("res_pjsip_sdp_rtp: Fix intermittent call drop on reINVITE sdp
change") — we backported that fix first and the fault persisted; the
RX-silent defect reported here is specific to 2016_opus_plc.patch.
- Environment: Ubuntu 26.04 (package synced unmodified from Debian),
Asterisk 22.5.2, PJSIP trunk (TCP) to SpeakUp NL, alaw only,
plain RTP (no ICE/SRTP/WebRTC/TURN).
More information about the Pkg-voip-maintainers
mailing list