diff --git a/client.go b/client.go index 5419c00..9c10107 100644 --- a/client.go +++ b/client.go @@ -43,6 +43,7 @@ type Client struct { lastResync int64 crypt *cryptstate.CryptState codecs []int32 + opus bool udp bool voiceTargets map[uint32]*VoiceTarget @@ -170,6 +171,8 @@ func (client *Client) disconnect(kicked bool) { client.Printf("Disconnected") client.conn.Close() + + client.server.updateCodecVersions(nil) } } @@ -306,7 +309,11 @@ func (client *Client) udpRecvLoop() { case mumbleproto.UDPMessageVoiceCELTAlpha: fallthrough case mumbleproto.UDPMessageVoiceCELTBeta: - kind := buf[0] & 0xe0 + if (client.server.Opus) { + return + } + fallthrough + case mumbleproto.UDPMessageVoiceOpus: target := buf[0] & 0x1f var counter uint8 outbuf := make([]byte, 1024) @@ -315,17 +322,22 @@ func (client *Client) udpRecvLoop() { outgoing := packetdatastream.New(outbuf[1 : 1+(len(outbuf)-1)]) _ = incoming.GetUint32() - for { - counter = incoming.Next8() - incoming.Skip(int(counter & 0x7f)) - if !((counter&0x80) != 0 && incoming.IsValid()) { - break + if kind != mumbleproto.UDPMessageVoiceOpus { + for { + counter = incoming.Next8() + incoming.Skip(int(counter & 0x7f)) + if !((counter&0x80) != 0 && incoming.IsValid()) { + break + } } + } else { + size := int(incoming.GetUint16()) + incoming.Skip(size & 0x1fff) } outgoing.PutUint32(client.Session) outgoing.PutBytes(buf[1 : 1+(len(buf)-1)]) - outbuf[0] = kind + outbuf[0] = buf[0] & 0xe0 // strip target if target != 0x1f { // VoiceTarget client.server.voicebroadcast <- &VoiceBroadcast{ diff --git a/message.go b/message.go index 3607027..6f836e9 100644 --- a/message.go +++ b/message.go @@ -1368,6 +1368,7 @@ func (server *Server) handleUserStatsMessage(client *Client, msg *Message) { } stats.Version = version stats.CeltVersions = target.codecs + stats.Opus = proto.Bool(target.opus) stats.Address = target.tcpaddr.IP } diff --git a/pkg/mumbleproto/types.go b/pkg/mumbleproto/types.go index f1dc23a..d2068ce 100644 --- a/pkg/mumbleproto/types.go +++ b/pkg/mumbleproto/types.go @@ -37,6 +37,7 @@ const ( UDPMessagePing UDPMessageVoiceSpeex UDPMessageVoiceCELTBeta + UDPMessageVoiceOpus ) // Returns the numeric value identifying the message type of msg on the wire. diff --git a/server.go b/server.go index 9862b87..ccfa4e9 100644 --- a/server.go +++ b/server.go @@ -90,6 +90,7 @@ type Server struct { AlphaCodec int32 BetaCodec int32 PreferAlphaCodec bool + Opus bool // Channels Channels map[int]*Channel @@ -529,9 +530,7 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { // Add codecs client.codecs = auth.CeltVersions - if len(client.codecs) == 0 { - server.Printf("Client %i connected without CELT codecs.", client.Session) - } + client.opus = auth.GetOpus() client.state = StateClientAuthenticated server.clientAuthenticated <- client @@ -567,9 +566,21 @@ func (server *Server) finishAuthenticate(client *Client) { // Add the client to the connected list server.clients[client.Session] = client + // Warn clients without CELT support that they might not be able to talk to everyone else. + if len(client.codecs) == 0 { + client.codecs = []int32{CeltCompatBitstream} + server.Printf("Client %i connected without CELT codecs. Faking compat bitstream.", client.Session) + if server.Opus && !client.opus { + client.sendMessage(&mumbleproto.TextMessage{ + Session: []uint32{client.Session}, + Message: proto.String("WARNING: Your client doesn't support the CELT codec, you won't be able to talk to or hear most clients. Please make sure your client was built with CELT support."), + }) + } + } + // First, check whether we need to tell the other connected // clients to switch to a codec so the new guy can actually speak. - server.updateCodecVersions() + server.updateCodecVersions(client) client.sendChannelList() @@ -669,12 +680,24 @@ func (server *Server) finishAuthenticate(client *Client) { client.clientReady <- true } -func (server *Server) updateCodecVersions() { +func (server *Server) updateCodecVersions(connecting *Client) { codecusers := map[int32]int{} - var winner int32 - var count int + var ( + winner int32 + count int + users int + opus int + enableOpus bool + txtMsg *mumbleproto.TextMessage = &mumbleproto.TextMessage{ + Message: proto.String("WARNING: Your client doesn't support the Opus codec the server is switching to, you won't be able to talk or hear anyone. Please upgrade to a client with Opus support."), + } + ) for _, client := range server.clients { + users++ + if client.opus { + opus++ + } for _, codec := range client.codecs { codecusers[codec] += 1 } @@ -697,33 +720,58 @@ func (server *Server) updateCodecVersions() { current = server.BetaCodec } - if winner == current { + enableOpus = users == opus + + if winner != current { + if winner == CeltCompatBitstream { + server.PreferAlphaCodec = true + } else { + server.PreferAlphaCodec = !server.PreferAlphaCodec + } + + if server.PreferAlphaCodec { + server.AlphaCodec = winner + } else { + server.BetaCodec = winner + } + } else if server.Opus == enableOpus { + if connecting != nil && !connecting.opus { + txtMsg.Session = []uint32{connecting.Session} + connecting.sendMessage(txtMsg) + } return } - if winner == CeltCompatBitstream { - server.PreferAlphaCodec = true - } else { - server.PreferAlphaCodec = !server.PreferAlphaCodec - } - - if server.PreferAlphaCodec { - server.AlphaCodec = winner - } else { - server.BetaCodec = winner - } + server.Opus = enableOpus err := server.broadcastProtoMessage(&mumbleproto.CodecVersion{ Alpha: proto.Int32(server.AlphaCodec), Beta: proto.Int32(server.BetaCodec), PreferAlpha: proto.Bool(server.PreferAlphaCodec), + Opus: proto.Bool(server.Opus), }) if err != nil { server.Printf("Unable to broadcast.") return } - server.Printf("CELT codec switch %#x %#x (PreferAlpha %v)", uint32(server.AlphaCodec), uint32(server.BetaCodec), server.PreferAlphaCodec) + if server.Opus { + for _, client := range server.clients { + if !client.opus && client.state == StateClientReady { + txtMsg.Session = []uint32{connecting.Session} + err := client.sendMessage(txtMsg) + if err != nil { + client.Panicf("%v", err) + } + } + } + if connecting != nil && !connecting.opus { + txtMsg.Session = []uint32{connecting.Session} + connecting.sendMessage(txtMsg) + } + } + + server.Printf("CELT codec switch %#x %#x (PreferAlpha %v) (Opus %v)", uint32(server.AlphaCodec), uint32(server.BetaCodec), server.PreferAlphaCodec, server.Opus) return }