diff --git a/Dockerfile b/Dockerfile index 99d86a05..79c56090 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update #RUN go get github.com/gordonklaus/portaudio #RUN apt-get install portaudio19-dev -y -RUN apt-get install pkg-config libvpx-dev -y +RUN apt-get install pkg-config libvpx-dev libopus-dev libopusfile-dev -y RUN go get github.com/pion/webrtc RUN go get github.com/gorilla/websocket RUN go get github.com/satori/go.uuid diff --git a/main.go b/main.go index 051139e8..642d54c2 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,8 @@ import ( "github.com/gorilla/websocket" pionRTC "github.com/pion/webrtc" uuid "github.com/satori/go.uuid" + + "gopkg.in/hraban/opus.v2" ) const ( @@ -45,6 +47,7 @@ var upgrader = websocket.Upgrader{} // A room stores all the channel for interaction between all webRTCs session and emulator type Room struct { imageChannel chan *image.RGBA + audioChannel chan float32 inputChannel chan int // Done channel is to fire exit event when there is no webRTC session running Done chan struct{} @@ -63,8 +66,6 @@ var serverID = "" var oclient *Client func main() { - rand.Seed(time.Now().UTC().UnixNano()) - flag.Parse() log.Println("Usage: ./game [debug]") if *config.IsDebug { @@ -133,13 +134,15 @@ func initRoom(roomID, gameName string) string { } log.Println("Init new room", roomID) imageChannel := make(chan *image.RGBA, 100) + audioChannel := make(chan float32, ui.SampleRate) inputChannel := make(chan int, 100) // create director - director := ui.NewDirector(roomID, imageChannel, inputChannel) + director := ui.NewDirector(roomID, imageChannel, audioChannel, inputChannel) room := &Room{ imageChannel: imageChannel, + audioChannel: audioChannel, inputChannel: inputChannel, rtcSessions: []*webrtc.WebRTC{}, sessionsLock: &sync.Mutex{}, @@ -148,7 +151,8 @@ func initRoom(roomID, gameName string) string { } rooms[roomID] = room - go room.start() + go room.startVideo() + go room.startAudio() go director.Start([]string{"games/" + gameName}) return roomID @@ -332,7 +336,7 @@ func generateRoomID() string { return roomID } -func (r *Room) start() { +func (r *Room) startVideo() { // fanout Screen for { select { @@ -363,6 +367,68 @@ func (r *Room) start() { } } +func (r *Room) startAudio() { + log.Println("Enter fan audio") + + enc, err := opus.NewEncoder(ui.SampleRate, ui.Channels, opus.AppAudio) + + maxBufferSize := ui.TimeFrame * ui.SampleRate / 1000 + pcm := make([]float32, maxBufferSize) // 640 * 1000 / 16000 == 40 ms + idx := 0 + + if err != nil { + log.Println("[!] Cannot create audio encoder") + return + } + + var count byte = 0 + + // fanout Audio + for { + select { + case <-r.Done: + r.remove() + return + case sample := <-r.audioChannel: + pcm[idx] = sample + idx ++ + if idx == len(pcm) { + data := make([]byte, 640) + + n, err := enc.EncodeFloat32(pcm, data) + + if err != nil { + log.Println("[!] Failed to decode") + continue + } + data = data[:n] + data = append(data, count) + + r.sessionsLock.Lock() + for _, webRTC := range r.rtcSessions { + // Client stopped + if webRTC.IsClosed() { + continue + } + + // encode frame + // fanout audioChannel + if webRTC.IsConnected() { + // NOTE: can block here + webRTC.AudioChannel <- data + } + //isRoomRunning = true + } + r.sessionsLock.Unlock() + + idx = 0 + count = (count + 1) & 0xff + + } + } + } +} + func (r *Room) remove() { log.Println("Closing room", r) r.director.Done <- struct{}{} @@ -543,4 +609,4 @@ func NewOverlordClient(oc *websocket.Conn) *Client { go oclient.listen() return oclient -} +} \ No newline at end of file diff --git a/static/index_ws.html b/static/index_ws.html index aae1e5de..ac08af69 100644 --- a/static/index_ws.html +++ b/static/index_ws.html @@ -10,7 +10,8 @@ textarea { -

+ + Your current room:
You can join a remote game by roomID.
Room ID: @@ -23,7 +24,7 @@ Play as player(1,2): DEBUG = true; + + + + @@ -68,6 +73,8 @@ Play as player(1,2):