From 7e1faba06bc55ffba43ea96295eacb18ef3e2fc7 Mon Sep 17 00:00:00 2001 From: Brandon McGinty Date: Thu, 20 Aug 2026 14:28:07 -0400 Subject: [PATCH] make the outgoing packet interval configurable Add -audio-interval to choose a 10, 20, 40, or 60 ms packet duration. Longer packets cut per-packet overhead on slow or lossy links at the cost of latency. Anything else is rejected at startup rather than silently truncated to 10 ms frames. Step the audio sequence by the frame duration. Sequence numbers are Mumble timestamps in 10 ms units, so a 60 ms packet advances the counter by six. Incrementing by one made the receiver see every packet as arriving far too early. Co-Authored-By: Claude Opus 5 --- gumble/gumble/client.go | 6 +++++- main.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gumble/gumble/client.go b/gumble/gumble/client.go index a80507f..7b05c1a 100644 --- a/gumble/gumble/client.go +++ b/gumble/gumble/client.go @@ -263,11 +263,15 @@ func (c *Client) AudioOutgoing() chan<- AudioBuffer { ch := make(chan AudioBuffer) go func() { var seq int64 + frameStep := int64(c.Config.AudioFrameSize() / AudioDefaultFrameSize) + if frameStep < 1 { + frameStep = 1 + } previous := <-ch for p := range ch { previous.writeAudio(c, seq, false) previous = p - seq = (seq + 1) % math.MaxInt32 + seq = (seq + frameStep) % math.MaxInt32 } if previous != nil { previous.writeAudio(c, seq, true) diff --git a/main.go b/main.go index 734bba5..57381e2 100644 --- a/main.go +++ b/main.go @@ -115,6 +115,7 @@ func main() { serverSet := false usernameSet := false buffers := flag.Int("buffers", 16, "number of audio buffers to use") + audioInterval := flag.Int("audio-interval", 10, "outgoing audio packet duration in ms (10, 20, 40, or 60)") jitterBuffer := flag.Int("jitter-buffer", 40, "incoming per-user audio buffer in ms (0, 20, 40, or 60)") profile := flag.Bool("profile", false, "add http server to serve profiles") noiseSuppressionEnabled := flag.Bool("noise-suppression", false, "enable noise suppression for microphone input") @@ -123,6 +124,10 @@ func main() { logFile := flag.String("logfile", "", "write logs to this file (logging is disabled when omitted)") flag.Parse() + selectedAudioInterval, err := audioIntervalDuration(*audioInterval) + if err != nil { + handle_raw_error(err) + } selectedJitterBuffer, err := jitterBufferDuration(*jitterBuffer) if err != nil { handle_raw_error(err) @@ -219,6 +224,7 @@ func main() { NoiseSuppressor: noise.NewSuppressor(), } b.Config.Buffers = *buffers + b.Config.AudioInterval = selectedAudioInterval b.Config.DisableUDP = *tcpOnly b.Config.IncomingAudioBuffer = selectedJitterBuffer @@ -260,6 +266,18 @@ func main() { handle_error(&b) } +// audioIntervalDuration converts the packet duration requested at startup to +// one of the Opus durations supported by Mumble. +func audioIntervalDuration(milliseconds int) (time.Duration, error) { + interval := time.Duration(milliseconds) * time.Millisecond + switch interval { + case 10 * time.Millisecond, 20 * time.Millisecond, 40 * time.Millisecond, 60 * time.Millisecond: + return interval, nil + default: + return 0, fmt.Errorf("audio interval must be 10, 20, 40, or 60 ms, got %d", milliseconds) + } +} + // jitterBufferDuration converts the requested incoming playout delay to a // supported duration. Zero starts playback without an initial safety buffer. func jitterBufferDuration(milliseconds int) (time.Duration, error) {