Keep OpenAL jitter playback continuous

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-10 16:07:47 -04:00
committed by Brandon McGinty
parent 5b0879ddaa
commit 99f1f4b9ab
2 changed files with 28 additions and 10 deletions
+16 -10
View File
@@ -51,9 +51,17 @@ type Recorder interface {
const recorderOutgoingSource uint32 = ^uint32(0) const recorderOutgoingSource uint32 = ^uint32(0)
const ( const (
maxBufferSize = 11520 // Max frame size (2880) * bytes per stereo sample (4) maxBufferSize = 11520 // Max frame size (2880) * bytes per stereo sample (4)
jitterMinPackets = 3
jitterMaxPackets = 10
) )
// jitterPlaybackReady holds the initial playout delay only once. Requiring
// the minimum on every packet drains and refills the renderer in bursts.
func jitterPlaybackReady(started bool, buffered int) bool {
return started || buffered >= jitterMinPackets
}
var ( var (
ErrState = errors.New("gumbleopenal: invalid state") ErrState = errors.New("gumbleopenal: invalid state")
ErrMic = errors.New("gumbleopenal: microphone disconnected or misconfigured") ErrMic = errors.New("gumbleopenal: microphone disconnected or misconfigured")
@@ -451,15 +459,10 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
} }
// Jitter buffer: collects incoming packets, reorders by // Jitter buffer: collects incoming packets, reorders by
// sequence number, and releases them in order after a small // sequence number, and releases them after a small initial delay.
// initial delay (3-5 packets / 30-50ms).
const (
jitterMinPackets = 3 // minimum packets before playout starts
jitterMaxPackets = 10 // maximum buffered packets before dropping oldest
)
var jitterBuf []*gumble.AudioPacket var jitterBuf []*gumble.AudioPacket
var jitterNextSeq int64 var jitterNextSeq int64
var jitterInit bool var jitterInit, jitterStarted bool
// insertSorted inserts a packet into the jitter buffer sorted // insertSorted inserts a packet into the jitter buffer sorted
// by sequence number. // by sequence number.
@@ -509,10 +512,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
jitterInit = true jitterInit = true
} }
// Only start playing when we have enough buffered packets // Hold only the initial packets. Once playback starts, drain every
if len(jitterBuf) < jitterMinPackets { // ready packet so the renderer is fed continuously rather than in
// bursts of jitterMinPackets packets.
if !jitterPlaybackReady(jitterStarted, len(jitterBuf)) {
continue continue
} }
jitterStarted = true
// Drain all packets that are ready (in sequence order) // Drain all packets that are ready (in sequence order)
for { for {
@@ -48,6 +48,18 @@ func TestStopSourceWaitsForWorker(t *testing.T) {
<-returned <-returned
} }
func TestJitterPlaybackDelayAppliesOnlyAtStartup(t *testing.T) {
if jitterPlaybackReady(false, jitterMinPackets-1) {
t.Fatal("jitter playback started before initial buffer filled")
}
if !jitterPlaybackReady(false, jitterMinPackets) {
t.Fatal("jitter playback did not start after initial buffer filled")
}
if !jitterPlaybackReady(true, 1) {
t.Fatal("jitter playback paused while refilling after startup")
}
}
func TestRenderRejectsWorkAfterShutdown(t *testing.T) { func TestRenderRejectsWorkAfterShutdown(t *testing.T) {
s := &Stream{renderClosed: true} s := &Stream{renderClosed: true}
called := false called := false