diff --git a/Makefile b/Makefile index ced8410..1d65f2b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,13 @@ GOFILES=main.go mumble.go discord.go mumble-discord-bridge: $(GOFILES) - go build -o $@ $(GOFILES) \ No newline at end of file + go build -o $@ $(GOFILES) + +docker-latest: + docker build . + docker push stieneee/mumble-bridge-latest + +clean: + rm mumble-discord-bridge + +.PHONY: all push clean \ No newline at end of file diff --git a/README.md b/README.md index 2a278fb..7f42d01 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Individual Discord servers need to invite the bot before it can connect. Prebuilt binaries are available. ```bash -curl ... +curl -s https://api.github.com/repos/stieneee/mumble-discord-bridge/releases/latest | grep "mumble-discord-bridge" | grep "browser_download_url" | cut -d '"' -f 4 | wget -qi - ``` ### Docker @@ -82,6 +82,8 @@ go build *.go -o mumble-discord-bridge Currently there is an issue opening the discord voice channel. It is a known issue with a dependency of this project. +Audio leveling from Discord needs to be improved. + ## License Distributed under the MIT License. See LICENSE for more information. diff --git a/discord.go b/discord.go index 59c19cc..c609ee6 100644 --- a/discord.go +++ b/discord.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "sync" "time" "github.com/bwmarrin/discordgo" @@ -11,6 +12,16 @@ import ( _ "layeh.com/gumble/opus" ) +type fromDiscord struct { + decoder *gopus.Decoder + pcm chan []int16 + streaming bool +} + +var discordMutex sync.Mutex +var discordMixerMutex sync.Mutex +var fromDiscordMap = make(map[uint32]fromDiscord) + // OnError gets called by dgvoice when an error is encountered. // By default logs to STDERR var OnError = func(str string, err error) { @@ -77,9 +88,8 @@ func discordSendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) { // ReceivePCM will receive on the the Discordgo OpusRecv channel and decode // the opus audio into PCM then send it on the provided channel. -func discordReceivePCM(v *discordgo.VoiceConnection, toMumble chan gumble.AudioBuffer) { +func discordReceivePCM(v *discordgo.VoiceConnection) { var err error - speakers := make(map[uint32]*gopus.Decoder) for { if v.Ready == false || v.OpusRecv == nil { @@ -93,22 +103,82 @@ func discordReceivePCM(v *discordgo.VoiceConnection, toMumble chan gumble.AudioB return } - _, ok = speakers[p.SSRC] + discordMutex.Lock() + _, ok = fromDiscordMap[p.SSRC] + discordMutex.Unlock() if !ok { - speakers[p.SSRC], err = gopus.NewDecoder(48000, 1) + newStream := fromDiscord{} + newStream.pcm = make(chan []int16, 100) + newStream.streaming = false + newStream.decoder, err = gopus.NewDecoder(48000, 1) if err != nil { OnError("error creating opus decoder", err) continue } + discordMutex.Lock() + fromDiscordMap[p.SSRC] = newStream + discordMutex.Unlock() } - p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 960, false) + discordMutex.Lock() + p.PCM, err = fromDiscordMap[p.SSRC].decoder.Decode(p.Opus, 960, false) + discordMutex.Unlock() if err != nil { OnError("Error decoding opus data", err) continue } - toMumble <- p.PCM[0:480] - toMumble <- p.PCM[480:960] + discordMutex.Lock() + fromDiscordMap[p.SSRC].pcm <- p.PCM[0:480] + fromDiscordMap[p.SSRC].pcm <- p.PCM[480:960] + discordMutex.Unlock() + } +} + +func fromDiscordMixer(toMumble chan<- gumble.AudioBuffer) { + ticker := time.NewTicker(10 * time.Millisecond) + sendAudio := false + + for { + <-ticker.C + discordMutex.Lock() + + sendAudio = false + internalMixerArr := make([][]int16, 0) + + // Work through each channel + for i := range fromDiscordMap { + if len(fromDiscordMap[i].pcm) > 0 { + sendAudio = true + if fromDiscordMap[i].streaming == false { + x := fromDiscordMap[i] + x.streaming = true + fromDiscordMap[i] = x + } + + x1 := (<-fromDiscordMap[i].pcm) + internalMixerArr = append(internalMixerArr, x1) + } else { + if fromDiscordMap[i].streaming == true { + x := fromDiscordMap[i] + x.streaming = false + fromDiscordMap[i] = x + } + } + } + + discordMutex.Unlock() + + outBuf := make([]int16, 480) + + for i := 0; i < len(outBuf); i++ { + for j := 0; j < len(internalMixerArr); j++ { + outBuf[i] += (internalMixerArr[j])[i] + } + } + + if sendAudio { + toMumble <- outBuf + } } } diff --git a/.env.example b/example/.env.example similarity index 100% rename from .env.example rename to example/.env.example diff --git a/example/docker-compose.yml.example b/example/docker-compose.yml.example new file mode 100644 index 0000000..8b5369b --- /dev/null +++ b/example/docker-compose.yml.example @@ -0,0 +1,13 @@ +version: "3.8" + +services: + mumble-discord-bridge: + image: stieneee/mumble-discord-bridge + restart: always + environment: + - MUMBLE_ADDRESS= + - MUMBLE_USERNAME= + - MUMBLE_PASSWORD= + - DISCORD_TOKEN= + - DISCORD_GID= + - DISCORD_CID= \ No newline at end of file diff --git a/main.go b/main.go index d874d6d..21ba51a 100644 --- a/main.go +++ b/main.go @@ -86,12 +86,8 @@ func main() { log.Fatalln("missing discord cid") } - // Shared Channels - // Shared channels pass PCM information in 10ms chunks [480]int16 - var toMumble = make(chan gumble.AudioBuffer, 100) - var toDiscord = make(chan []int16, 100) + // MUMBLE Setup - // MUMBLE config := gumble.NewConfig() config.Username = *mumbleUsername config.Password = *mumblePassword @@ -105,14 +101,19 @@ func main() { return } + // Shared Channels + // Shared channels pass PCM information in 10ms chunks [480]int16 + var toMumble = mumble.AudioOutgoing() + var toDiscord = make(chan []int16, 100) + go m.fromMumbleMixer(toDiscord) - go m.sendToMumble(mumble, toMumble) config.AudioListeners.Attach(m) log.Println("Mumble Connected") - // DISCORD + // DISCORD Setup + discord, err := discordgo.New("Bot " + *discordToken) if err != nil { log.Println(err) @@ -134,7 +135,8 @@ func main() { return } - go discordReceivePCM(dgv, toMumble) + go discordReceivePCM(dgv) + go fromDiscordMixer(toMumble) go discordSendPCM(dgv, toDiscord) // Wait for Exit Signal diff --git a/mumble.go b/mumble.go index dd8f509..fb4e917 100644 --- a/mumble.go +++ b/mumble.go @@ -90,32 +90,3 @@ func (m MumbleDuplex) fromMumbleMixer(toDiscord chan []int16) { } } } - -func (m MumbleDuplex) sendToMumble(mumble *gumble.Client, toMumble chan gumble.AudioBuffer) { - streaming := false - ticker := time.NewTicker(10 * time.Millisecond) - - send := mumble.AudioOutgoing() - - for { - <-ticker.C - - if len(toMumble) > 0 { - streaming = true - - pcm := <-toMumble - - // gain up - for i := range pcm { - pcm[i] = int16((float64(pcm[i]) * 8)) - } - - send <- pcm - } else { - if streaming { - streaming = false - } - - } - } -}