2020年7月13日月曜日

Nginxを使って素の状態のブラウザで視聴可低遅延ライブストリーミングをしてみる(Nginx-http-flv-module)

コロナ禍で!
コロナ禍でライブ配信が注目されてますね。ええ。コロナ禍と言いたかっただけです。
ZOOMのようなビデオチャットや、Youtube Live ニコニコ生放送のような
1対多の生放送アプリまで、実際に集まって行う会議やイベント、
あるいは授業なんかもライブ配信で済ませている。そんな時代が一気に訪れました。
ここではWebRTCなどを使った双方向のビデオチャットではなく、
いわゆる映像配信と言われる1対多のストリーミングサービスの話題が中心です。

そんな中、セキュリティの関係もあり自分でライブ配信プラットフォームを作れないか
という相談が寄せられた人もいることでしょう。
これまでは、Adobe Media ServerやWowzaを動かしてそこにRTMPで打ち上げ、
Flash Playerで視聴といったアプリケーションが一般的でしたが、
ライセンス料をはじめ、非常にサーバーのコストがかかりました。
また、映像送信側では現役で使われているRTMPも、
現在では視聴環境側のブラウザでは、
RTMPの受信に必須であるFlash Playerのサポートを打ち切られているなど
すでに終わった技術になりつつあります。
RTMPで打ち上げるのはいいけどじゃあどうやって見るの?という話になるわけです。

いくつか方法があり、
・サーバーでHTTP Live Streaming形式に変換し、
HTTPプロトコルでのストリーミング視聴を可能にする
・同じくMPEG-DASH形式にしてHTTPプロトコルでのストリーミング視聴を可能にする

主に使われているのはこの2種類です。

どちらも仕組みとしては、配信したい映像を一定時間に区切ったTSファイルにして、
TSファイルの構成を示したプレイリストと一緒にブラウザでダウンロード、
プレイリストにしたがって再生、プレイリストを逐次更新して
また新しいTSファイルをダウンロードという形で、
HTTP GETを繰り返ししていくことにより映像をつながった状態で見られる仕組みです。

RTMPで打ち上げた映像をそのままHLS形式に変換してくれる機能まで持ったのが、
Nginx-RTMP-moduleです。
https://github.com/arut/nginx-rtmp-module
WebサーバーとしてはおなじみのNginxのモジュールで、
設定をするとRTMPサーバーとして動作、
HLSファイルの生成をONにしてWebサーバーでホストするとTSファイルと
M3U8プレイリストを生成して、HLSの配信環境も作ることができます。

これはすでに試している方も多いと思います。
HLSはサポートしているブラウザも多く、スマホでもPCでも安定して視聴できます。
しかし、先に述べた仕組みなので、
TSファイルの長さ分、録画した動画ファイルを生成してからプレイリストに乗せて、
ブラウザがダウンロード、再生ということでその分遅延が大きくなります。
このへんは調べるか、実際にやってみるとよく分かると思います。
僕が限界までチューニングした結果3秒くらいの遅延までは縮められましたが、
回線が遅くなったりするとすぐ映像が途切れてしまうピーキーな感じになってしまいました。

毎回前置きが長いですが、そこでタイトルのNginx-http-flv-moduleの出番です。
https://github.com/winshining/nginx-http-flv-module
まずはサクッとインストールから。

環境: Ubuntu 20系 (NTTArena Indigo VPS)
nginx/1.20.2 使用
追加モジュール以外はapt-getでインストールされるNginxと同じビルドオプションに
Nginx-http-flv-moduleを追加

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev -y

Nginx-flv-moduleモジュールレポジトリをダウンロードしてビルド
git clone https://github.com/winshining/nginx-http-flv-module.git
wget http://nginx.org/download/nginx-1.20.2.tar.gz
tar -zxvf nginx-1.20.2.tar.gz
cd nginx-1.20.2/
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module \
--with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module \
--with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module \
--with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream \
--with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.20.2/debian/debuild-base/nginx-1.20.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \
--with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \
--add-dynamic-module=./../nginx-http-flv-module

make
sudo make install

成功すると、/usr/sbin/にnginxのバイナリが作成されます。

サービスの登録
sudo vi /usr/lib/systemd/system/nginx.service

[Unit]
Description=A high performance web server and a reverse proxy server
Documentation=man:nginx(8)
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

sudo systemctl enable nginx

Nginxの設定

sudo vi /etc/nginx/nginx.conf

