Files
go-ts/internal/client/voice.go
2026-01-15 22:06:35 +01:00

189 lines
4.8 KiB
Go

package client
import (
"encoding/binary"
"fmt"
"log"
"go-ts/pkg/protocol"
"gopkg.in/hraban/opus.v2"
)
const (
// TeamSpeak Codecs
CodecOpusVoice = 4
CodecOpusMusic = 5
)
func (c *Client) handleVoice(pkt *protocol.Packet) {
// Only process Opus packets
// Parse Voice Header (Server -> Client)
// VId(2) + CId(2) + Codec(1) + Data
var data []byte = pkt.Data
// Decrypt if Encrypted
if !pkt.Header.FlagUnencrypted() {
if c.Handshake == nil || len(c.Handshake.SharedIV) == 0 {
log.Println("Received encrypted voice packet but no SharedIV available. Dropping.")
return
}
crypto := &protocol.CryptoState{
SharedIV: c.Handshake.SharedIV,
SharedMac: c.Handshake.SharedMac,
GenerationID: 0,
}
// Server->Client = false
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, false)
// Meta for Server->Client: PID(2) + PT(1) = 3 bytes
meta := make([]byte, 3)
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
meta[2] = pkt.Header.Type
var err error
data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
if err != nil {
// Voice decryption failure is common if keys mismatch or packet loss affects counter?
// But EAX doesn't depend on counter state, only PID which is in header.
log.Printf("Voice decryption failed: %v", err)
return
}
}
if len(data) < 6 {
// Ignore empty/too small packets (e.g. silence or end)
return
}
// The first 2 bytes are the sequence ID, not the client ID for the decoder key.
// The client ID is at data[2:4]
// vid := binary.BigEndian.Uint16(data[0:2]) // Sequence ID
vid := binary.BigEndian.Uint16(data[2:4]) // Talking Client ID (CORRECT KEY)
codec := data[4]
voiceData := data[5:]
// Only process Opus packets
if codec != CodecOpusVoice && codec != CodecOpusMusic {
log.Printf("Received non-Opus voice packet (Codec=%d). Ignoring.", codec)
return
}
channels := 1
if codec == CodecOpusMusic {
channels = 2
}
// 1. Get or Create Decoder for this VID
decoder, ok := c.VoiceDecoders[vid]
if !ok {
var err error
decoder, err = opus.NewDecoder(48000, channels)
if err != nil {
log.Printf("Failed to create Opus decoder: %v", err)
return
}
c.VoiceDecoders[vid] = decoder
}
// 2. Decode Opus to PCM
// Max frame size for 120ms at 48kHz is 5760 samples
pcm := make([]int16, 5760*channels)
n, err := decoder.Decode(voiceData, pcm)
if err != nil {
log.Printf("Opus decode error: %v", err)
return
}
pcm = pcm[:n*channels]
// 3. Emit audio event instead of auto-echo
c.emitEvent("audio", map[string]any{
"senderID": vid,
"codec": int(codec),
"pcm": pcm,
"channels": channels,
})
}
// SendVoice sends PCM audio data to the server
// PCM must be 48kHz, 960 samples for 20ms frame (mono)
func (c *Client) SendVoice(pcm []int16) error {
if c.Conn == nil {
return fmt.Errorf("not connected")
}
channels := 1
codec := uint8(CodecOpusVoice)
// Get or Create Encoder
if c.VoiceEncoder == nil {
var err error
app := opus.AppVoIP
encoder, err := opus.NewEncoder(48000, channels, app)
if err != nil {
return fmt.Errorf("failed to create Opus encoder: %w", err)
}
// Optimize Quality
encoder.SetBitrate(64000) // 64 kbps (High Quality for Voice)
encoder.SetComplexity(10) // Max Complexity (Best Quality)
c.VoiceEncoder = encoder
}
// Encode PCM to Opus
encoded := make([]byte, 1024)
nEnc, err := c.VoiceEncoder.Encode(pcm, encoded)
if err != nil {
return fmt.Errorf("opus encode error: %w", err)
}
encoded = encoded[:nEnc]
// Build voice packet (Client -> Server)
// Payload format: [VId(2)] [Codec(1)] [Data...]
voiceData := make([]byte, 2+1+len(encoded))
c.VoicePacketID++ // Increment counter before using it
// Set VId in Payload to be the Sequence Number (not ClientID)
binary.BigEndian.PutUint16(voiceData[0:2], c.VoicePacketID)
voiceData[2] = codec
copy(voiceData[3:], encoded)
pkt := protocol.NewPacket(protocol.PacketTypeVoice, voiceData)
pkt.Header.PacketID = c.VoicePacketID
pkt.Header.ClientID = c.ClientID
// Encrypt voice packet
if c.Handshake != nil && len(c.Handshake.SharedIV) > 0 {
crypto := &protocol.CryptoState{
SharedIV: c.Handshake.SharedIV,
SharedMac: c.Handshake.SharedMac,
GenerationID: 0,
}
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, true)
meta := make([]byte, 5)
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
meta[4] = pkt.Header.Type
encData, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
if err != nil {
return fmt.Errorf("voice encryption failed: %w", err)
}
pkt.Data = encData
copy(pkt.Header.MAC[:], mac)
} else {
if c.Handshake != nil && len(c.Handshake.SharedMac) > 0 {
copy(pkt.Header.MAC[:], c.Handshake.SharedMac)
} else {
pkt.Header.MAC = protocol.HandshakeMac
}
}
return c.Conn.SendPacket(pkt)
}