Google Stadiaライクなリアルタイム映像配信を試す


TL;DR
Vulkan と WebRTC を使ってリアルタイム映像配信にチャレンジしました。
https://github.com/ogukei/rigel

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/xAg6Vnage4">pic.twitter.com/xAg6Vnage4</a></p>&mdash; 焼き魚 (@ogukei_t) <a href="https://twitter.com/ogukei_t/status/1335023080284934145?ref_src=twsrc%5Etfw">December 5, 2020</a></blockquote>

## 免責事項

筆者はクラウドゲーミング開発の専門家ではないです。

Google Stadiaなどのサービスでは大規模なスケールを可能にするための様々な工夫が施されていますがここでは特に取り扱いません。

## はじめに
こんにちは。[@ogukei_t](https://twitter.com/ogukei_t) です。社内ではiOSアプリを開発しています。

本記事は業務と関係が薄いです。iOS開発からは離れた内容となってしまいました。1年ほど前に社内LTで個人的に発表した内容のまとめになります。

## 何をつくるか

ユーザーがコントローラーを操作するとそれに応じてサーバーが映像を作成し、瞬時にその映像がユーザーの元に届くシステムをつくります。イメージは以下の図のとおりです。

1. ユーザーが操作
2. サーバーが映像を作成
3. ユーザーへ映像を配信

![01-overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189818/64964972-60f8-3057-b73c-e0154efdfe22.png)

## モチベーション

主に以下の3点があります。Google Stadiaの雰囲気をちょっとでも体験したいというのがモチベーションになります。リアルタイム映像配信の面白さを少しでも感じていただければ幸いです。

* 色々な環境でリアルタイム映像をヌルヌル動かしたい
* クラウドゲーミングで使われている技術がとても興味深い
* おま国

### Google Stadiaとは

2019年のGDCでGoogleによって[Google Stadia](https://stadia.google.com/)が発表されました[^stadia-presentation]。Google Stadiaはクラウドゲーミングサービスのひとつで、YouTubeを活用して瞬時にゲームを起動・共有したりモバイル端末からでもAAAタイトルのゲームをプレイできる特徴があります[^stadia-desc]。

![02-stadia.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189818/e7600545-2d02-8330-bfa9-033b0aeb0054.png)
> [YouTube: Google GDC 2019 Gaming Announcement](https://youtu.be/W7hc4R8JAJY?t=480)

[^stadia-presentation]: [YouTube - Google GDC 2019 Gaming Announcement](https://youtu.be/W7hc4R8JAJY?t=480)
[^stadia-desc]: [Engadget - 速報：Google Stadiaは月9.99ドル、日本含まぬ14か国で11月開始。初期タイトル発表](https://japanese.engadget.com/jp-2019-06-06-google-stadia-9-99-14-11.html)

クラウドゲーミングサービスには様々な種類があり、他にはAmazon LunaやProject xCloud, Nvidia GeForce Nowなどがあります。

2020年現在、Google Stadiaはまだ日本では残念ながらサービスが開始されていません。ちなみに筆者はかなり期待[^cantwait]しているのですが、やはり結構ハードルがあるのでしょうか。

[^cantwait]: 当時のTwitterの様子
 https://twitter.com/ogukei_t/status/1108057981604683778?s=20
 https://twitter.com/ogukei_t/status/1108065742392422400?s=20
 https://twitter.com/ogukei_t/status/1108064603047510016?s=20
 https://twitter.com/ogukei_t/status/1108066208589340672?s=20

### 使われている技術（ざっくり）

Google Stadiaのベースになっている技術をざっくり追ってみます。解説についてはGoogle IO 2019で興味深いセッションがありました[^google-io-qiita]。Google Stadiaの主なコンセプトについて解説されていて参考になります。
> [YouTube - Stadia Streaming Tech: A Deep Dive (Google I/O'19)](https://www.youtube.com/watch?v=9Htdhz6Op1I)

冒頭は特に**リアルタイム性**が求められるという話です。例えば一般的な動画配信ではオフラインエンコードが可能でキャッシュをCDNに持つことができます。しかしクラウドゲーミングはユーザーの入力を得て初めて映像をつくり出すため、キャッシュしたいところですがそうはいきません。

![cloud-gaming-diff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189818/f12d2219-a2d8-1438-f525-ca8f99bc6f70.png)

続いてネットワークの課題の話です。**一貫した体験**を大きなスケールであらゆる環境のユーザーへ届けるのは困難を伴います。ユーザーへデータが届くまでにいくつもの地点を経由します。バッファが特に大きい地点があるとそこでパケットが詰まって再送・パケットロスが発生するケースがあります。そのため再送・輻輳制御が重要です[^stadia-analysis]。

[^stadia-analysis]: [Cloud-gaming:Analysis of Google Stadia traffic](https://arxiv.org/abs/2009.09786)

![congestion.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189818/da79357c-fea0-f665-82c2-3dc7d481cf77.png)


[^google-io-qiita]: [Qiita - Stadiaのリアルタイムストリーミングを実現する技術（Google I/O 2019の発表まとめ）](https://qiita.com/kkoiwai/items/584b7a9317afd3ca9406)

## 実装に向けて

リアルタイム性を保つために遅延を最小限にします。TCPによる送受信だけではオーバーヘッドが大きいため大部分のデータの送受信にはUDPを使います。

しかしサーバーからクライアントへの経路において単にUDPのパケットを送りつけるだけではNATを超えられない問題があり、その解決にはNAT traversal, UDP hole punchingといったテクニックが有効です。ただし1から実装するととても大変なため既存プロトコルの Interactive Connectivity Establishment (ICE) を用いるとよいです。

接続が確立すると今度は操作入力や映像といったデータを送受信します。TCPに比べるとUDPはステートレスで高速ですがパケットロスが問題になる場合があります。様々な環境で安定して動作させるためには再送・輻輳制御が重要になります。

ここでWebRTCが登場します。SCTPやRTP(SRTP)を活用することで順序保証や再送・輻輳制御をやってくれます。

上記の理由かは定かではありませんが、Google StadiaはWebRTCを使用しているようです[^shiguredo-ref-stadia][^webrtc-stadia-youtube][^webrtc-stadia]。WebRTCには上記の例以外にもふんだんに技術が詰め込まれています。

さらにゆくゆくはGoogle StadiaはWebTransportを使用する予定があるようです[^webrtc-stadia][^webtransport-stadia]。WebTrasportはQUICを用い到達を保証しながらもリアルタイム双方向通信を実現します[^webtrasport-dev][^webtrasport-blog-0][^webtrasport-blog-1]。本記事ではWebTransportは紹介に留めて実装にはWebRTCを用います。

[^webrtc-stadia]: [WebRTC by Dr Alex - #WebRTC in Google Stadia – Redefining RealTime streaming at scale](http://webrtcbydralex.com/index.php/2020/01/21/webrtc-in-google-stadia-redefining-realtime-streaming-at-scale/)
[^webrtc-stadia-youtube]: [YouTube - Google WebRTC project update & Stadia review](https://youtu.be/avtlQeaxd_I?t=438)
[^webtransport-stadia]: [WebRTC unbundling: the beginning of the end for WebRTC?](https://bloggeek.me/webrtc-unbundling/)
[^webtrasport-dev]: [web.dev - Experimenting with WebTransport](https://web.dev/webtransport/)
[^webtrasport-blog-0]: [WebTransportを用いてブラウザ上からUDP/QUICによるリアルタイム双方向通信を行う](https://sbfl.net/blog/2020/07/31/web-transport-introduction/)
[^webtrasport-blog-1]: [WebTransport と WebCodecs そして Web はどこまで “ゲーム化” するか](https://blog.jxck.io/entries/2019-08-18/webtransport-and-webcodecs.html)

## 既存ライブラリの紹介

国内のネイティブWebRTCクライアントといえば、時雨堂さんの [WebRTC Native Client Momo](https://github.com/shiguredo/momo) が有名です。またWebRTCに関する知見がまとまった資料がMediumで多数公開されています[^shiguredo-ref-stadia][^shiguredo-ref-corp]。

> [Medium - 時雨堂の WebRTC に関する取り組み](https://medium.com/shiguredo/%E6%99%82%E9%9B%A8%E5%A0%82%E3%81%AE-webrtc-%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E5%8F%96%E3%82%8A%E7%B5%84%E3%81%BF-3e116bebd7ce)

[^shiguredo-ref-corp]: [Medium - 時雨堂の WebRTC に関する取り組み](https://medium.com/shiguredo/%E6%99%82%E9%9B%A8%E5%A0%82%E3%81%AE-webrtc-%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E5%8F%96%E3%82%8A%E7%B5%84%E3%81%BF-3e116bebd7ce)

[^shiguredo-ref-stadia]: [Medium - リアルタイム映像配信サーバ開発者からみた STADIA](https://voluntas.medium.com/%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E6%98%A0%E5%83%8F%E9%85%8D%E4%BF%A1%E3%82%B5%E3%83%BC%E3%83%90%E9%96%8B%E7%99%BA%E8%80%85%E3%81%8B%E3%82%89%E3%81%BF%E3%81%9F-stadia-163f660c4db8)

## 実装

主な処理の流れは以下のようになります。入力はユーザーによる、例えばコントローラーやマウスなどの何かしらの操作で、出力はその入力を元にレンダリングされた映像です。

![03-process.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189818/00581aa3-a6d0-da60-2729-aacfbaa11370.png)

それぞれ以下のプロトコル・ライブラリを使用します。

| 機能  | プロトコル・ライブラリ  |
| --- | --- |
| 入力 | WebRTC data channels over SCTP |
| 描画 | Vulkan |
| エンコード | CUDA NVENC |
| 配信 | WebRTC media channels H.264 |

Ubuntu 18.04 LTS上でネイティブアプリケーションとして動きます。

WebRTCの実装はGoogle謹製のChromiumにも搭載されている[libwebrtc](https://webrtc.googlesource.com/src/)を使います。本実装の開発言語はC++14です。最適化がしやすい点とChromium等ライブラリとの互換性が高い点があるためです。

Docker上でビルドして動かせます。描画とエンコードにGPUが必要ですが [Nvidia Container Toolkit](https://github.com/NVIDIA/nvidia-docker) を使うことでコンテナ内部でNvidiaのGPUを利用することができます。

全コードはGitHubに公開しています。
> Rigel
> Stadia-like realtime graphics streaming software using WebRTC and Vulkan
> https://github.com/ogukei/rigel

### 基本的な工夫

以下の2点に重きを置いています。

- コピーを最小限にする
- とにかくシンプルにする

#### コピーを最小限にする
処理の流れは上記でも触れましたが、ユーザーの操作を受信した後はできる限り遅延を少なくして映像をユーザーの元に返したいです。オーバーヘッドをできるだけ少なくする努力をしました。実装上でオーバーヘッドを少なくする代表的な手段のひとつはデータの無駄なコピーを避けることです。

描画はVulkanを使ってGPU上で行います。描画結果はGPUのメモリに保持されますが、この後のエンコードもGPU上で行うため、描画結果をそのまま使うことができます。こうしてCPUとGPU間のデータのやり取りを最小限にすることで高速化を図ります。

具体的にはVulkanの[VK_KHR_external_memory extension](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_external_memory.html)とCUDAの[graphics interoperability functions](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__INTEROP.html)を組み合わせて実現します[^interop-code]。

[^interop-code]: [Vulkan CUDA interopのコード](https://github.com/ogukei/rigel/blob/0e5096d711158f9a40e272586da0e17a7c205e4b/src/render_engine_cuda.cc)

#### とにかくシンプルにする
描画機能はオフスクリーンへの描画だけ必要で、モニターへ実際に表示する必要はありません。Vulkanはheadless renderingが可能です。Swap chainなどの使わない拡張機能を削ぎ落として使用します。

## デモ

映像はFull HD 60fpsに設定しました。奥のデスクトップPC（見えない）から手前のiPhone, iPad, MacBookの3台へ同時に配信しています。iPhoneは4G回線で接続しています。iPadとMacBookはローカルネットワーク内にあります。

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/xAg6Vnage4">pic.twitter.com/xAg6Vnage4</a></p>&mdash; 焼き魚 (@ogukei_t) <a href="https://twitter.com/ogukei_t/status/1335023080284934145?ref_src=twsrc%5Etfw">December 5, 2020</a></blockquote>

ラグを感じさせない程度に反応してくれます。

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/Ng8CTgOypF">pic.twitter.com/Ng8CTgOypF</a></p>&mdash; 焼き魚 (@ogukei_t) <a href="https://twitter.com/ogukei_t/status/1334991131415736321?ref_src=twsrc%5Etfw">December 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

デスクトップPCの動作環境です。

|  | 動作環境 |
| --- | --- |
| OS | Ubuntu 18.04 LTS |
| CPU | Intel Core i7-3930K @ 3.20GHz |
| GPU |  Nvidia RTX 2070 |

## まとめ

<!-- 便益を述べる -->
<!-- 将来の展望 -->

リアルタイム映像配信の基本部分をつくりました。WebRTCはとても強力で偉大な技術が詰め込まれているという認識を新たにしました。

遅延との戦いは果てしないのですが、Webページやスマートフォンアプリでも通用する話かと思います。最適化を行う上での考え方に役立てていきます。