diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index 4c2918e..30f216b 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -470,8 +470,8 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { emptyBufs = append(emptyBufs, reclaimedBufs...) } reclaimLogCounter++ - // Log every 50th reclaim to avoid spam, but always log if state is unusual - if reclaimLogCounter%50 == 1 || processed == 0 || srcState != openal.Playing { + // Log every 50th reclaim, or if state is not Playing + if reclaimLogCounter%50 == 1 || srcState != openal.Playing { log.Debug("reclaim #%d: state=%s processed=%d queued=%d empty=%d", reclaimLogCounter, srcState, processed, queued, len(emptyBufs)) } @@ -516,7 +516,26 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { } p := jitterBuf[0] jitterBuf = jitterBuf[1:] - jitterNextSeq = p.Sequence + 1 + // Frame numbers are Mumble timestamps in 10 ms units. + // Compute the actual step from the PCM sample count so we + // never skip a legitimate gap. + samples := len(p.AudioBuffer) + if samples > gumble.AudioDefaultFrameSize && samples%2 == 0 { + // Stereo: step = stereo frames / base frame size + step := int64((samples / 2) / gumble.AudioDefaultFrameSize) + if step >= 1 { + jitterNextSeq = p.Sequence + step + } else { + jitterNextSeq = p.Sequence + 1 + } + } else { + step := int64(samples / gumble.AudioDefaultFrameSize) + if step >= 1 { + jitterNextSeq = p.Sequence + step + } else { + jitterNextSeq = p.Sequence + 1 + } + } return p } @@ -548,13 +567,25 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { for { pkt := popNext() if pkt == nil { - // A locally dropped packet must not permanently stall the - // jitter buffer waiting for a sequence number that cannot arrive. - if len(jitterBuf) > 0 && jitterBuf[0].Sequence > jitterNextSeq { - log.Debug("jitter: seq gap for %s, skipping from %d to %d (buf=%d)", - e.User.Name, jitterNextSeq, jitterBuf[0].Sequence, len(jitterBuf)) - jitterNextSeq = jitterBuf[0].Sequence - continue + if len(jitterBuf) > 0 { + if jitterBuf[0].Sequence < jitterNextSeq { + // Late or duplicate: discard so it doesn't + // permanently block the drain loop. + log.Debug("jitter: discarding late seq=%d for %s (next=%d buf=%d)", + jitterBuf[0].Sequence, e.User.Name, jitterNextSeq, len(jitterBuf)) + jitterBuf = jitterBuf[1:] + continue + } + if jitterBuf[0].Sequence > jitterNextSeq { + // Gap in sequence: skip ahead so we don't + // wait forever for a lost packet. + log.Debug("jitter: seq gap for %s, skipping from %d to %d (buf=%d)", + e.User.Name, jitterNextSeq, jitterBuf[0].Sequence, len(jitterBuf)) + jitterNextSeq = jitterBuf[0].Sequence + continue + } + // Sequence == jitterNextSeq but popNext returned nil? + // Shouldn't happen; break to avoid infinite loop. } break }