DiscordのVCはOpusでpacketやり取りしているので、
そこをうまくつついて遊んだっていう記録。
開発環境としてはdiscordgo v1.28.1です。
BotをVCに接続させるとsession.OpusRecvにRTPの整形済みデータが来るので、
そこからOpus Packetだけを抜き取ってsession.OpusSend<-することでそのままオウム返しできました。
func onReady(discord *discordgo.Session, r *discordgo.Ready) {
log.Println("Discord bot on ready")
vc, _ := discord.ChannelVoiceJoin("***", "***", false, false)
go func() {
for {
p := <-vc.OpusRecv
vc.OpusSend <- p.Opus
}
}()
}
vc.OpusRecvを RTP packetに変換し それをffmpegに流すことで wavで保存しました
func onReady(discord *discordgo.Session, r *discordgo.Ready) {
log.Println("Discord bot on ready")
vc_to, decTo := NewVCSession(discord, "***", "***")
es := NewMemEncodeStream(vc_to, EncodeOpts{}, make(chan error))
go func() {
for {
pcm := decTo.pcm
if es.pipe == nil {
continue
}
n, err := es.pipe.Write(pcm)
if n > 0 && err != nil {
log.Println("Write", n, err)
}
}
}()
}
func NewVCSession(discord *discordgo.Session, guild, channel string) (vc *discordgo.VoiceConnection, d *decoder) {
vc, err := discord.ChannelVoiceJoin(guild, channel, false, false)
if err != nil {
panic(err)
}
d = NewDecoder()
go func() {
for {
s := <-vc.OpusRecv
d.Decode(s)
}
}()
return
}
type decoder struct {
ffmpeg *exec.Cmd
writer *oggwriter.OggWriter
ffmpegOutput io.ReadCloser
pcm []byte
}
func NewDecoder() (d *decoder) {
rawCommand := strings.Split("ffmpeg -loglevel debug -i pipe:0 -c:a pcm_s16le -ac 2 -ar 48000 -b:a 1024k -f wav example.wav", " ")
ffmpeg := exec.Command(rawCommand[0], rawCommand[1:]...)
ffmpegOutput, _ := ffmpeg.StderrPipe()
stdIn, _ := ffmpeg.StdinPipe()
stdOut, _ := ffmpeg.StdoutPipe()
go func() {
buf := make([]byte, 4096)
for {
n, err := ffmpegOutput.Read(buf)
if err != nil {
fmt.Println("ffmpeg Error", err)
return
}
fmt.Print(string(buf[:n]))
}
}()
go func() {
buf := make([]byte, 4096)
for {
n, err := stdOut.Read(buf)
if err != nil {
fmt.Println("Opus Encode Error", err)
return
}
d.pcm = buf[:n]
}
}()
ffmpeg.Start()
writer, _ := oggwriter.NewWith(stdIn, 48000, 2)
return &decoder{
ffmpeg: ffmpeg,
ffmpegOutput: ffmpegOutput,
writer: writer,
}
}
func (d *decoder) Decode(p *discordgo.Packet) {
rtp := &rtp.Packet{
Header: rtp.Header{
Version: 2,
// Taken from Discord voice docs
PayloadType: 0x78,
SequenceNumber: p.Sequence,
Timestamp: p.Timestamp,
SSRC: p.SSRC,
},
Payload: p.Opus,
}
d.writer.WriteRTP(rtp)
}
Opus(.ogg)=>PCM(.wav)は上のまんまでそっからOpusに変換するのにくじけました。
雑にコードを書くとffmpeg -i pipe:0 -c:a pcm_s16le pipe:1 | ffmpeg -i pipe:0 -c:a libopus output.oggをしたが
謎のハウリングを起こしたのであきらめた。
PCMに変換することはできたのでPCM同士の合成後 ファイル(pipeではなく)からOpusに変換することで、
うまく複数:1のVC 接続できるのでは?と思った。