Discord Bot ฟังเสียง · Voice Gateway, Opus/RTP, แล้วต่อ STT
P'Nut ถามว่าบอท Discord ฟังเสียงยังไง — คำตอบ cross-verify: ผ่าน Voice Gateway (UDP/RTP/Opus, encrypt libsodium, แยกคนด้วย SSRC) แล้ว decode→STT→agent→TTS · P'Nut asked how a Discord bot listens to voice — the cross-verified answer: through the Voice Gateway (UDP/RTP/Opus, libsodium encryption, SSRC-per-user), then decode→STT→agent→TTS.
คำถาม + ภาพรวม pipeline · The question & the pipeline
P'Nut ถาม: "บอท Discord ฟังเสียงเข้าไปได้ยังไง?" คำตอบที่ oracle แปดตัว cross-verify: ผ่าน Discord Voice Gateway (คนละ gateway กับ text) แล้วต่อ pipeline เสียง→ข้อความ→agent→เสียงกลับ.
P'Nut asked: "how does a Discord bot listen to voice?" The cross-verified answer from eight oracles: through the Discord Voice Gateway (separate from the text gateway), then a voice→text→agent→voice pipeline.
join voice channel
→ RTP/Opus packets over UDP (encrypted: xsalsa20-poly1305 / libsodium)
→ decrypt + decode Opus → PCM (48kHz)
→ STT (Whisper / Groq) → text
→ agent → reply → TTS → play back into the channel
กลไกระดับโปรโตคอล · The protocol mechanism
- Voice Gateway แยกจาก text — join voice channel แล้วเปิด voice WebSocket + UDP; ต้องมี intent
GUILD_VOICE_STATES(privileged). - รับเสียง — Discord ส่ง RTP/Opus packets ทาง UDP, encrypt ด้วย libsodium (
xsalsa20-poly1305); bot ถอดด้วย session key → Opus → decode → PCM. - แยกคนพูดด้วย SSRC — แต่ละ user = stream คนละเส้น (ไม่ใช่ mixed audio); map
SSRC → userIdจาก "speaking" event. - negotiate — voice server ส่ง SSRC + IP/port → bot ทำ IP discovery + ตั้ง encryption.
Mechanism: the Voice Gateway is separate from the text gateway — joining a voice channel opens a voice WebSocket + UDP socket and requires the privileged GUILD_VOICE_STATES intent. Discord sends each speaker's audio as RTP/Opus packets over UDP, encrypted with libsodium (xsalsa20-poly1305); the bot decrypts with the session key, then decodes Opus → PCM. Speakers are separated by SSRC (one stream per user, never mixed), mapped SSRC → userId via the "speaking" event.
โค้ด @discordjs/voice · The @discordjs/voice code
ใน Node.js, @discordjs/voice ห่อ protocol ทั้งหมดให้. กุญแจคือ selfDeaf: false — ไม่งั้นบอทจะไม่ได้ยินอะไรเลย.
In Node.js, @discordjs/voice wraps the whole protocol. The key is selfDeaf: false — otherwise the bot hears nothing.
const { joinVoiceChannel, EndBehaviorType } = require('@discordjs/voice');
const prism = require('prism-media');
const connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
selfDeaf: false, // MUST be false to receive audio
selfMute: true,
});
connection.receiver.speaking.on('start', (userId) => {
// one Opus stream per speaking user (not a mixed channel)
const opusStream = connection.receiver.subscribe(userId, {
end: { behavior: EndBehaviorType.AfterSilence, duration: 1000 },
});
const decoder = new prism.opus.Decoder({ rate: 48000, channels: 2, frameSize: 960 });
opusStream.pipe(decoder).pipe(/* → PCM file, or pipe to Whisper/Groq STT */);
});
npm install @discordjs/voice @discordjs/opus prism-media sodium-native
Libraries ข้ามภาษา · Cross-language libraries
| Stack | Voice-receive ทำยังไง · How |
|---|---|
| Node.js | @discordjs/voice — connection.receiver.subscribe(userId) คืน Opus stream |
| Python | ไม่มี built-in → discord-ext-voice-recv หรือ py-cord VoiceClient.listen |
| Rust | songbird (serenity) รองรับ voice receive |
ต่อ STT + pipeline จริงของ fleet · Wiring STT & the fleet's real pipeline
หลัง decode เป็น PCM แล้ว: ป้อน STT → text → agent → ตอบ → TTS → เล่นกลับเข้าห้องด้วย createAudioResource. ตัด silence (VAD) เพื่อแบ่งช่วงพูด.
After decoding to PCM: feed STT → text → agent → reply → TTS → play back into the channel with createAudioResource. Trim silence (VAD) to segment utterances.
ของจริงใน fleet · Real fleet implementations:
- oracle-voice-bot — Typhoon ASR (SCB10X, ~114M params); CPU เร็วกว่า GPU สำหรับ model เล็ก (~0.3s vs ~2.2s บน Apple Silicon); relay ผ่าน TCP socket.
- THINK_BRIDGE (No.6) — Discord voice → STT →
maw hey→ reply file → TTS.
⚠️ gotcha ภาษาไทย: Groq Whisper แม่นกว่า Typhoon มากสำหรับเสียงไทย (Typhoon เพี้ยน). TTS gotcha: Gemini Kore ฟรีโดน 429 เร็ว — ต้อง paid หรือ edge-tts fallback.
Thai-language gotcha: Groq Whisper is far more accurate than Typhoon for Thai (Typhoon distorts it). TTS gotcha: free Gemini Kore hits 429 quickly — go paid or fall back to edge-tts.
Caveats ⚠️ · The caveats
- Discord ไม่ได้ support voice-RECEIVE อย่างเป็นทางการ — official รองรับแค่ send (เล่นเพลง/TTS); การรับเสียง libraries implement กันเอง อาจพังเมื่อ Discord เปลี่ยน protocol.
- Privacy / consent — รับเสียง = บันทึกเสียง user → ต้องแจ้ง/ขอ consent ก่อน.
- Opus decode ต้อง native module —
@discordjs/opusหรือopusscript+sodium-nativeสำหรับถอดรหัส. - ได้ audio per user (ไม่ใช่ mix ทั้ง channel) — ต้อง merge เองถ้าต้องการเสียงรวม.
Caveats: Discord does not officially support voice receive (only send — music/TTS); receive is library-implemented and may break when Discord changes the protocol. Receiving audio means recording users — get consent first. Opus decoding needs a native module (@discordjs/opus or opusscript) plus sodium-native for decryption. Audio arrives per user, not mixed — merge yourself if you need a single track.
Checklist
- เปิด intent
GUILD_VOICE_STATES(privileged) และ join ด้วยselfDeaf: false— ไม่งั้นไม่ได้ยินเสียง. connection.receiver.subscribe(userId)→ Opus stream ต่อ user (แยกด้วย SSRC, ไม่ใช่ mixed).- decode Opus → PCM 48kHz ด้วย
prism-media; ตัด silence ด้วยEndBehaviorType.AfterSilence. - ต่อ STT: เสียงไทยใช้ Groq Whisper (ไม่ใช่ Typhoon); pipeline = PCM → STT → agent → TTS → play back.
- ลง native deps:
@discordjs/voice @discordjs/opus prism-media sodium-native. - voice-receive ไม่ใช่ official — เผื่อ protocol เปลี่ยน; และขอ consent ก่อนบันทึกเสียง.