Dustin
Dustin

Reputation: 6307

How to binary read an Opus file

In still learning Go, I'm trying to read an Opus file correctly and send it up the Voice channel of Discord (which only supports an Opus codec). Originally using this script I was able to pass a WAV and the script would encode it to Opus and then send it through the channel to Discord. Instead I'd like to send a ready made Opus file. However, instead I hear garbled noise meaning I've done something wrong reading it.

This is a cut down but full working example (minus the email, password, and server id), magic happens in playSong. I have a feeling it has something to do with my audio buffer, but again still learning the tricks.

What would be the correct method to reading an opus file and passing it to a channel? If in need of an opus file to test, find one here. Any help is appreciated. Thanks!

package main

import (
    "encoding/binary"
    "fmt"
    "io"
    "os"
    "os/exec"
    "runtime"
    "strings"

    "github.com/bwmarrin/discordgo"
    "github.com/oleiade/lane"
)

type voiceInstancesMap map[string]*VoiceInstance

var (
    run            *exec.Cmd
    voiceInstances = voiceInstancesMap{}
)

const (
    email     string = ""
    password  string = ""
    serverID  string = ""
    channels  int    = 2     // 1 for mono, 2 for stereo
    frameRate int    = 48000 // audio sampling rate
)

type VoiceInstance struct {
    discord  *discordgo.Session
    queue    *lane.Queue
    serverID string
}

func (vi *VoiceInstance) playSong() {
    f, err := os.Open("./test.opus")
    defer f.Close()

    audiobuf := make([]byte, 1024)

    vi.discord.Voice.Speaking(true)
    defer vi.discord.Voice.Speaking(false)

    for {
        err = binary.Read(f, binary.LittleEndian, &audiobuf)
        if err == io.EOF || err == io.ErrUnexpectedEOF {
            break
        }
        if err != nil {
            fmt.Println("error reading from ffmpeg stdout :", err)
            break
        }

        fmt.Println("Sending audio")
        vi.discord.Voice.OpusSend <- audiobuf
    }
}

func (vi *VoiceInstance) connectVoice() {
    vi.discord, _ = discordgo.New(email, password)

    // Open the websocket and begin listening.
    err := vi.discord.Open()
    if err != nil {
        fmt.Println(err)
    }

    channels, err := vi.discord.GuildChannels(vi.serverID)

    var voiceChannel string
    voiceChannels := []string{}
    for _, channel := range channels {
        if channel.Type == "voice" {
            voiceChannels = append(voiceChannels, channel.ID)
            if strings.Contains(strings.ToLower(channel.Name), "music") && voiceChannel == "" {
                voiceChannel = channel.ID
            }
        }
    }

    if voiceChannel == "" {
        fmt.Println("Selecting first channel")
        voiceChannel = voiceChannels[0]
    }

    err = vi.discord.ChannelVoiceJoin(vi.serverID, voiceChannel, false, true)
    if err != nil {
        fmt.Println(err)
        return
    }

    // Hacky loop to prevent sending on a nil channel.
    // TODO: Find a better way.
    for vi.discord.Voice.Ready == false {
        runtime.Gosched()
    }
}

func main() {
    vi := new(VoiceInstance)
    voiceInstances[serverID] = vi

    fmt.Println("Connecting Voice...")
    vi.serverID = serverID
    vi.queue = lane.NewQueue()
    vi.connectVoice()
    vi.playSong()
}

Upvotes: 2

Views: 1875

Answers (1)

bain
bain

Reputation: 2102

Use https://github.com/hraban/opus. From the docs at that link:

To decode a .opus file (or .ogg with Opus data), or to decode a "Opus stream" (which is a Ogg stream with Opus data), use the Stream interface. It wraps an io.Reader providing the raw stream bytes and returns the decoded Opus data.

A crude example for reading from a .opus file:

f, err := os.Open(fname)
if err != nil {
    ...
}
s, err := opus.NewStream(f)
if err != nil {
    ...
}
defer s.Close()
buf := make([]byte, 16384)
for {
    n, err = s.Read(buf)
    if err == io.EOF {
        break
    } else if err != nil {
        ...
    }
    pcm := buf[:n*channels]

    // send pcm to audio device here, or write to a .wav file

}

Upvotes: 0

Related Questions