Skip to content

fix(audio): copy Opus frame data in OGG decoder to prevent corruption#2504

Open
jacrify wants to merge 1 commit intosipeed:mainfrom
jacrify:fix/ogg-opus-frame-corruption
Open

fix(audio): copy Opus frame data in OGG decoder to prevent corruption#2504
jacrify wants to merge 1 commit intosipeed:mainfrom
jacrify:fix/ogg-opus-frame-corruption

Conversation

@jacrify
Copy link
Copy Markdown

@jacrify jacrify commented Apr 13, 2026

Description

Fix a buffer reuse bug in DecodeOggOpus that corrupts every Opus frame before Discord's voice sender can transmit it.

packet.Bytes() returns a slice backed by the bytes.Buffer internal array. This slice is passed to the onFrame callback, which sends it to discordgo's OpusSend channel (buffered, cap 16). Immediately after, packet.Reset() resets the buffer length but retains the underlying array. As subsequent packets are assembled, the previous slice's data is silently overwritten while it is still queued in the channel waiting to be read by the opusSender goroutine.

The result is that Discord receives corrupted Opus frames on every send. Depending on how much of each frame is overwritten before transmission, this manifests as garbled or whispery audio in Discord voice channels.

Root cause

packetBytes := packet.Bytes()   // slice into buffer's backing array
onFrame(packetBytes)            // queued in buffered channel
packet.Reset()                  // resets length, keeps same array
// next packet.Write() overwrites the memory packetBytes points to

Fix

Allocate a copy of the frame data before passing it to the callback:

frame := make([]byte, len(raw))
copy(frame, raw)
onFrame(frame)

Type of Change

  • Bug fix

🤖 AI Code Generation

🛠️ Mostly AI-generated — AI identified the bug through systematic diagnosis of Discord voice audio quality issues and wrote the fix; contributor validated and tested on hardware.

Test Environment

  • Hardware: Raspberry Pi 5 (Linux ARM64)
  • OS: Debian (aarch64)
  • Channel: Discord voice
  • TTS Provider: OpenAI (tts-1, gpt-4o-mini-tts)
  • Picoclaw version: built from main @ 2e149f4

Evidence

Before fix: Discord voice TTS consistently produced whispery/garbled audio across all TTS models and encoding approaches (direct opus passthrough, PCM+ffmpeg transcode, WAV+opusenc). The encoding pipeline was verified correct by round-trip decode testing — the issue was isolated to frame delivery.

After fix: Clear, normal-volume TTS playback in Discord voice with direct opus passthrough from OpenAI (zero transcoding, no ffmpeg dependency).

Checklist

  • I have read the CONTRIBUTING guide
  • My code follows the existing style of the project
  • I have tested my changes in a real environment
  • I have reviewed my own code for correctness and security

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 <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@sipeed-bot sipeed-bot bot added type: bug Something isn't working domain: channel labels Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: channel type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants