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
}