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) // Protect shared encoder c.VoiceEncoderMu.Lock() defer c.VoiceEncoderMu.Unlock() // 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) }