From 989e5a554c208028dafa123517b4e9d23e0d45b7 Mon Sep 17 00:00:00 2001 From: johncranneyscw Date: Mon, 13 Apr 2026 23:25:37 +1000 Subject: [PATCH] fix(audio): copy Opus frame data before passing to consumer DecodeOggOpus passes a slice from bytes.Buffer.Bytes() to the onFrame callback, then calls packet.Reset(). Reset reuses the underlying array, so when the next packet is assembled the previous slice's data is overwritten. In Discord voice playback the frames are sent to a buffered channel (OpusSend, cap 16); by the time opusSender reads a frame, its backing memory has been mutated by subsequent decoder iterations. The resulting Opus frame corruption causes audible artifacts: garbled or whispery audio depending on how much of each frame is overwritten before the voice sender transmits it. Fix: allocate a dedicated copy of each frame before handing it to the callback, decoupling the consumer's data from the reusable buffer. Co-Authored-By: Claude Opus 4.6 --- pkg/audio/ogg.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/audio/ogg.go b/pkg/audio/ogg.go index f0055a574..baff36a05 100644 --- a/pkg/audio/ogg.go +++ b/pkg/audio/ogg.go @@ -40,11 +40,17 @@ func DecodeOggOpus(r io.Reader, onFrame func([]byte) error) error { // If lacing is less than 255, the packet is complete if lacing < 255 { if packet.Len() > 0 { - packetBytes := packet.Bytes() + raw := packet.Bytes() // Ignore Ogg Opus headers - if !bytes.HasPrefix(packetBytes, []byte("OpusHead")) && - !bytes.HasPrefix(packetBytes, []byte("OpusTags")) { - if err := onFrame(packetBytes); err != nil { + if !bytes.HasPrefix(raw, []byte("OpusHead")) && + !bytes.HasPrefix(raw, []byte("OpusTags")) { + // Copy the frame data: packet.Reset() reuses the + // underlying array, so the slice would be + // overwritten by subsequent packets before the + // consumer (e.g. OpusSend channel) reads it. + frame := make([]byte, len(raw)) + copy(frame, raw) + if err := onFrame(frame); err != nil { return err } }