fix: configure Opus encoder bitrate from available bandwidth

Replace SetBitrateToMax() with dynamic bitrate configuration based on
the per-frame byte budget (AudioDataBytes). Previously, the encoder
always targeted maximum bitrate (~510 kbps) and relied on output
truncation when frames exceeded the available bandwidth. This could
produce truncated/invalid Opus frames when the server's max bandwidth
is lower than the encoder's target.

The bitrate is now recalculated on every Encode() call as:
  maxDataBytes * 8 * 100 (bits per second for 10ms frames)

This ensures the encoder produces frames that fit within the byte
budget without truncation.
This commit is contained in:
Brandon McGinty (deepseek)
2026-08-08 19:17:31 -04:00
committed by Brandon McGinty
parent 679127bcf1
commit 5c1cd69d65
+9 -4
View File
@@ -29,9 +29,8 @@ func (*generator) ID() int {
func (*generator) NewEncoder() gumble.AudioEncoder { func (*generator) NewEncoder() gumble.AudioEncoder {
// Force mono for voice transmission // Force mono for voice transmission
e, _ := opus.NewEncoder(gumble.AudioSampleRate, VoiceChannels, opus.AppVoIP) e, _ := opus.NewEncoder(gumble.AudioSampleRate, VoiceChannels, opus.AppVoIP)
_ = e.SetBitrateToMax()
return &Encoder{ return &Encoder{
e, Encoder: e,
} }
} }
@@ -39,9 +38,8 @@ func (*generator) NewEncoder() gumble.AudioEncoder {
func NewStereoEncoder() gumble.AudioEncoder { func NewStereoEncoder() gumble.AudioEncoder {
// Create stereo encoder for file playback // Create stereo encoder for file playback
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio) e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
_ = e.SetBitrateToMax()
return &Encoder{ return &Encoder{
e, Encoder: e,
} }
} }
@@ -65,6 +63,13 @@ func (*Encoder) ID() int {
} }
func (e *Encoder) Encode(pcm []int16, _, maxDataBytes int) ([]byte, error) { func (e *Encoder) Encode(pcm []int16, _, maxDataBytes int) ([]byte, error) {
// Set the encoder bitrate to match the available bandwidth per frame.
// The bitrate is: maxDataBytes * 8 (bits per byte) * 100 (10ms frames per second).
// Opus encodes best when the bitrate target is set properly rather than
// relying on truncation, which can produce incomplete or corrupt frames.
bitrate := maxDataBytes * 8 * 100
_ = e.Encoder.SetBitrate(bitrate)
buf := make([]byte, maxDataBytes) buf := make([]byte, maxDataBytes)
n, err := e.Encoder.Encode(pcm, buf) n, err := e.Encoder.Encode(pcm, buf)
if err != nil { if err != nil {