func (*generator) NewEncoder() gumble.AudioEncoder { e, _ := gopus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, gopus.Voip) e.SetBitrate(gopus.BitrateMaximum) return &Encoder{ e, } }
func init() { var err error opusEncoder, err = gopus.NewEncoder(FrameRate, 1, gopus.Audio) if err != nil { fmt.Println("NewEncoder Error:", err) return } opusEncoder.SetBitrate(OpusBitrate) sequence = 0 timestamp = 0 }
// Connect connects to the server. func (c *Client) Connect() error { if c.state != StateDisconnected { return errors.New("client is already connected") } encoder, err := gopus.NewEncoder(AudioSampleRate, 1, gopus.Voip) if err != nil { return err } encoder.SetBitrate(40000) encoder.SetVbr(false) c.audioSequence = 0 c.audioTarget = nil c.connection, err = tls.DialWithDialer(&c.config.Dialer, "tcp", c.config.Address, &c.config.TLSConfig) if err != nil { return err } c.audioEncoder = encoder c.users = Users{} c.channels = Channels{} c.contextActions = ContextActions{} c.state = StateConnected // Channels and goroutines c.end = make(chan bool) go c.readRoutine() go c.pingRoutine() // Initial packets version := Version{ release: "gumble", os: runtime.GOOS, osVersion: runtime.GOARCH, } version.setSemanticVersion(1, 2, 4) versionPacket := MumbleProto.Version{ Version: &version.version, Release: &version.release, Os: &version.os, OsVersion: &version.osVersion, } authenticationPacket := MumbleProto.Authenticate{ Username: &c.config.Username, Password: &c.config.Password, Opus: proto.Bool(true), Tokens: c.config.Tokens, } c.Send(protoMessage{&versionPacket}) c.Send(protoMessage{&authenticationPacket}) return nil }
// SendPCM will receive on the provied channel encode // received PCM data into Opus then send that to Discordgo func SendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) { // make sure this only runs one instance at a time. mu.Lock() if sendpcm || pcm == nil { mu.Unlock() return } sendpcm = true mu.Unlock() defer func() { sendpcm = false }() var err error opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio) if err != nil { fmt.Println("NewEncoder Error:", err) return } for { // read pcm from chan, exit if channel is closed. recv, ok := <-pcm if !ok { fmt.Println("PCM Channel closed.") return } // try encoding pcm frame with Opus opus, err := opusEncoder.Encode(recv, frameSize, maxBytes) if err != nil { fmt.Println("Encoding Error:", err) return } if v.Ready == false || v.OpusSend == nil { fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.OpusSend) return } // send encoded opus data to the sendOpus channel v.OpusSend <- opus } }
// very simple program that wraps ffmpeg and outputs raw opus data frames // with a uint16 header for each frame with the frame length in bytes func main() { ////////////////////////////////////////////////////////////////////////// // BLOCK : Basic setup and validation ////////////////////////////////////////////////////////////////////////// // If only one argument provided assume it's a filename. if len(os.Args) == 2 { InFile = os.Args[1] } // If reading from a file, verify it exists. if InFile != "pipe:0" { if _, err := os.Stat(InFile); os.IsNotExist(err) { fmt.Println("error: infile does not exist") flag.Usage() return } } // If reading from pipe, make sure pipe is open if InFile == "pipe:0" { fi, err := os.Stdin.Stat() if err != nil { fmt.Println(err) return } if (fi.Mode() & os.ModeCharDevice) == 0 { } else { fmt.Println("error: stdin is not a pipe.") flag.Usage() return } } ////////////////////////////////////////////////////////////////////////// // BLOCK : Create chans, buffers, and encoder for use ////////////////////////////////////////////////////////////////////////// // create an opusEncoder to use OpusEncoder, err = gopus.NewEncoder(FrameRate, Channels, gopus.Audio) if err != nil { fmt.Println("NewEncoder Error:", err) return } // set opus encoding options // OpusEncoder.SetVbr(true) // bool if Bitrate < 1 || Bitrate > 512 { Bitrate = 64 // Set to Discord default } OpusEncoder.SetBitrate(Bitrate * 1000) switch Application { case "voip": OpusEncoder.SetApplication(gopus.Voip) case "audio": OpusEncoder.SetApplication(gopus.Audio) case "lowdelay": OpusEncoder.SetApplication(gopus.RestrictedLowDelay) default: OpusEncoder.SetApplication(gopus.Audio) } OutputChan = make(chan []byte, 10) EncodeChan = make(chan []int16, 10) if RawOutput == false { // Setup the metadata Metadata = MetadataStruct{ Dca: &DCAMetadata{ Version: FormatVersion, Tool: &DCAToolMetadata{ Name: "dca", Version: ProgramVersion, Url: GitHubRepositoryURL, Author: "bwmarrin", }, }, SongInfo: &SongMetadata{}, Origin: &OriginMetadata{}, Opus: &OpusMetadata{ Bitrate: Bitrate * 1000, SampleRate: FrameRate, Application: Application, FrameSize: FrameSize, Channels: Channels, }, Extra: &ExtraMetadata{}, } _ = Metadata // get ffprobe data if InFile != "pipe:0" { ffprobe := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", InFile) ffprobe.Stdout = &CmdBuf err = ffprobe.Start() if err != nil { fmt.Println("RunStart Error:", err) return } err = ffprobe.Wait() if err != nil { fmt.Println("FFprobe Error:", err) return } err = json.Unmarshal(CmdBuf.Bytes(), &FFprobeData) if err != nil { fmt.Println("Erorr unmarshaling the FFprobe JSON:", err) return } bitrateInt, err := strconv.Atoi(FFprobeData.Format.Bitrate) if err != nil { fmt.Println("Could not convert bitrate to int:", err) return } Metadata.SongInfo = &SongMetadata{ Title: FFprobeData.Format.Tags.Title, Artist: FFprobeData.Format.Tags.Artist, Album: FFprobeData.Format.Tags.Album, Genre: FFprobeData.Format.Tags.Genre, Comments: "", // change later? } Metadata.Origin = &OriginMetadata{ Source: "file", Bitrate: bitrateInt, Channels: Channels, Encoding: FFprobeData.Format.FormatLongName, } CmdBuf.Reset() // get cover art cover := exec.Command("ffmpeg", "-loglevel", "0", "-i", InFile, "-f", "singlejpeg", "pipe:1") cover.Stdout = &CmdBuf err = cover.Start() if err != nil { fmt.Println("RunStart Error:", err) return } err = cover.Wait() if err == nil { buf := bytes.NewBufferString(CmdBuf.String()) if CoverFormat == "png" { img, err := jpeg.Decode(buf) if err == nil { // silently drop it, no image err = png.Encode(&PngBuf, img) if err == nil { CoverImage = base64.StdEncoding.EncodeToString(PngBuf.Bytes()) } } } else { CoverImage = base64.StdEncoding.EncodeToString(CmdBuf.Bytes()) } Metadata.SongInfo.Cover = &CoverImage } CmdBuf.Reset() PngBuf.Reset() } else { Metadata.Origin = &OriginMetadata{ Source: "pipe", Channels: Channels, Encoding: "pcm16/s16le", } } } ////////////////////////////////////////////////////////////////////////// // BLOCK : Start reader and writer workers ////////////////////////////////////////////////////////////////////////// wg.Add(1) go reader() wg.Add(1) go encoder() wg.Add(1) go writer() // wait for above goroutines to finish, then exit. wg.Wait() }