Auto jitter buffer hysteresis compares against a constant, not the previous decision
I maintainer di solito rispondono entro 3 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 30/100
- Tipo di issue
- Bug
- Chiarezza
- Da chiarire
- Stato di attività
- Attiva
- Stack tecnologico
- cpp
- Ambito
- audio-video-rtc, performance
Direzione di ricerca
Leggi src/buffer.cpp in corrispondenza di UpdateAutoSetting e Init(), src/channel.cpp in corrispondenza di SetSockBufNumFrames e MathUtils::DecideWithHysteresis in src/util.h. Per prima cosa determina quale comportamento è previsto: l’ancora congelata, l’arrotondamento semplice o un’ancora basata dinamicamente sulla decisione precedente. Il lavoro è completato quando i maintainer del progetto scelgono uno dei comportamenti e concordano l’implementazione corrispondente e la copertura delle regressioni.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
🤖 AI: Splitting the jitter-buffer hysteresis item out of #3916 into its own thread, since the explanation there was not landing. This is a question about intended behaviour, not a patch proposal.
The code
The call in UpdateAutoSetting asks for hysteresis against the previous decision:
// apply a hysteresis
iCurAutoBufferSizeSetting = MathUtils().DecideWithHysteresis ( dCurIIRFilterResult, iCurDecidedResult, dHysteresisValue );
The return lands in iCurAutoBufferSizeSetting. The "previous decision" argument, iCurDecidedResult, is assigned in exactly one place — inside Init()'s !bPreserve branch:
iCurAutoBufferSizeSetting = 6;
dCurIIRFilterResult = iCurAutoBufferSizeSetting;
iCurDecidedResult = iCurAutoBufferSizeSetting;
An auto resize calls SetSockBufNumFrames ( SockBuf.GetAutoSetting(), true ), which re-Inits with bPreserve = true, so no resize ever re-runs that branch — and 6 is the only value the branch assigns anyway, so the argument is 6 on every call for the life of the connection. DecideWithHysteresis takes it as const int, by value, so the callee cannot write it back either — the missing statement is on the caller's side.
It is not dead code
Sampling the shipped function over a grid with the anchor pinned at 6 and the shipped FILTER_DECISION_HYSTERESIS of 0.1 — the comment above it states the goal, "to avoid fast changes if close to the bound" — printing where its output steps up:
MAP|h=0.10|old= 6|0.400->1 1.400->2 2.400->3 3.400->4 4.400->5 5.400->6 6.600->7 7.600->8 ...
Size 6 is held over [5.400, 6.600) — width 1.200. Every other size is bounded by thresholds exactly 1.000 apart: plain rounding with the boundaries moved to N.400 below the anchor and N.600 above it. The dead band is not disabled, it is relocated to a single size — 6, the value Init() assigns — and absent everywhere else. Deleting the call puts every boundary back at N.500, so removal is a behaviour change rather than a cleanup.
Changing the constant is not a third option either: with the anchor frozen the steps stay exactly 1.000 apart, and 0.05, 0.2 and 0.3 only slide the ladder — below the anchor to N.450, N.300, N.200, and the same distance the other way above it. A bigger or smaller pull changes how hard the size is biased toward 6; it adds stickiness nowhere. Only a live anchor produces a band, and its width is 1 + 2h.
What the bias costs
With synthetic jitter alternating between 1.0 and 3.0 frames every 10 000 frames, three seeds — the regime where the sizer wants a buffer near 9 to 10 blocks — the pull toward 6 holds the applied size 0.166 blocks lower than plain rounding and drops 29.0% more packets (8 386 against 6 499). It fights the error-rate criterion in exactly the regime where that criterion is asking for a large buffer.
Since when, and why no patch is attached
93e12245 ("code cleanup", 2011-06-29) removed the last assignment that tracked the current decision, and every tagged release carrying the auto sizer's IIR post-filter also carries that cleanup — so no release has ever compared against the previous decision.
Arrival traces recorded from two real paths, one datacentre and one consumer, 562 500 probe packets sent on each at 375/s, each trace replayed through the shipped code at nine consumer-clock phase offsets with every candidate seeing byte-identical arrivals — 369 thirty-second windows per candidate per path (datacentre / consumer):
| windows with a size change | mean applied depth | |
|---|---|---|
| frozen anchor, as shipped | 10.8% / 16.5% | 4.546 / 6.582 |
| removal (plain rounding) | 10.8% / 12.5% | 4.467 / 6.721 |
| anchor written back | 5.4% / 7.3% | 4.464 / 6.692 |
Writing the anchor back halves how often the size moves, on both paths. Its latency cost changes sign with the operating point, for the reason above: -0.082 blocks where the buffer settles below 6 (4.5 blocks, the datacentre path), +0.110 blocks (+0.29 ms) where it settles above (6.6, the consumer path).
The same replay driven by a genuine client (headless, jackd dummy driver; the second capture carries sequence numbers on the wire — 647 973 packets, none lost, none reordered) confirms the ordering: the write-back changes size least, 17 against the shipped 27 on one path and 10 against 14 on the other, over 342 and 432 windows. It also qualifies removal: these clients settled at 6.0 and 6.8 blocks, close to the anchor, and there removal was the least stable of the three — 38 and 24 changes.
So the question, in #545 territory: which of the three is intended — the frozen anchor as shipped, plain rounding (proposed on #3916), or hysteresis against the previous decision? I can put the harness and the traces in a gist.
🤖 This message was written by AI and reviewed by @mcfnord.
- Lingua principale
- C
- Stelle
- 1.1k
- Fork
- 248
- Merge medio
- 2g 22h
- PR unite (30g)
- 6
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di jamulussoftware/jamulus
-
Move translation checker (and potentially other runners) to ARM runnerForse già presa @ann0see l’ha presa 19 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
jamulussoftware/jamulus#3953 · 2 commenti ·
I maintainer di solito rispondono entro 3 giorni
-
AI bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
jamulussoftware/jamulus#3901 · 4 commenti · 1 reazione ·
I maintainer di solito rispondono entro 3 giorni
-
AI
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
jamulussoftware/jamulus#3846 ·
I maintainer di solito rispondono entro 3 giorni
-
Qt6 moving towards cmakeApertafeature request
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
jamulussoftware/jamulus#3964 · 3 commenti ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 65/100
jamulussoftware/jamulus#3961 ·
I maintainer di solito rispondono entro 3 giorni
Tutte le issue di jamulussoftware/jamulus
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
containers/bubblewrap#813 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
tree-sitter/tree-sitter#6005 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
kubernetes-sigs/security-profiles-operator#3537 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
darktable-org/darktable#22502 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno