@m_seki の

I like ruby tooから引っ越し

Discordの音楽再生botの話 #FM719

Discordの音楽再生botの話 #FM719

はてブロproにしてからほとんど記事を書いてないということを、請求書を見て思い出しました。とほほ。 半年ぶりに書きます。discordrbのvoice botをスムーズにした話。テックブログ風!

DiscordにBGMいれたい

先日、とある配信のためにDiscordでBGMを流す必要がありました。

方式の候補は二つ

  • 音声入力にミキシングする
  • 音楽再生サービスのbotを使う

ミキシングする

ミキシングするやつは半年前に効果音で使って実績がある。 マイクからの音声とMacの中の音源を混ぜるやつ。

druby.hatenablog.com

これ、効果音のときは気づかなかったんだけど、連続する音楽だとうまくいかない。 アプリ側が持っているノイズ抑制の機能のせいなのか、途切れ途切れになってしまう。

音楽再生サービスのbotを使う→作る

じゃあ、bot使うか、と思って音楽再生サービスのbotを調べたら軒並みサービス停止されてました。*1 これはこまった。

よく考えたら再生したいのは自分のmp3なんだから自分でbot作ればいいんだよな。 じゃあ、作るか。

音声が途切れちゃう

まず、discordrbというgemを読みました。名前がdrbっぽいね。

サンプルに再生botがついていて便利。

https://github.com/shardlab/discordrb/blob/main/examples/voice_send.rb

ちょっと修正して実験。確かにmp3が再生されるけどぶつぶつ途切れてしまう。なんだこれは。 流行りの技術系ブログにも似たような質問に答えがあって「帯域のせい」とのことでした。(ほんとか?)

調べていると、fredboatっていうサービスはまだ生きていて、youtubeの曲を再生させることができました*2。 でもね、このbotは途切れないんですよ。なんだこれ。ということは帯域とかAPIのせいじゃなくてbotの実装がおかしいんじゃないの? これは直すしかない。

処理時間に注目してる

discordrbの音声を再生する部分はここ。play_internalっていうのが再生ループ。

https://github.com/shardlab/discordrb/blob/main/lib/discordrb/voice/voice_bot.rb

Discordは20msecごとにudpでデータを送る約束になってるように見える(斜め読み)。 できるだけ20msecに近い間隔で送信したいと思っているんだろうな、と想像して読んでみる。

自分とはリズムが違うコードなので、読むのしんどい。まずはチラ見で。

あ。Timeのnsec部分だけを使って計算してる...。これじゃ秒跨ぎ問題(0:00:00.999と0:00:01.001のnsecどうしを比べたら負になる問題ね)が起きるだろ!と思って修正したけど特に効果なし。 よく考えたら20msec以内の処理なんだから、秒を跨ぐところに処理がくる頻度は低いよな..。 もっと本格的にダメなんだな。

チラ見じゃなくて、ちゃんと読むか...。

あーー。これ、ループの内側で送信の前処理にかかる時間を計測して、送信後に20msecから前処理の時間分減らした時間でsleepしてる。ループの内側が20msecにしようとしてるんだな。ダメじゃん。これ職場やtoRubyタイマーでも似たようなの見たことあるぞ。ループごとに隙間あくし、遅延も累積する。*3

かかった処理時間に注目して、ループの内側が20msecに調整するのだ、という作戦かな。

送信時刻に注目する

定番のバグなのでささっと直しました。

  • ループの開始前に起点になる時刻を決める。
  • 起点の時刻 + 20msec * n回目、でそのループでの送信したい時刻を計算。
  • 送信したい時刻と現在時刻の差だけsleepする。すぎてたらsleepしない。
  • udbで送信。

送信する時刻を20msecおきにしたいのだ、という作戦です。

sleepのための計算とsleep解除後からudb送信までに僅かな処理があり遅延がある、とも言える。しかしどのループでもだいたい同じくらいの遅延になるので、再生に隙間は現れにくい。

gist925d73ba7aab871ad71597d579864f44

というわけで、ほぼ20msecおきに送信することができるようになって、再生もとてもスムーズになりました。

パッチでフィードバックしたいと思ったんだけど、元のコードの気持ちが読み取れきれず、パッチにできませんでした。とりあえずサンプルコードを送りました。

もし他のvoiceなbotで音声が途切れちゃうのであれば、どんなふうに20msecを刻んでいるのか調べてみるといいかもね。

おまけ

botのパーミションの設定とかはここを真似したよ!

Discordでローカルのmp3をボイチャで流す - ておくれるままに...

BGMのためにこれを読んだよ!

作りながら覚える 3日で作曲入門

作りながら覚える 3日で作曲入門

Amazon

*1:youtubesoundcloudの楽曲を再生するからかなあ

*2:soundcloudに対応しているはずなんだが失敗した

*3:なお調整は10回に一度くらい行われている。なんでそうしたんだろ。