mirror of
https://github.com/stryan/mumble-discord-bridge.git
synced 2024-11-16 20:15:40 -05:00
add configurable buffer for mumble -> discord stream fixes #20
add silence to -> discord streams as suggested by disocrd docs closes #11
This commit is contained in:
parent
dcc3fd48b4
commit
026cdff797
41
README.md
41
README.md
@ -12,41 +12,46 @@ The binary will also attempt to load .env file located in the working directory.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
Usage of ./mumble-discord-bridge:
|
Usage of ./mumble-discord-bridge:
|
||||||
|
-cpuprofile file
|
||||||
|
write cpu profile to file
|
||||||
|
-debug-level int
|
||||||
|
DEBUG_LEVEL, Discord debug level, optional, (default 1) (default 1)
|
||||||
-discord-cid string
|
-discord-cid string
|
||||||
DISCORD_CID, discord cid, required
|
DISCORD_CID, discord cid, required
|
||||||
-discord-command string
|
-discord-command string
|
||||||
DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord) (default "mumble-discord")
|
DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord) (default "mumble-discord")
|
||||||
-discord-disable-text
|
-discord-disable-text
|
||||||
DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)
|
DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)
|
||||||
-discord-gid string
|
-discord-gid string
|
||||||
DISCORD_GID, discord gid, required
|
DISCORD_GID, discord gid, required
|
||||||
-discord-token string
|
-discord-token string
|
||||||
DISCORD_TOKEN, discord bot token, required
|
DISCORD_TOKEN, discord bot token, required
|
||||||
-mode string
|
-mode string
|
||||||
MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant) (default "constant")
|
MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant) (default "constant")
|
||||||
-mumble-address string
|
-mumble-address string
|
||||||
MUMBLE_ADDRESS, mumble server address, example example.com, required
|
MUMBLE_ADDRESS, mumble server address, example example.com, required
|
||||||
|
-mumble-certificate string
|
||||||
|
MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server
|
||||||
-mumble-channel string
|
-mumble-channel string
|
||||||
MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional
|
MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional
|
||||||
-mumble-disable-text
|
-mumble-disable-text
|
||||||
MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)
|
MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)
|
||||||
-mumble-insecure
|
-mumble-insecure
|
||||||
MUMBLE_INSECURE, mumble insecure, optional
|
MUMBLE_INSECURE, mumble insecure, optional
|
||||||
-mumble-certificate
|
|
||||||
MUMBLE_CERTIFICATE, mumble client certificate, optional
|
|
||||||
-mumble-password string
|
-mumble-password string
|
||||||
MUMBLE_PASSWORD, mumble password, optional
|
MUMBLE_PASSWORD, mumble password, optional
|
||||||
-mumble-port int
|
-mumble-port int
|
||||||
MUMBLE_PORT, mumble port, (default 64738) (default 64738)
|
MUMBLE_PORT, mumble port, (default 64738) (default 64738)
|
||||||
-mumble-username string
|
-mumble-username string
|
||||||
MUMBLE_USERNAME, mumble username, (default: discord) (default "Discord")
|
MUMBLE_USERNAME, mumble username, (default: discord) (default "Discord")
|
||||||
-nice
|
-nice
|
||||||
NICE, whether the bridge should automatically try to 'nice' itself, (default false)
|
NICE, whether the bridge should automatically try to 'nice' itself, (default false)
|
||||||
-debug
|
-to-discord-buffer int
|
||||||
DEBUG_LEVEL, DISCORD debug level, optional (default: 1)
|
TO_DISCORD_BUFFER, Delay buffer from Mumble to Discord to absorb timing issues related to network and hardware quality. (Increments of 10ms) (default 50)
|
||||||
```
|
```
|
||||||
|
|
||||||
The bridge can be run with the follow modes:
|
The bridge can be run with the follow modes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
auto
|
auto
|
||||||
The bridge starts up but does not connect immediately. It can be either manually linked (see below) or will join the voice channels when there's at least one person on each side.
|
The bridge starts up but does not connect immediately. It can be either manually linked (see below) or will join the voice channels when there's at least one person on each side.
|
||||||
|
21
config.go
21
config.go
@ -21,16 +21,17 @@ const (
|
|||||||
//BridgeConfig holds configuration information set at startup
|
//BridgeConfig holds configuration information set at startup
|
||||||
//It should not change during runtime
|
//It should not change during runtime
|
||||||
type BridgeConfig struct {
|
type BridgeConfig struct {
|
||||||
MumbleConfig *gumble.Config
|
MumbleConfig *gumble.Config
|
||||||
MumbleAddr string
|
MumbleAddr string
|
||||||
MumbleInsecure bool
|
MumbleInsecure bool
|
||||||
MumbleCertificate string
|
MumbleCertificate string
|
||||||
MumbleChannel []string
|
MumbleChannel []string
|
||||||
MumbleDisableText bool
|
MumbleDisableText bool
|
||||||
Command string
|
Command string
|
||||||
GID string
|
GID string
|
||||||
CID string
|
CID string
|
||||||
DiscordDisableText bool
|
DiscordStartStreamingCount int
|
||||||
|
DiscordDisableText bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupEnvOrString(key string, defaultVal string) string {
|
func lookupEnvOrString(key string, defaultVal string) string {
|
||||||
|
66
discord.go
66
discord.go
@ -55,6 +55,12 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup,
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate Opus Silence Frame
|
||||||
|
opusSilence := []byte{0xf8, 0xff, 0xfe}
|
||||||
|
for i := 3; i < frameSize; i++ {
|
||||||
|
opusSilence = append(opusSilence, 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(20 * time.Millisecond)
|
ticker := time.NewTicker(20 * time.Millisecond)
|
||||||
|
|
||||||
lastReady := true
|
lastReady := true
|
||||||
@ -63,6 +69,27 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup,
|
|||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
|
internalSend := func(opus []byte) {
|
||||||
|
dd.Bridge.DiscordVoice.RWMutex.RLock()
|
||||||
|
if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil {
|
||||||
|
if lastReady {
|
||||||
|
OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil)
|
||||||
|
readyTimeout = time.AfterFunc(30*time.Second, func() {
|
||||||
|
log.Println("set ready timeout")
|
||||||
|
cancel()
|
||||||
|
})
|
||||||
|
lastReady = false
|
||||||
|
}
|
||||||
|
} else if !lastReady {
|
||||||
|
fmt.Println("Discordgo ready to send opus packets")
|
||||||
|
lastReady = true
|
||||||
|
readyTimeout.Stop()
|
||||||
|
} else {
|
||||||
|
dd.Bridge.DiscordVoice.OpusSend <- opus
|
||||||
|
}
|
||||||
|
dd.Bridge.DiscordVoice.RWMutex.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@ -71,7 +98,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup,
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
if len(pcm) > 1 {
|
if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) {
|
||||||
if !streaming {
|
if !streaming {
|
||||||
log.Println("Debug: Discord start speaking")
|
log.Println("Debug: Discord start speaking")
|
||||||
speakingStart = time.Now()
|
speakingStart = time.Now()
|
||||||
@ -89,28 +116,27 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dd.Bridge.DiscordVoice.RWMutex.RLock()
|
internalSend(opus)
|
||||||
if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil {
|
|
||||||
if lastReady {
|
|
||||||
OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil)
|
|
||||||
readyTimeout = time.AfterFunc(30*time.Second, func() {
|
|
||||||
log.Println("set ready timeout")
|
|
||||||
cancel()
|
|
||||||
})
|
|
||||||
lastReady = false
|
|
||||||
}
|
|
||||||
} else if !lastReady {
|
|
||||||
fmt.Println("Discordgo ready to send opus packets")
|
|
||||||
lastReady = true
|
|
||||||
readyTimeout.Stop()
|
|
||||||
} else {
|
|
||||||
dd.Bridge.DiscordVoice.OpusSend <- opus
|
|
||||||
}
|
|
||||||
dd.Bridge.DiscordVoice.RWMutex.RUnlock()
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if streaming {
|
if streaming {
|
||||||
log.Println("Debug: Discord stop speaking", time.Since(speakingStart).Milliseconds(), "ms")
|
// Check to see if there is a short speaking cycle.
|
||||||
|
// It is possible that short speaking cycle is the result of a short input to mumble (Not a problem). ie a quick tap of push to talk button.
|
||||||
|
// Or when timing delays are introduced via network, hardware or kernel delays (Problem).
|
||||||
|
// The problem delays result in choppy or stuttering sounds, especially when the silence frames are introduced into the opus frames below.
|
||||||
|
// Multiple short cycle delays can result in a Discrod rate limiter being trigger due to of multiple JSON speaking/not-speaking state changes
|
||||||
|
log.Println("Debug: Discord stop speaking", time.Since(speakingStart).Milliseconds(), "ms") // remove before merge
|
||||||
|
if time.Since(speakingStart).Milliseconds() < 100 {
|
||||||
|
log.Println("Warning: Short Mumble to Discord speaking cycle. Consider increaseing the size of the TO_DISCORD_BUFFER")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send silence as suggested by Discord Documentation.
|
||||||
|
// We want to do this after alerting the user of possible short speaking cycles
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
internalSend(opusSilence)
|
||||||
|
<-ticker.C
|
||||||
|
}
|
||||||
|
|
||||||
dd.Bridge.DiscordVoice.Speaking(false)
|
dd.Bridge.DiscordVoice.Speaking(false)
|
||||||
streaming = false
|
streaming = false
|
||||||
}
|
}
|
||||||
|
29
main.go
29
main.go
@ -4,6 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
@ -45,6 +46,7 @@ func main() {
|
|||||||
discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required")
|
discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required")
|
||||||
discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid, required")
|
discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid, required")
|
||||||
discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid, required")
|
discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid, required")
|
||||||
|
discordSendBuffer := flag.Int("to-discord-buffer", lookupEnvOrInt("TO_DISCORD_BUFFER", 50), "TO_DISCORD_BUFFER, Delay buffer from Mumble to Discord to absorb timing issues related to network and hardware quality. (Increments of 10ms)")
|
||||||
discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord)")
|
discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord)")
|
||||||
discordDisableText := flag.Bool("discord-disable-text", lookupEnvOrBool("DISCORD_DISABLE_TEXT", false), "DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)")
|
discordDisableText := flag.Bool("discord-disable-text", lookupEnvOrBool("DISCORD_DISABLE_TEXT", false), "DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)")
|
||||||
mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)")
|
mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)")
|
||||||
@ -95,20 +97,29 @@ func main() {
|
|||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buffer Math
|
||||||
|
if *discordSendBuffer < 10 {
|
||||||
|
*discordSendBuffer = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
var discordStartStreamingCount int = int(math.Round(float64(*discordSendBuffer) / 10.0))
|
||||||
|
log.Println("Discord Streaming Buffer: ", discordStartStreamingCount*10, " ms")
|
||||||
|
|
||||||
// BRIDGE SETUP
|
// BRIDGE SETUP
|
||||||
|
|
||||||
Bridge := &BridgeState{
|
Bridge := &BridgeState{
|
||||||
BridgeConfig: &BridgeConfig{
|
BridgeConfig: &BridgeConfig{
|
||||||
// MumbleConfig: config,
|
// MumbleConfig: config,
|
||||||
MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort),
|
MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort),
|
||||||
MumbleInsecure: *mumbleInsecure,
|
MumbleInsecure: *mumbleInsecure,
|
||||||
MumbleCertificate: *mumbleCertificate,
|
MumbleCertificate: *mumbleCertificate,
|
||||||
MumbleChannel: strings.Split(*mumbleChannel, "/"),
|
MumbleChannel: strings.Split(*mumbleChannel, "/"),
|
||||||
MumbleDisableText: *mumbleDisableText,
|
MumbleDisableText: *mumbleDisableText,
|
||||||
Command: *discordCommand,
|
Command: *discordCommand,
|
||||||
GID: *discordGID,
|
GID: *discordGID,
|
||||||
CID: *discordCID,
|
CID: *discordCID,
|
||||||
DiscordDisableText: *discordDisableText,
|
DiscordStartStreamingCount: discordStartStreamingCount,
|
||||||
|
DiscordDisableText: *discordDisableText,
|
||||||
},
|
},
|
||||||
Connected: false,
|
Connected: false,
|
||||||
DiscordUsers: make(map[string]discordUser),
|
DiscordUsers: make(map[string]discordUser),
|
||||||
|
Loading…
Reference in New Issue
Block a user