#追加
include /etc/nginx/streams-enabled/*;

#http内に追加
include /etc/nginx/sites-enabled/*;

sudo mkdir /var/cache/nginx
sudo useradd nginx
sudo mkdir /var/cache/nginx/client_temp
sudo mkdir /etc/nginx/sites-available
sudo mkdir /etc/nginx/sites-enabled

sudo vi /etc/nginx/sites-available/vod

location /http-flv {
  flv_live on;
  chunked_transfer_encoding on;
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Credentials' true;
}

RTMPサーバー用設定ファイルを作る。
各パラメーターの詳細はNginx-RTMP-Moduleのドキュメントに書いてあります。
sudo mkdir /etc/nginx/streams-available
sudo mkdir /etc/nginx/streams-enabled

sudo vi /etc/nginx/streams-available/rtmp

rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;
rtmp {
        out_queue               4096;
        out_cork                8;
        max_streams             8;
        timeout                 3s;
        drop_idle_publisher     5s;

        log_interval            5s;
        log_size                1m;

        server {
                listen 1935;
                server_name localhost;

                application rtmp {
                        live on;
                }
        }
}

設定有効化(sites-available、sites-enabledにリンク)
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/vod /etc/nginx/sites-enabled/vod
sudo ln -s /etc/nginx/streams-available/rtmp /etc/nginx/streams-enabled/rtmp
sudo /usr/sbin/nginx

こんな感じで設定したらOBSなどでRTMPの配信を受けられます。
そして、プレイヤーの画面を作っていきます。
プレイヤーにはbilibiliのflv.jsを使います。
から、flv.min.jsをダウンロードしてwwwrootに置きます。
同じくプレイヤーのHTMLを作ります。

sudo vi /var/www/html/player.html

<script src="flv.js"></script>
<video id="videoElement" muted autoplay controls></video>
<script>
    if (flvjs.isSupported()) {
        var videoElement = document.getElementById('videoElement');
        var flvPlayer = flvjs.createPlayer({
            type: 'flv',
            isLive: true,
            enableStashBuffer: false,
            autoCleanupSourceBuffer: true,
            stashInitialSize: 1024 * 128,
            url: '/http-flv?port=1935&app=rtmp&stream=stream01'
        });
        flvPlayer.attachMediaElement(videoElement);
        flvPlayer.load();
        flvPlayer.play();
    }
</script>

これでhttp://localhost/player.htmlを見ると、
rtmp://localhost/rtmp/stream01宛にRTMPで送った映像が見られます。

仕組みとしては、 /etc/nginx/sites-available/vodに設定を書きましたが、
受け取ったRTMPストリームを、80番ポートで再配信しています。
location /http-flv...
という部分がそれです。
flv live on;とすると、
locationになっている/http-flvにAPIが生成されます。

このAPIは、
port: RTMPストリーム受信ポート
app: live on;にしているRTMPサーバーのapplication名
stream: ストリームキー名
をクエリパラメータに食わすと、内部で該当ストリームをHTTP経由で返してくれます。

これにより、RTMPストリーム(FLV)を直接HTTP GETできるようになっています。
Flash playerがないとRTMPを再生できないのは、ブラウザがrtmp://プロトコルをサポートしていないのが主な要因ですが、HTTPプロトコルでストリームが受信できるとなると話は別です。
flv.jsは、RTMP over HTTPのストリームをデコードしてHTML5プレイヤーで再生します。
視聴中の通信イメージとしては、動画をHTTP GETするとチャンクドレスポンスで、Content-Lengthが無限のBodyが降り続けてきて、ずっと配信が続く限り無限に終わらない動画のダウンロードをしている感じです。

実際に試してみると、VPSサーバーに作った配信サーバーでも2秒くらいの遅延でした。

さらに追試したところ、遅延はキーフレームの約2倍になるようで、
キーフレームを1秒にしたときは2秒、2秒にしたときは4秒ほどの遅延になりました。
動画はキーフレームが来るまでデコード開始できないので、
その分待ち時間が生まれてしまうのも納得です。

HLS配信の場合も、TSファイルの分割時間で設定した時間が短くてもストリームのキーフレーム間隔のほうが長いと、キーフレーム間隔を最短時間として分割してしまいます。
遅延と戦うには多少画質を犠牲にしてでもキーフレーム間隔を短くする必要がありそうです。

エンコーダーの設定でキーフレーム間隔をさらに短くできれば、遅延も減りそうです。




チャット付きのライブ配信や監視系など、遅延が大きすぎると問題なアプリケーションではとても威力を発揮しそうです。

0 件のコメント:

コメントを投稿