From 4b1baec7574e2bc9d4869bbc7599367800601a50 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Tue, 26 Jan 2016 20:22:01 -0600 Subject: [PATCH] Added opusSender func --- voice.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/voice.go b/voice.go index 2946845..db343aa 100644 --- a/voice.go +++ b/voice.go @@ -14,6 +14,7 @@ import ( "encoding/json" "fmt" "net" + "runtime" "strings" "sync" "time" @@ -27,14 +28,16 @@ import ( // A Voice struct holds all data and functions related to Discord Voice support. type Voice struct { - sync.Mutex // future use - Ready bool // If true, voice is ready to send/receive audio - Debug bool // If true, print extra logging - Chan chan struct{} // future use - UDPConn *net.UDPConn // exported for dgvoice, may change. - OP2 *voiceOP2 // exported for dgvoice, may change. + sync.Mutex // future use + Ready bool // If true, voice is ready to send/receive audio + Debug bool // If true, print extra logging + OP2 *voiceOP2 // exported for dgvoice, may change. + Opus chan []byte // Chan for sending opus audio + // FrameRate int // This can be used to set the FrameRate of Opus data + // FrameSize int // This can be used to set the FrameSize of Opus data - wsConn *websocket.Conn + wsConn *websocket.Conn + UDPConn *net.UDPConn // this will become unexported soon. sessionID string token string @@ -174,9 +177,10 @@ func (v *Voice) wsEvent(messageType int, message []byte) { return } - // start udpKeepAlive - go v.udpKeepAlive(5 * time.Second) - // TODO: find a way to check that it fired off okay + // Start the opusSender. + // TODO: Should we allow 48000/960 values to be user defined? + v.Opus = make(chan []byte, 2) + go v.opusSender(v.Opus, 48000, 960) return @@ -347,6 +351,10 @@ func (v *Voice) udpOpen() (err error) { return } + // start udpKeepAlive + go v.udpKeepAlive(5 * time.Second) + // TODO: find a way to check that it fired off okay + v.Ready = true return } @@ -375,3 +383,63 @@ func (v *Voice) udpKeepAlive(i time.Duration) { <-ticker.C } } + +// opusSender will listen on the given channel and send any +// pre-encoded opus audio to Discord. Supposedly. +func (v *Voice) opusSender(opus <-chan []byte, rate, size int) { + + // TODO: Better checking to prevent this from running more than + // one instance at a time. + v.Lock() + if opus == nil { + v.Unlock() + return + } + v.Unlock() + + runtime.LockOSThread() + + var sequence uint16 = 0 + var timestamp uint32 = 0 + udpHeader := make([]byte, 12) + + // build the parts that don't change in the udpHeader + udpHeader[0] = 0x80 + udpHeader[1] = 0x78 + binary.BigEndian.PutUint32(udpHeader[8:], v.OP2.SSRC) + + // start a send loop that loops until buf chan is closed + ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000))) + for { + + // Add sequence and timestamp to udpPacket + binary.BigEndian.PutUint16(udpHeader[2:], sequence) + binary.BigEndian.PutUint32(udpHeader[4:], timestamp) + + // Get data from chan. If chan is closed, return. + recvbuf, ok := <-opus + if !ok { + return + } + + // Combine the UDP Header and the opus data + sendbuf := append(udpHeader, recvbuf...) + + // block here until we're exactly at the right time :) + // Then send rtp audio packet to Discord over UDP + <-ticker.C + v.UDPConn.Write(sendbuf) + + if (sequence) == 0xFFFF { + sequence = 0 + } else { + sequence += 1 + } + + if (timestamp + uint32(size)) >= 0xFFFFFFFF { + timestamp = 0 + } else { + timestamp += uint32(size) + } + } +}