Discordの音楽再生botの話 #FM719
はてブロproにしてからほとんど記事を書いてないということを、請求書を見て思い出しました。とほほ。 半年ぶりに書きます。discordrbのvoice botをスムーズにした話。テックブログ風!
DiscordにBGMいれたい
先日、とある配信のためにDiscordでBGMを流す必要がありました。
お知らせです!!@bufferings @m_seki @miwa719 の三人でおしゃべりすることになりました。聴きたい方がいましたら、当日このツイートにURLをメンションするので見てくださいね。
— miwa (@miwa719) 2021年10月7日
日時:10月14日(木) 21:00-22:00
場所:Discord
内容:咳チームがやらないこと、他
方式の候補は二つ
- 音声入力にミキシングする
- 音楽再生サービスのbotを使う
ミキシングする
ミキシングするやつは半年前に効果音で使って実績がある。 マイクからの音声とMacの中の音源を混ぜるやつ。
これ、効果音のときは気づかなかったんだけど、連続する音楽だとうまくいかない。 アプリ側が持っているノイズ抑制の機能のせいなのか、途切れ途切れになってしまう。
音楽再生サービスの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のためにこれを読んだよ!
*1:youtubeやsoundcloudの楽曲を再生するからかなあ
*2:soundcloudに対応しているはずなんだが失敗した
*3:なお調整は10回に一度くらい行われている。なんでそうしたんだろ。