友人にゲーム画面を共有することが多いのですが、SkypeでもDiscordでもZoomでもSteamのブロードキャストでも、低解像度だったり、解像度は取れていてもビットレートの関係で文字すら読めなかったり(画質が悪い)、遅延がそこそこ長かったりします。
配信知識ゼロから、この一週間で勉強したので、諸々まとめです。
この記事はやり方を説明するものではないのであしからず。
#高画質、低遅延配信がしたい!
まあ普通にYoutubeに限定配信してURLを友人に教えれば見る側で手軽に画質変えられるし便利なんですけれど.... なんか自分でやりたくなってしまいました。
ポートの開放ができるなら、RTMPをグローバルIPで外からアクセスさせたり、HLS,MPEG-DASHでCMAF chunkを活用したりと選択肢はあります。
しかし自分の環境下ではポートの開放ができないので、動的に空いてるところを使ってもらうためにブラウザ上でWebRTCを試してみました。
WebRTCのページ共有にngrokを使いましたが、友人側にhtmlを渡してローカルサーバを立ててもらえば不要です。ローカルかhttpsであればいいので、当然Github Pagesとかでもいいです。
ひとまずXSplit Broadcasterを入力として使います。ロゴ入りますが無料でも十分です。
OBS Virtual cameraでもいいですが、XsplitはCPU使用率OBS比で3割くらいで済みます。
今回はSkyway SDKを使わせていただきました。(TURN月500GBまでなら無償で使える)
自分は一対一の共有を想定していたため、TURNサーバの使用のみでSFUはオフにしました。
※テストしたら、自分と友人の場合はTURNサーバオフでも通信できました。
この場合シグナリングサーバとSTUNサーバしか使っていないので、シグナリング月5万回までは制限なしで使えます。最終的にはここも自分で実装予定ですが。
#いきなり問題発生
やけに画質が悪い。
配信ソフトのプレビューは綺麗だし、仮想カメラを他の配信ソフトに入力して見ても綺麗だったので、ブラウザ側の問題と認定。
どうも、デフォルトの帯域がそこまで広くなく、それを超えるものはブラウザ側で自動的に圧縮され、画質が悪くなるようで....恐らく1.5Mbps程度で調整されていました。環境次第?
ブラウザのデフォルトにも、おそらくSkyway SDKのデフォルトにも制限はあるんじゃないかな。
FPS維持優先で、画質が先に悪くなる印象。
##1. 入力ソースの解像度はちゃんと指定しないと圧縮される
getUserMediaでは幅、高さ、フレームレートを指定可能で、理想値を指定したり、最低値最大値の指定ができます。
何も書かず指定だけすると、理想値(ideal)と見なされます。
正直、理想値を下回る分には問題なさそうなので自分の環境で出力し得る最大を入れればいいです。
ソースではとりあえずHD 60fpsで指定してます。
とりあえずこれでプレビューする分には高画質で見れるはずです。
(chrome://media-internals/ で見た感じ、OBS cameraやXsplitの仮想カメラ機能は30fpsまでらしいですが、メディアソースのFPSは60ですし見た目で明らかに60fps対応です。
chromeの画面キャプチャも60fpsは対応してます)
##2. 送るデータの最大ビットレートも指定しないと圧縮される
プレビューは綺麗だけどいざ通信を開始して受信した映像を見ると相変わらずの画質でした。
今度は送信する際になにか制限が掛かっているはずです。
SDPで帯域を指定するには、今回はSkywayのコードを書き換える必要があったり....?
幸いオープンソースなので覗いたところ、javascriptのコードに直に書き込まず、htmlでオプションを追加するだけでした。コードでは14000kbpsにしてあります。まぁ14Mbpsですね。
一応最大ビットレートの指定らしいですが、挙動としては制限というよりもリクエストに過ぎないような気がします。
また、通信途中に帯域変える場合はSDPを弄るのかな? これはまた別の機会に。
#コード
ありふれてますが一応、htmlのコード github
プレビューしやすくするためにオート再生、コントロールは初めからオン。
htmlやjavascriptにはまだ詳しくありませんが、Skywayのリファレンスを見ればほとんどの人が作れるかと。
こいつの問題は、通信しだしてからのメディア変更に対応できないのを無理やり再接続で対応させているということ。
SFU通信の場合などはreplaceStream()が使えるみたいなんですがP2Pだとよくわからず力業です。いろいろ試してみます。
また、共有停止にlocalStream.getVideoTracks()[0].enabled = false; を使うとあくまでミュートでしかなく、元々HD60fpsで送信していた場合、HD60fpsの黒い画面を引き続き送るためビットレート的にはほぼ停止できますが、開封負荷により負荷は下がらない。
ちゃんと停止させる場合はlocalStream.getVideoTracks()[0].stop(); でOK。
今後変わる可能性は大いにあり。
画面共有のほうは共有停止ボタンが出るのでそれ押しても停止できます。
##感想
現在欧州にいますが、東京の友人にTime isを共有して遅延を確認したところ、2秒未満でした
だいぶすごい。しかも電話が同じくらい遅延しているので会話している時は遅延0の感覚です(笑)
加えて、分かりやすく画質方向での改善が大きい。
まあ世の中の画面共有全部これにしたら輻輳の危険性が高まりますがね....
自分も友人もマシンパワーやネットワーク速度を考慮しないで済む環境なのでそこは楽でした。
CMAF chunkによる低遅延HLSでもなんでもそうですが、基本的に低遅延と負荷はトレードオフなのでそれなりにCPU負荷使いますね。特に双方向共有を一つのPCで横に並べて行うと顕著です。
次回はSkyway SDKなしでやってみる感じです。
その後はWebRTCというかP2Pでの画面共有についてもう少し詰めていきます。敢えて遅延があるほうが話しやすい場合もあるので、接続時に遅延を選択できたりすると面白いかも?(受信側でバッファする感じでしょうか)
API周りの理解が皆無で根拠が薄いので、仕様と異なる場合はぜひコメントしてください!
##参考
便利な機能 chrome://webrtc-internals (ローカルリソースなのでリンクは組み込めません)送信ビットレートとかTURNサーバを経由しているどうかなど見れます
Skyway https://webrtc.ecl.ntt.com/
Skyway リファレンス https://webrtc.ecl.ntt.com/api-reference/javascript.html