Scale Opus bitrate to audio frame duration
This commit is contained in:
committed by
Brandon McGinty
parent
2bdf9a8193
commit
aa38c99aab
@@ -176,7 +176,7 @@ Priority 2: protocol and data correctness
|
||||
terminators, positional data, and volume adjustment. Test real UDP
|
||||
fallback behavior too.
|
||||
|
||||
22. Opus bitrate calculation assumes a 10 ms interval
|
||||
[x] 22. Opus bitrate calculation assumes a 10 ms interval
|
||||
File: gumble/opus/opus.go
|
||||
bitrate is maxDataBytes * 8 * 100. For permitted 20/40/60 ms intervals it
|
||||
is 2x/4x/6x too high. Calculate bits per frame divided by the actual
|
||||
|
||||
+13
-6
@@ -31,6 +31,7 @@ func (*generator) NewEncoder() gumble.AudioEncoder {
|
||||
e, _ := opus.NewEncoder(gumble.AudioSampleRate, VoiceChannels, opus.AppVoIP)
|
||||
return &Encoder{
|
||||
Encoder: e,
|
||||
channels: VoiceChannels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ func NewStereoEncoder() gumble.AudioEncoder {
|
||||
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
|
||||
return &Encoder{
|
||||
Encoder: e,
|
||||
channels: gumble.AudioChannels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,18 +58,15 @@ func (*generator) NewDecoder() gumble.AudioDecoder {
|
||||
// encoder
|
||||
type Encoder struct {
|
||||
*opus.Encoder
|
||||
channels int
|
||||
}
|
||||
|
||||
func (*Encoder) ID() int {
|
||||
return ID
|
||||
}
|
||||
|
||||
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
|
||||
func (e *Encoder) Encode(pcm []int16, frameSamples, maxDataBytes int) ([]byte, error) {
|
||||
bitrate := encoderBitrate(maxDataBytes, frameSamples, e.channels)
|
||||
if bitrate < 8000 {
|
||||
bitrate = 8000 // Opus minimum viable bitrate for voice
|
||||
}
|
||||
@@ -81,6 +80,14 @@ func (e *Encoder) Encode(pcm []int16, _, maxDataBytes int) ([]byte, error) {
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// encoderBitrate converts a per-frame packet budget to bits per second.
|
||||
func encoderBitrate(maxDataBytes, frameSamples, channels int) int {
|
||||
if frameSamples <= 0 || channels <= 0 {
|
||||
return 8000
|
||||
}
|
||||
return maxDataBytes * 8 * gumble.AudioSampleRate * channels / frameSamples
|
||||
}
|
||||
|
||||
func (e *Encoder) Reset() {
|
||||
_ = e.Encoder.Reset()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package opus
|
||||
|
||||
import "testing"
|
||||
|
||||
// Regression: the encoder always assumed ten millisecond frames, causing the
|
||||
// bitrate for 20/40/60 ms packets to be 2/4/6 times their actual budget.
|
||||
func TestEncoderBitrateUsesFrameDuration(t *testing.T) {
|
||||
const budget = 100
|
||||
for _, frameSamples := range []int{480, 960, 1920, 2880} {
|
||||
got := encoderBitrate(budget, frameSamples, 1)
|
||||
want := budget * 8 * 48000 / frameSamples
|
||||
if got != want {
|
||||
t.Fatalf("%d samples: got %d, want %d", frameSamples, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user