[構想編]VRChatにリアルタイムかつ高品質なDJ&VJを配信する仕組みを実装する

この記事は、 [ライブ配信/ビデオ通話SDK（Agora）を使用したサービスのアイデアを大募集！【PR】V-CUBE Advent Calendar 2021](https://qiita.com/advent-calendar/2021/agora) の9日目の記事です。

最近VRChatにハマっており、VRChat(VRC)上で「リアルタイム性と高ビットレートの両立」を追求したDJ/VJ配信システムがあったら良いなと思っていたところ、ユースケース的にAgoraが一番しっくりきそうでした。今回は、AgoraでDJ/VJ配信システムを実現するにはどうしたらよいか、どんな指標を目指せばよいかを考えてみました。

実装編は [こちら](https://qiita.com/daisukeoda/items/6e0b525701d860bf4dd0) の記事に書いています。

## VRChatでDJ/VJ配信をするとは

VRChat民の方は読み飛ばしてください。

VRChatには、クラブイベント専用のワールドが多く存在しており、定期的に開催されているクラブも多いです。日本でいうと代表的なものは [ゴーストクラブ.コム](https://xn--pckjp4dudxftf.xn--tckwe/) などでしょうか。他にも、個人で運営されている楽しいクラブイベントが多々存在します。

そういったワールドでDJをするVRCのDJや、VRCのVJや、ワールドのモデリングやライティング、空間設計を得意とする人が一定数存在し、VRCクラブ界隈のコミュニティ(?)もそれなりに盛り上がっている空気を感じます。

初心者・玄人関係なくワイガヤしようぜ、みたいなスタンスのクラブもありつつ、技工派なクラブもありつつ。多岐に渡る特色あるクラブワールドが存在し、そこにはインターネット黎明期のような良い意味のカオスを感じます。ちなみに自分もVRCでよくVJ活動をします。

そう。世はまさに、第一次VRChatクラブ時代なのである。

## 一般的な動画コンテンツ配信と、VRCでのDJ/VJ配信の特徴の違い

※あくまで個人的な見解です。

### 一般的な動画コンテンツ

「一般的」とは、Youtubeに上がってるようなUGCのコンテンツや、映画やアニメ・ドラマなどの完パケの2Dコンテンツを指しています。

上記のようなコンテンツは、当たり前ですが動きがあるシーンと無いシーンの差が激しいケースが多いです。動かないシーンは低ビットレートで表現し、激しいシーンは高ビットレートで表現するといった具合でしょうか。

### DJ/VJ配信の特徴

一般的な動画コンテンツとは違って、常に音声と映像に動きがあるケースが多いのではないでしょうか。ただ音声部分はそもそも必要なビットレートが映像と比べてかなり低いので、激しい音楽が流れてもそれなりに良い音質で再生しやすい側面があります。

しかし映像(VJ)の方については、パーティクル表現だったり、ポリゴンフラッシュのように激しい動きが流れる場合が多々あります。そうしたときにたまにあるのが、音声は正常に流れてるけど、「映像だけなんかガクガクしてるなぁ」という違和感です。VRCのクラブイベントの場合、広範囲(例えば箱の壁全面)にVJ映像が流れることもあるので、意外とそういう細かいカクつきが気になることもあるんじゃないかなと個人的には思います。

そういったケースで、安定したフレームレートが出せる&良い音が出る&カクつかずにワールド内で映像を再生できるようにするためには、それなりのビットレートが必要です。

さらにDJ/VJでの配信で重要なのが、打ち上げてからワールドで再生されるまでの遅延です。
特にクラブイベントのシーンでは、オーディエンスの反応を見て曲や映像の雰囲気を変えることが多々あります。そのため、自分(DJ or VJ)が演奏した曲・映像が低遅延でワールドに反映され、オーディエンスの反応を元に素早く演奏にフィードバックできるようになる事って結構大事なんじゃないかなと思いました。

上記の理由で、VRChatのクラブイベントの配信システムでは「レイテンシ」と「スループット(いかにビットレートを出せるか)」の２点が重要なんじゃないかなと考えます。

## 今回のシステムで目指す指標

以下の3つを目指してみます。

1. 「DJ -> VJ -> ワールド内で再生」という配信経路で、End-to-Endで最大5秒以内の遅延。
1. FullHDでの配信
1. VRCへの配信だけに特化した、ミニマルなクライアントの構築

1.の遅延の内訳は以下です。

1. DJからVJへの音声の受け渡しは、[公式ページ](https://vcube-support.zendesk.com/hc/ja/articles/360034562551-%E9%81%85%E5%BB%B6%E7%A7%92%E6%95%B0%E3%81%AF%E3%81%A9%E3%81%AE%E3%81%8F%E3%82%89%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B-)によると1秒以内でできそう
1. VJ側で音声と自身のVJ映像をエンコードして合体させるのに多少時間がかかるかな、というのを加味して、2秒程度
1. ワールドにストリームを打ち上げて、再生できるまでの部分が、[公式ページ](https://vcube-support.zendesk.com/hc/ja/articles/360034562551-%E9%81%85%E5%BB%B6%E7%A7%92%E6%95%B0%E3%81%AF%E3%81%A9%E3%81%AE%E3%81%8F%E3%82%89%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B-)によると2秒以内

1 + 2 + 2 = 最大5秒という内訳です。

また、タイトルに書いてある高品質とは、どれだけ高画質な映像を配信できるかという意味です。今回はとりあえずFullHDを目指してみます。

普通のWebRTCスタックでFullHDの画質を実現するのかかなり厳しいと思いますが、Agora.ioの場合は仕様的に可能そうだったので、頼もしいですね。

## 従来の、DJ/VJ配信のパターン

基本的には、DJがVJに対して遠隔で音声を送信して、VJ側でそれをmixしたものを、配信サーバに打ち上げ、VRChatのワールドで動画として再生する、というパターンが多いと思います。私個人が観測した範囲だと、以下の方法があります。

### 1. SyncRoom等でDJからVJへ音声を渡し、RTMPで配信サーバに打ち上げ

SyncRoomを使う場合超低遅延が実現できますが、ストリームが途切れたり、音質が悪くなる現象が結構な頻度で観測されたので、長時間の配信で使う分には若干の不安が個人的にはあります。おそらくもともと楽器などのオンラインセッションでの利用を想定されていそうなので、数十分以上継続的に安定した配信をしたい場合は向いてないのかもしれませんね。あと、おそらく裏側でシンプルにWebRTCのSFU鯖か何かが動いてると思うので、ユーザ側のネットワークに依存しているのもありそうですね。例えばベストエフォート型の回線で、一時的に帯域が細くなったときにビットレートが足らなくなって音質が悪くなる、といったような感じでしょうか(あくまで想像です)。

SyncRoom以外にも、discordなどで楽曲の音声を受け取るパターンもありそうですが、試したことがないのでなんとも言えない部分があります🙇

### 2. RTMPでDJからVJへ音声を渡し、さらにRTMPで配信サーバに打ち上げ

これはDJ<>VJ間、VJ<>配信サーバ間両方とも、長時間安定した配信ができそうですが、WebRTCに比べてどうしても若干の遅延が発生してしまいます。

### 3. TopazChatでの配信

TopazChatは低遅延(2,3秒)でそれなりのビットレートのストリームをVRCのワールドで再生できる素晴らしいツールです。しかし2Mbpsまで、という制約があります。2Mbpsだと頑張っても720p(もしくは480p) 30fpsが限界といったところでしょうか。

TopazChat
https://booth.pm/ja/items/1752066

## リアルタイムかつ高品質な配信経路を構築してみる

上記を踏まえて、Agora.io を使ったDJとVJの連携部分&ワールドへの配信部分をまるっと実装して、実際に使っていきます。

## 構成

![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73555/66987af1-6982-53a2-2041-92e0f7766afa.png)

図にあるとおり、フロートしては以下のイメージです。

1. DJからVJにAgora経由で音声を渡す
1. VJ側で音声とVJ映像をmix
1. Agoraに打ち上げ
1. VRC側でAgoraのストリームを受け取り



## まとめ

次からは実際に以下のフローで実装しながら、実際にツールを使ってみます。

1. DJがVJに音声を渡す部分(ElectronかWindowsネイティブ)
1. VJが、音声と映像をmixして、Agoraに打ち上げる部分(ElectronかWindowsネイティブ)
1. ワールドで2.を受信して再生する部分(Unity)