Fix recording truncation and rate mismatch causing static

The recording pipeline had two bugs that combined to produce
buzzing/static in recorded audio:

1. NormalizeStereoFrame truncated incoming audio frames to
   frameSize*AudioChannels samples. When the Opus decoder produced
   20ms frames (1920 stereo samples) but the recorder used a 10ms
   frameSize (960 samples), half the audio from every packet was
   silently dropped.

2. The recorder's run() loop dequeued one fixed-length frame per
   source per tick. After truncation, the remaining 10ms of each
   20ms packet was gone, so every other tick produced silence.
   This 50 Hz on/off pattern sounded like static.

Fixes:
- NormalizeStereoFrame no longer truncates; it only converts mono
  to stereo and preserves all audio data
- RecordAudioFrame now accepts an explicit stereo flag from callers
  instead of guessing from sample count (which failed for even-length
  mono data like 480-sample mic frames)
- run() accumulates variable-length frames per source and consumes
  them in fixed-size chunks, preserving any leftover for the next tick
This commit is contained in:
Brandon McGinty (deepseek)
2026-08-10 18:34:52 -04:00
committed by Brandon McGinty
parent cd003d7ac4
commit 90f8a1ca5f
3 changed files with 57 additions and 33 deletions
+4 -4
View File
@@ -45,7 +45,7 @@ type FilePlayer interface {
}
type Recorder interface {
RecordAudioFrame(source uint32, samples []int16)
RecordAudioFrame(source uint32, samples []int16, stereo bool)
}
const recorderOutgoingSource uint32 = ^uint32(0)
@@ -742,7 +742,7 @@ func (s *Stream) processAudioPacket(packet *gumble.AudioPacket, user *gumble.Use
}
}
if recorder != nil && recordPtr > 0 {
recorder.RecordAudioFrame(user.Session, recordBuffer[:recordPtr])
recorder.RecordAudioFrame(user.Session, recordBuffer[:recordPtr], true)
}
if len(emptyBufs) == 0 {
@@ -932,7 +932,7 @@ func (s *Stream) sourceRoutine(inputDevice *string, stop chan bool, done chan st
// Send stereo buffer when file is playing
outgoing <- gumble.AudioBuffer(outputBuffer)
if recorder := s.getRecorder(); recorder != nil {
recorder.RecordAudioFrame(recorderOutgoingSource, outputBuffer)
recorder.RecordAudioFrame(recorderOutgoingSource, outputBuffer, true)
}
} else if hasMicInput {
// Send mic when no file is playing. If the microphone is
@@ -949,7 +949,7 @@ func (s *Stream) sourceRoutine(inputDevice *string, stop chan bool, done chan st
}
outgoing <- gumble.AudioBuffer(outBuf)
if recorder := s.getRecorder(); recorder != nil {
recorder.RecordAudioFrame(recorderOutgoingSource, outBuf)
recorder.RecordAudioFrame(recorderOutgoingSource, outBuf, false)
}
}
}