fix(android): a mute is a pause, not a hole the host tries to conceal
The Android uplink kept advancing `seq` while muted, so the first frame after an unmute looked to the host like loss the width of the mute. The de-jitter reads that as a gap: up to five concealment frames of stale voice, and a seq gap counted in the uplink-health line. Past 600 ms the pump's stale flush resets the chain first and hides it, which is why the usual long mute looks fine — a quick toggle does not. Freeze `seq` while muted, as the desktop uplink already does, so the frame after an unmute continues the chain. `reset_stream` says it plainly: a pause is not loss, and must not conceal or count a gap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -328,17 +328,16 @@ fn encode_loop(
|
||||
}
|
||||
while ring.len() >= frame {
|
||||
// Muted: drop the frame at the last point before it would become an Opus packet —
|
||||
// room audio is never encoded and nothing goes on the wire. `seq` still advances,
|
||||
// because it numbers the captured 10 ms TIMELINE rather than the datagrams: the gap
|
||||
// the host then sees is exactly the audio that never came, which its de-jitter reads
|
||||
// as at most a few concealment frames before the pump's 600 ms stale-gap flush resets
|
||||
// the chain outright — the right reading of a mute. (Encoding silence instead would
|
||||
// keep a pointless uplink and a host-side ring alive for the whole mute.) `peak` is
|
||||
// the loudest sample the UPLINK carried since the last log, so a dropped frame resets
|
||||
// rather than raises it.
|
||||
// room audio is never encoded and nothing goes on the wire. `seq` does NOT advance:
|
||||
// it numbers the datagrams the host de-jitters, and that side reads a seq jump as
|
||||
// loss (conceal + a counted gap) where a mute is a pause. Freezing it means the
|
||||
// frame after an unmute continues the chain, which is what the host's own
|
||||
// `reset_stream` doc calls for and what the desktop uplink does. (Encoding silence
|
||||
// instead would keep a pointless uplink and a host-side ring alive for the whole
|
||||
// mute.) `peak` is the loudest sample the UPLINK carried since the last log, so a
|
||||
// dropped frame resets rather than raises it.
|
||||
if muted.load(Ordering::Relaxed) {
|
||||
ring.drain(..frame);
|
||||
seq = seq.wrapping_add(1);
|
||||
muted_frames += 1;
|
||||
peak = 0.0;
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user