diff --git a/gumble/gumble/udp15.go b/gumble/gumble/udp15.go index 6919f35..fe8907c 100644 --- a/gumble/gumble/udp15.go +++ b/gumble/gumble/udp15.go @@ -559,27 +559,37 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) { log.Info("UDP15 #%d: ping response, ignoring", pktNum) return } - if msgType != 0x00 { - log.Warn("UDP15 #%d: unknown message type 0x%02x", pktNum, msgType) + + // MumbleUDP.Audio protobuf format (1.5 native). + if msgType == 0x00 { + session, frameNum, opusData, terminator := decodeUDPAudio(plaintext) + c.dispatchOpus15(pktNum, session, int64(frameNum), opusData, terminator) return } - // Parse MumbleUDP.Audio protobuf. - session, frameNum, opusData, terminator := decodeUDPAudio(plaintext) + // Legacy UDPVoiceOpus format: type byte has bits 5-7 = 4. + if (msgType >> 5) == 4 { + c.handleLegacyUDPVoice(pktNum, plaintext) + return + } + log.Warn("UDP15 #%d: unknown message type 0x%02x", pktNum, msgType) +} + +// dispatchOpus15 processes a decoded MumbleUDP.Audio frame and dispatches +// the decoded PCM to audio listeners. +func (c *Client) dispatchOpus15(pktNum uint64, session uint32, frameNum int64, opusData []byte, terminator bool) { if len(opusData) == 0 && !terminator { log.Info("UDP15 #%d: no opus data (session=%d frame=%d), skipping", pktNum, session, frameNum) return } - // Find the user. user := c.Users[session] if user == nil { log.Warn("UDP15 #%d: unknown session %d", pktNum, session) return } - // Get or create decoder. decoder := user.decoder if decoder == nil { codec := c.audioCodec @@ -593,7 +603,6 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) { } if terminator && len(opusData) == 0 { - // Decoder reset on terminator. decoder.Reset() log.Info("UDP15 #%d: terminator for %s, decoder reset", pktNum, user.Name) return @@ -603,9 +612,59 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) { return } - // Detect sequence gaps via frame_number. + c.decodeAndDispatch(pktNum, user, decoder, frameNum, opusData) +} + +// handleLegacyUDPVoice parses the legacy UDPVoice format (type byte 0x80) +// inside a 1.5-decrypted payload. +func (c *Client) handleLegacyUDPVoice(pktNum uint64, data []byte) { + pos := 0 + + // Session varint. + session, n := varint.Decode(data[pos:]) + if n <= 0 { + log.Warn("UDP15 #%d: legacy session varint decode failed", pktNum) + return + } + pos += n + + // Sequence varint. + seq, n := varint.Decode(data[pos:]) + if n <= 0 { + log.Warn("UDP15 #%d: legacy seq varint decode failed", pktNum) + return + } + pos += n + + // Length varint (bit 13 = terminator). + length, n := varint.Decode(data[pos:]) + if n <= 0 { + log.Warn("UDP15 #%d: legacy length varint decode failed", pktNum) + return + } + pos += n + + terminator := (length & 0x2000) != 0 + audioLen := int(length &^ 0x2000) + if audioLen > len(data)-pos { + log.Warn("UDP15 #%d: legacy audio length %d > remaining %d", + pktNum, audioLen, len(data)-pos) + return + } + + opusData := data[pos : pos+audioLen] + + log.Info("UDP15 #%d: legacy voice session=%d seq=%d opus_len=%d term=%v", + pktNum, session, seq, len(opusData), terminator) + + c.dispatchOpus15(pktNum, uint32(session), seq, opusData, terminator) +} + +// decodeAndDispatch decodes an Opus frame and dispatches PCM to audio listeners. +func (c *Client) decodeAndDispatch(pktNum uint64, user *User, decoder AudioDecoder, frameNum int64, opusData []byte) { + // Detect sequence gaps. if user.audioSequenceValid { - gap := int64(frameNum) - user.audioSequence + gap := frameNum - user.audioSequence if gap > 1 && gap < 100 { log.Info("UDP15 #%d: seq gap for %s: %d -> %d (loss=%d), generating PLC", pktNum, user.Name, user.audioSequence, frameNum, gap-1) @@ -621,10 +680,9 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) { return } } - user.audioSequence = int64(frameNum) + user.audioSequence = frameNum user.audioSequenceValid = true - // Decode Opus. pcm, err := decoder.Decode(opusData, AudioMaximumFrameSize) if err != nil { log.Warn("UDP15 #%d: Opus decode failed for %s: %v", pktNum, user.Name, err) @@ -634,12 +692,11 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) { log.Info("UDP15 #%d: Opus OK for %s, pcm_samples=%d", pktNum, user.Name, len(pcm)) - // Dispatch. event := AudioPacket{ Client: c, Sender: user, Target: &VoiceTarget{ID: 0}, - Sequence: int64(frameNum), + Sequence: frameNum, AudioBuffer: AudioBuffer(pcm), } c.dispatchAudio(user, &event)