Replies: 1 comment 2 replies
|
Server versions are slow to change, so I wouldn't think about a solution that relies on Server-side changes.
So any solution that requires either a Server-only or bi-directional change probably will be of limited impact. |
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
🤖 AI: Every OPUS64 encoder in Jamulus is configured with
OPUS_SET_PACKET_LOSS_PERC(35)(src/client.cpp:119,src/server.cpp:128) whether or not the connection is losing anything. The comment there - "for 64 samples frame size we have to adjust the PLC behavior to avoid loud artifacts" - is correct, and the constant is doing real work. It is also being paid for by everyone, including people losing nothing.The setting tells Opus how much of a fixed bit budget to spend making each frame survivable when its neighbours go missing. Pinned at 35, it spends that on every connection. Jamulus is used to play music, so the measurements below are music: 21 recordings of contrasting character, the codec driven at Jamulus's own OPUS64 settings, scored with ViSQOL in audio mode - an objective estimator of MOS, the 1-to-5 listening-quality scale - with segmental SNR as the independent cross-check.
What the constant costs a clean connection. At the shipped 35-byte tier, plc=35 costs ViSQOL MOS against plc=0 on 20 of the 21 recordings, median 0.014, up to 0.056 on a solo guitar. Small, real - and a property of the recording that nothing at the sender can estimate: the two percussion-labelled recordings span 0.002 to 0.037, the two guitar-labelled ones sit at opposite ends of the whole range, and the per-recording cost correlates with no signal-domain measure tried (rank correlation -0.12 against the decoded-waveform cost of the same setting). An earlier measurement on a single piano recording had found no cost at all - the four
loss_percvalues within 0.006 and 35 not even lowest - and across the corpus that file turned out to be the single exception. Controls: identical input scores 4.732, the audio-mode ceiling; the score rises monotonically across the shipped 24/35/73-byte tiers; and all 44 clean p0/p35 arms reproduce bit-identically (same encoder checksums) when re-run.What it buys under loss.
ViSQOL, medians across the music recordings (11 at loss 2 and 10, 21 at loss 5). Two things the table compresses. The benefit is roughly proportional to the damage: at burst 1, plc=35 gives back 83-87% of whatever quality the loss destroyed, on nearly every recording. And it does not survive bursts: real paths lose in runs, and by mean burst 32 the recovered share has collapsed to 5-23% - at 2% loss the benefit is actually negative on 6 of the 11 recordings measured there. So the constant earns its keep on genuinely bad connections, pays a small unpredictable tax on clean ones, and past long-burst loss stops helping at all. No single value is right for all three regimes, which is why retuning the constant is not the fix.
One honesty note about the instrument. Segmental SNR over the same arms agrees with every direction above but puts the recovered share at 47% where ViSQOL puts 86 (10% loss, burst 1, same recordings). Concealment evidently sounds better than it matches the waveform, and how much better is a question no amount of scoring settles without ears.
What could drive the ctl. The obvious input is the one Jamulus already computes:
CNetBuf::Get()returns false exactly when the decoder must conceal, andCChannel::GetDataturns that intoGS_BUFFER_UNDERRUN. Measured, that counter cannot support this ctl on its own, because it reports the event and not the cause. Real client against real server, 120 s per arm, one network condition injected at a time:Concealment reads 5.39 on a link losing 5% and 4.21 on a link losing nothing - it cannot tell them apart - and a client that merely stops sending books 42.6%. The second column comes from a counter on the one-byte sequence number every audio packet already carries when negotiated (
channel.cpp:207): received and missing counted against a running span maximum, so a late arrival cancels the gap it opened and reordering never books as loss - 0.15% under 52.7% reorder - and a silent client adds nothing because its sequence stream has no gap. It matches the wire to within 0.07 points. That counter is not hypothetical: it runs in this fork's fleet binary today, in the same per-window record as the concealment count, so both quantities are available side by side.Direction needs no protocol to resolve: each end counts the stream it receives, so uplink and downlink are separate measurements by construction. What the pair of counters adds is a distinction a single one cannot make: a struggling client - stalled callbacks, frozen process - produces concealment on the server with zero sequence loss, and from the server that used to be indistinguishable from a lossy uplink. Whether the ctl should follow concealment (the perceptual event, whatever its cause) or sequence loss (the network's actual share of it) is a real design question, posed below.
The design problem, which is the reason for this post. Loss protection has come up before - #781 asked about XOR redundancy, and #3895 covers the raw-audio path this ctl does not touch - but adapting the protection Opus already ships is a different lever. The ctl sits on the sender's encoder; the loss is only visible at the receiver. Two shapes:
Deliberately conservative reading. Whatever the clean-side gain turns out to be worth, it applies to the download direction only in the server-only shape, to OPUS64 only, and only on paths clean enough to sit in the lowest bucket. Anything past a few percent concealment lands back on today's behaviour by design, as does every degenerate case: no completed window, a format change, a silent client. CBR is unchanged, so packet size and cadence do not move, and the jitter buffer is a separate subsystem that is not touched.
Where scrutiny would be most useful:
Everything above is reproducible and the harness is nothing exotic:
opus_customdriven at Jamulus's own OPUS64 settings for the codec numbers, scored with ViSQOL and segmental SNR as labelled; and for the counter, two network namespaces on a veth pair withtc netemshaping one direction at a time while both ends are polled over JSON-RPC. Corrections to the method, or tests worth adding, are more useful here than agreement - particularly on which instrument to trust where they disagree, which no amount of further scoring settles without ears.🤖 This message was written by AI and reviewed by @mcfnord.
All reactions