自宅 RTMP リレーサーバで IRL 外配信システムを構築するには

この記事は以下の人向け：
- 回線に対して頑健性の高い外配信システムを**安価で**構築したい
- 自宅に RTMP サーバとして運用可能な PC (Windows, Mac, Linux) がある
- ポートフォワーディング可能なネットワークがある
- ある程度ネットワーク構築、サーバ構築などの作業に慣れている
# 概要
外配信では回線の不安定性が重要な問題となる。性質上、どうしてもモバイル通信になるため、単純な構成ではエリア切り替え、電波の弱い場所などで通信が途切れ、多くの場合画質や途切れ途切れの絵になることのみならず、配信自体が終了/開始を繰り返し、視聴体験が非常に悪い。

その問題にアプローチする良い方法として、複数回線を切替可能なモバイルルータや、SRT という UDP ベースの低遅延な映像伝送プロトコルと専用のエンコーダ機材を組み合わせる構成が考えられている（例：[BELABOX](https://github.com/BELABOX/tutorial/wiki/)）。

しかしながら、それぞれの機材は比較的高価であり複雑な構成であるため持ち運びにも工夫が必要である。本記事では比較的手軽に扱える機材と広く使われている RTMP プロトコルを用いて、自宅サーバを介すことで通信路に冗長性をもたせた上、回線強度を監視して適切に映像シーンを切り替えることで、高価な機材を使った場合までは至らずとも、安定性のある外配信システムの構築を解説する。
![スクリーンショット 2024-01-07 5.08.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/93f7879d-9d62-c944-e970-88e0b8c82b21.png)

# 構成
このシステムの概要図を以下に示す：
![RTMP_STREAM.drawio (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/04b2ee4b-205a-6937-3037-8dfc4c8ff1e5.png)



撮影機材としてスマートフォンや GoPro を使うことを想定する。スマートフォンの場合は特定のサーバに対してストリーミングできるアプリケーションがいくつかあり、GoPro には直接本体からネットワーク経由で映像を送信する機能がある（GoPro Hero 7 Black 以降）。

このシステムは自宅に PC を常駐させる必要がある。PC 上で RTMP のリレーサーバを立ち上げ、まず利用者はリレーサーバに対して映像を送信する。リレーサーバは映像を受け取り、OBSなどの配信ソフトウェアへの映像送信を待ち受ける。配信ソフトウェア上でリレーサーバの映像をソースに設定し、そこから Twitch など配信プラットフォームに配信することで、撮影機材からの映像が途絶えても、配信自体は途絶えない。

また、リレーサーバ上の映像の状態を監視することで、映像の状態に応じて配信ソフトウェア上のシーンを切り替えることが可能になる。例えば、映像が途切れてしまったら、「通信復活中…」などのシーンに切り替え、映像が復活しだい元のシーンに戻すというやり方だ。監視には [Loopy SRT Monitor](https://github.com/loopy750/SRT-Stats-Monitor) というソフトウェアを利用する。名前から SRT プロトコル専用に思えるが、RTMP サーバの監視にも対応している。この Loopy SRT Monitor から `obs-websocket-http` という OBS を外部から操作できるソフトウェアにシーン切り替え命令を送り、シーン切り替えを可能にする。

映像を直接 OBS に送信したいところだが、RTMPというプロトコルは PUSH と PULL の２つにモードが別れており、**それぞれ送信専用、受信専用の指定のみ受け付ける**ため、まず PUSH された映像を受け取り、それを PULL するためのサーバが必要になる。
出典: [RTMP送信できるビデオカメラ(NX80)からOBS Studioに入力してみる](https://do-gugan.com/blog/archives/2021/08/rtmp.html)

https://do-gugan.com/blog/archives/2021/08/rtmp.html

将来的に GoPro や OBS などに RTMP サーバの機能が実装されることがあれば、リレーサーバを外部的に立てる必要はなくなるが… RTMP 自体が古いプロトコルであるため、新規でその機能が追加されるのを期待することは厳しい気もする。(SRT というよい次世代プロトコルがある）

自宅サーバ部分をクラウド上のインスタンスに構築することもできるが、長時間配信の場合は通信量が多くなり、料金が増える。その場合は SRT 通信で BELABOX CLOUD などを利用したほうが結果的に良いと思う。

# 構築例
本章に以下の機材/設定の場合の構築例を示す。

- 撮影機材: GoPro Hero 8 Black
- 自宅 PC: Mac Mini, MacOS 13, Apple M1
- 配信ソフトウェア: OBS (Open Broadcaster Software)
- RTMPリレーサーバ: [tiangolo/nginx-rtmp-docker](https://github.com/tiangolo/nginx-rtmp-docker) (Docker 導入前提）

## RTMP リレーサーバの設定
設定ファイルを用意する。

```nginx.conf
worker_processes auto;
rtmp_auto_push on;

rtmp {
    server {
        listen 1935;
        listen [::]:1935 ipv6only=on;
        ping 3s;
        ping_timeout 15s;
        timeout 15s;
 
        application live {
            live on;
            record off;
            push rtmp://127.0.0.1/streamout;
        }
        application streamout {
            live on;
            record off;
        }
    }
}

http {
    server {
        listen 80;
	    location /stat {
            rtmp_stat all;
        }
    }
}
```

ここでは使用するポート番号（RTMP デフォルトは 1935) と、RTMP PUSH 先のエンドポイント（例では `/live`）、RTMP PULL 先のエンドポイント（例では `/streamout` )、また通信状態を HTTP プロトコルで表示するための設定を行っている（例では `/stat` で通信統計を表示） `/live` で受け取った映像を `127.0.0.1` (つまりローカルマシン）の `/streamout` に push 指定することで、`streamout` から同じ映像を OBS で pull できるようになる。

また、リレーサーバを起動しやすいように `docker-compose.yml` を用意する。
```docker-compose.yml
version: '3'

services:
  nginx-rtmp:
    platform: linux/amd64
    image: tiangolo/nginx-rtmp
    ports:
      - "1935:1935"
      - "80:80"
    restart: always
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
```

設定後、CUI で

```shell
$ docker compose up
```

のようにリレーサーバを起動すれば、外側からは
`rtmp://{自宅ネットワークのIPアドレス}:1935/live/{任意のstreamkey}`
に向けて映像を RTMP PUSH で送信でき、内側からは
`rtmp://127.0.01/streamout/{任意のstreamkey}`
で RTMP PULL で映像を受信できる。
`http://127.0.0.1/stats`
でストリームの統計情報を取得することもできる。

## ポートフォワーディング設定
ネットワークの外側から特定のポートに対する通信を受け取ったとき、ネットワークの内側の特定のマシンの特定ポートにその通信を転送する設定を行う。これをポートフォワーディングという。ここでは RTMP で使う 1935 ポートをネットワーク内部の PC (Mac) の 1935 ポートに転送する設定を行う。

設定方法は使用するルータによって変わるが、基本的にはルータに用意されている GUI で設定できる。ルータによってはポート変換、ポート開放などの名前の違いがあるが。特定のポートを特定のIPを持つマシンの特定ポートに転送する設定の項目を探せばよい。

以下の例は、外部 IP の 1935 ポート宛の通信を、自宅サーバを設置したマシンのローカル IP `192.1６8.11.3` の 1935 ポートに転送している。
![スクリーンショット 2024-01-07 17.25.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/55bf84df-cc62-7e6c-f80b-f75fc2e4dae6.png)


また、ポートの設定はセキュリティリスクにもなるため、一時的な設定にするなど、慎重に検討する必要がある。そのためルータや回線やネットワークの構成によっては使用できるポートが制限されているので、ポートフォワーディングできない可能性があることには注意する。その場合は別でネットワーク構成を変更するなどの工夫をする必要がある（筆者宅では単純にはフォワーディングできないネットワークだったので対策が必要だった。今後記事化したい）。

## OBS の設定
配信ソフトウェアの設定を行う。ここでは OBS Version 30.0.2 (64bit) 版を使う。

### WebSocketの設定
通信断絶時などのシーン切り替えを可能にするための WebSocket サーバを設定する。OBS 28 以降では `OBS WebSocket 5.x` というファーストパーティの組み込みサーバが使える。メニューのツールから「WebSocket サーバ設定」を選び、設定を進める。

「![スクリーンショット 2024-01-07 17.52.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/cb185e30-879e-84f7-2101-70b0fa89fbd1.png)


![スクリーンショット 2024-01-07 17.53.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/cd4e7a99-7aa5-3818-904e-a8a496b8623d.png)
ここでは、ポートを4455,認証は無し（パスワード認証をつけることが望ましいが、動作確認のため）


### シーンの設定
シーンは外配信用にシーンコレクションで作るとスッキリしてわかりやすいが、お好みで設定する。
![スクリーンショット 2024-01-07 17.44.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/054aa97b-9763-223e-82ef-8364673a26cb.png)
OBS では、以下のシーンを最低限用意する。
- 外配信のメイン画面（ここでは MAIN とする）
- 回線断絶時の画面（ここでは FAIL とする）

回線速度が低いときや待受け画面なども設定できるがここでは省略する。

#### MAIN
メイン画面では、メディアソース（環境によっては [VLC Media Player](https://www.videolan.org/vlc/) をインストールする必要があるかもしれない）を追加し、

![スクリーンショット 2024-01-07 18.07.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/48657a84-59a3-f751-ada5-009f27ebbb7b.png)

以下のように設定を行って RTMP サーバからの映像を埋め込む。
![スクリーンショット 2024-01-07 17.59.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/9157ba84-826c-4779-eb89-f7cb2f70bc18.png)
ここでは RTMP サーバは OBS が起動しているマシンと同じ場所で起動しているので、ローカルアドレス `127.0.0.1` を指定し、エンドポイント `/streamout` を指定する。エンドポイント後のパラメータ `/a` はストリームキーであり、任意に設定可能なストリームごとに一意な値である。ここでは仮に `a` としている。

#### FAIL
通信途絶時のシーンは自由に設定する。復旧をしていることを表すメッセージでも良いし、過去クリップ集でもよい。

一例：（復旧メッセージ、踊っているバンジョーと猫、過去の配信アーカイブ、クリップ、現在地図、通信速度など）
![スクリーンショット 2024-01-07 5.05.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/42c4b38c-ab46-9999-996e-0ad537648fe7.png)

## GoPro の設定
RTMP で映像を送信できる撮影機材であればなんでもよいが、ここでは GoPro Hero 8 black を使う。スマートフォンの配信アプリケーションでも可能である。 GoPro の場合はスマートフォンアプリの「GoPro Quick」と連携し、RTMP 送信設定を行うことができる。
![IMG_4684.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/f9e68888-fe41-276c-d773-47a4e506040d.png)

![IMG_4685.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/548b7378-706b-d049-0f18-74e402a81019.png)
赤線の部分は自宅サーバの[外部 IP](https://www.cman.jp/network/support/go_access.cgi) を入力し、エンドポイントとして `/live` を指定する。ストリームキーのパラメータはOBSで設定したもの `a` に合わせる。

ここまでの設定でライブストリームを開始すれば、自宅 PC の RTMP リレーサーバに映像が届き、OBS 側でそれを受信して、MAIN シーンに映像/音声が映し出されるはずである。
通信が途切れたときは、再度同じように配信を行えば、自動的に再接続される。
GoPro 組み込みの Twitch 連携機能で直接配信していると、配信自体が途切れることになるが、このシステムを利用すれば映像は途切れても配信自体は途切れることがない。

### GoPro Experimental Firmware
ここでは詳細を省くが、[GoPro Experimental Firmware](https://gopro.com/en/us/info/gopro-labs) を導入することで、QRコードを GoPro 本体に読み込ませてライブストリームを開始することができる。スマートフォンのアプリへの連携を介す必要がなくなるため、再接続などをスムーズに行うことができる。

https://gopro.com/en/us/info/gopro-labs


## obs-websocket-http の設定
OBS に対して外部から操作信号を送るためのソフトウェアを設定する。[ここ](https://github.com/IRLToolkit/obs-websocket-http/releases/tag/v2) から最新の `obs-websocket-http` を対応 OS ごとに手に入れる。
MacOS では以下のように実行権限を変更し、
```
$ chmod +x ./obs-websocket-http-v2-macOS
```

以下のように起動できる。
```
$ ./obs-websocket-http-v2-macOS
```
OBSが起動されていて、設定が正常にできていれば、以下のように待ち受けが始まる。ポート番号の違いなどが多いので注意する。

```
$ ./obs-websocket-http-v2-macOS
INFO:root:HTTP server will start without authentication.
INFO:root:CORS Domains Accepted: *
INFO:root:HTTP Server Running: 0.0.0.0:4456
INFO:root:Connecting to obs-websocket: ws://127.0.0.1:4455
INFO:root:Connected to obs-websocket.
======== Running on http://0.0.0.0:4456 ========
(Press CTRL+C to quit)
```

OBS 側の WebSocket 設定でも接続を確認できる。
![スクリーンショット 2024-01-07 17.54.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205151/27ab0129-d5d2-028c-894f-78111d31ebf7.png)


## Loopy SRT Monitor の設定
通信状況によってシーンを切り替えるための監視ソフトウェアを設定する。
[リリース](https://github.com/loopy750/SRT-Stats-Monitor/releases/tag/v1.1.2)からそれぞれの環境に適したバージョンを手に入れ、 `config.ini` を設定する。

以下に重要な設定項目のみを抜き出して示す。全体ファイルは[ここ](https://gist.github.com/azumag/3624a3014445f291afbf9a4b470abe0a)


```config.ini

[config]
SceneOK=Main
SceneFail=FAIL
WebSocketAddress=127.0.0.1:4455
WebSocketConnection=obs-websocket-http

[obs-websocket-http]
HTTPBindAddress=127.0.0.1
HTTPBindPort=4456
HTTPAuthKey=
HTTPCommunication=native

[nginx-rtmp-module]
RTMP1Enabled=true
RTMP2Enabled=false
RTMPServerIP=127.0.0.1
RTMPServerPort=80
RTMPServerStats=stat
RTMPServerKeepAlive=Default
RTMPServerPublisher1=streamout
RTMPServerPublisher2=none
RTMPBitrateLow1=1000
RTMPBitrateLow2=1000
RTMPBitrateFail1=400
RTMPBitrateFail2=400

[http]
bind_to_address = 0.0.0.0
bind_to_port = 4456
cors_domains = *
authentication_key = 

[obsws]
#obs-websocket v5 uses port 4455 by default
ws_url = ws://127.0.0.1:4455
```

これらを適切に設定し、OBS　と obs-websocket-http が起動されていれば、以下のように
```
$ ./loopy_srt_monitor
```
として起動でき、RTMP リレーサーバの映像状況によってシーンが自動に切り替わるはずである。

## 外出先から配信開始/終了操作を行うには
OBSは自宅PC内で起動しているので、外出先から配信を開始/終了するにはリモート接続する必要がある。

### Chrome Remote Desktop
Chrome Remote Desktop を設定することで、スマートフォン/ノートPCなどから手軽にリモートアクセスすることができる。筆者はこれを利用。

https://remotedesktop.google.com/?pli=1

### OBS tablet Remote
OBS tablet Remote を設定することで、http サーバを介して OBS WebSocket 経由で　OBS 本体を操作できる。ただし、設定は http アクセス用のポートフォワーディングも必要となるのですこし複雑になる。筆者は試していない…

https://t2t2.github.io/obs-tablet-remote/

# おわりに
RTMP リレーサーバを自宅に設置してシーン切り替えを行うシステムの全体と設定を記事としてまとめた。
本システムを導入することで、回線が不安定ながらも、視聴体験をできるだけ損ないにくい配信を比較的安価で実現できた。導入は複雑だが、一度設定すればさほどセットアップには時間がかからない。もちろん Belabox + SRT を利用したシステムの方が高品質の配信を行えるが、RTMP でもなんとかここまではできるということを示すことができた。

配信例のアーカイブ：

https://www.twitch.tv/videos/2000968066

### 欠点
GoPro の配信機能は再接続機能が一応備わっているが、かなり貧弱であるため、配信が停止しがちである。そのため毎回手動でGoProを操作する（or QRコードを読ませる）必要があり、手間になる。Belabox を利用するシステムでは、GoPro などを単純にカメラとして利用し、安定した高品質なエンコーダを介して映像を送信するため、手間がかからなないシステムを求めるならば、次のステップとして Belabox を検討することをオススメする。


# 謝辞
このシステムを考えるにあたり、情報収集と実験を通じて情報を提供していただいた Pitman 氏（[X: @pitman_live](https://twitter.com/pitman_live) / [Tw: pitman_live](https://www.twitch.tv/pitman_live))に感謝いたします。

https://www.twitch.tv/pitman_live?lang=ja

https://twitter.com/pitman_live