Nginx rtmp module予期せぬ切断への対応 長時間映像配信を実現

Nginx rtmp-moduleを利用して高ビットレート長時間映像配信を実現したい。
hls配信で目指した。
エンコーダは、OpenBroadCaster studio を利用。
インターネット回線は NTT 光フレッツ隼

最初はどうしても長時間のエンコードができず途中で止まってしまう状況だったが、下記のようにサーバの設定を少しずつ変え、またエンコーダ設定も変えながら改善を重ねた結果エンコーダが止まる事はなくなった。ただ、残念ながら、ちょっとした映像が止まったりという状況の改善までは至らなかった。恐らく、Pingでもロスが確認できる時間帯があるので、ロスがあるとどうしても瞬間的な停止などにいたってしまう。hls配信にくらべ、rtmp配信にすると送信状況はかなり改善される事も分かった。

インターネット回線が光フレッツ隼でもPingロスが出る。うちの会社は比較的田舎にあるので、この地域のインターネット環境に引っ張られるわけではなく、他の地域でも同程度起こる事だと思う。

経緯について

LAN内にサーバを設置して、運用している分には特に問題がおきないのに、Cloudn上にUbuntu + Nginxサーバを設置して映像配信を始めたら、予期せぬ切断が出るようになった。今のところ

高ビットレート
2500Kbps

だと2時間おきくらい発生する。

低ビットレート
368Kbps

にしても同程度の頻度で発生していた。

何とか安定させたい。バッファを持たせれば切断に強くなるんじゃないかと思うのだけど、情報が少ない。バッファ設定は2つの場所でできそうだけど、どちらの機能がどうなのかが良く分からない。

このサイトが一番マニュアル的に信頼があるのかな
https://jp9000.github.io/OBS/settings/encodingsettings.html
他の、日本語サイトに書いてある通り、画質向上に役立つらしい。特に、動きが激しい時に、ビットレートが上がってしまうからそれをならすために利用するけど、リアルタイム性が失われるからあまりお勧めされてない。

This ties in closely with the bitrate. x264 will encode everything into a fixed size buffer of this size. Raising it can increase motion quality, but at a cost; if there is a sudden scene change and high motion, it can fill up this buffer at a rate faster than the average max bitrate. For example if you have a bitrate of 3000 and a buffer of 6000, x264 could decide that if a scene is complex enough, to use 5000 of the 6000 buffer at once. Though your overall average bitrate will stay the same, this makes your actual network data quite spiky, which can lead to latency issues for both you and your viewers. Setting it too low on the other hand can make your transmission less spiky, but can reduce motion quality.

Getting the Stream Started: OBS Setup Guide


特定バッファサイズの調整は、インターネットの信頼性向上につながるかもしれない。

Generally, Buffer Size should never need to be changed
If you need to alter it, check the Use Custom Buffer Size box and enter the number from OBS Estimator into Buffer Size
This may need tweaking to accommodate for your internet reliability

https://steamcommunity.com/sharedfiles/filedetails/?l=german&id=239575989
多くの場合必要ない設定

Once you know your upload speed enter around 70-80% of it into the “Max Bitrate (kb/s)” and “Buffer Size (kbit)” field. Advanced users can set the buffer size higher, but it’s not necessary in most situations.

予期せぬ切断に特定バッファサイズを使用にチェックを入れて動作させたところ、低ビットレートの切断しない時間は長くなった。

高ビットレート
2500Kbps 特定バッファサイズ 125000 2時間程度切断は変わらず

低ビットレート
368Kbps 特定バッファサイズ 2500 18時間程度切断せず

低ビットレートに状況の改善はみられるものの、まだ切断が発生する。

https://jp9000.github.io/OBS/settings/advancedsettings.html
ネットワークに衝撃(障害)があった時にそれを最小限に抑える調整ができる。最終手段的な感じで書かれてる。

These are advanced options used to fine tune the “Minimize network impact” option. The effect varies depending on the network conditions. Higher value increases spacing between packets. This is a very last-resort option for precise fine tuning of packet intervals

では次に、遅延配信を有効にしてみる

※配信遅延があると、エンコーダを停止させた後もしばらく映像配信は継続する

高ビットレート
2500Kbps 配信遅延 有効にする 20s 1時間程度で切断 あーダメかぁ。

低ビットレート
368Kbps は一応配信続けてるけど、同じエンコーダでやっているので一度停止する。

高ビットレート
2500Kbps 3時間ほどで切断

低ビットレート
368Kbps 今のところ切れてない

Sets RTMP acknowledge window size. It’s the number of bytes received after which peer should send acknowledge packet to remote side. Default value is 5000000.

ウィンドウサイズの説明
http://www.infraexpert.com/study/tcpip11.html

今起こっている現象は、サーバ側では、RTMPを受け続けているつもりで切断はなく、エンコーダはサーバからdisconnect されたと出てくる。推測すると、エンコーダから通信を開始して、サーバからackが返って来ないって事かな。で、低ビットレートの時はいいけど、高ビットレートになるとこの現象は起きやすくなる。高ビットレートでもバッファサイズを大きくすると、この現象は起きにくくなるが、完全にはなくならない。

ちょっと違うけど

LinuxのTCP受信バッファ溢れと輻輳制御の話


nginxサーバのTCP受信バッファが溢れた問題
クライアントからの再送も重なり、結果的にnginxサーバが慢性的なバッファフルによる輻輳状態に陥り、パケットをぼろぼろ落とすという現象が発生していたようです。

ack_windowを小さくしてみる

これらの事から予測して、nginx のウィンドウサイズを小さくする事で、ackを早め早めに返す。もしackをロスしても、再送量を少なくさせる事で、ackロスを重ねた時に復帰しやすくするという考え方。デフォルトの1/10で設定するとどうか。この設定をする事により、スループットは落ちてしまうはずだが、速度自体は必要十分出ているので。

 

高ビットレート
2500Kbps 3時間ほどで切断

状況変わらずなので、上記と逆。nginxとの通信でウィンドウサイズを大きくする事で、できるだけackが返る量を少なくしてみる。再送の際には負荷がかかるが、ウィンドウサイズが大きいのでackが返るまでのデータ量も多く効率を上げる事でスループットが上がる。

 

 

効果なし
高ビットレート
2500Kbps 1時間ほどで切断

ズバリじゃないけど、chunk_sizeはデータの分轄を行うサイズ
http://kledgeb.blogspot.jp/2014/03/ubuntu-mdadm-5.html

chunk_sizeを倍にしてみる

 

高ビットレート

2500Kbps 40分くらいで切断

特にバッファ関係を厚く処理

nginxの設定にて
buflen 10s;

OBSの設定にて
特定バッファサイズを使用にチェック 125000
遅延配信有効にする20s

今度はOBSがハングアップする。
「obs32.exeは動作を停止しました。」
3時間くらいごとに起こる。

nginxの設定にて
buflen 10s;

OBSの設定にて
特定バッファサイズを使用にチェック 25000
遅延配信有効にする20s

配信3時間ほどで停止。Nginxは動き続けてるけど、エンコーダがNginxから切断されたと判断。

切断しても仕方ない、すぐにエンコーダがreconnectできればという発想に転換

nginxの設定にて
buflen 10s;
ping 10s;

OBSの設定にて
特定バッファサイズを使用にチェック 25000
遅延配信有効にする20s

https://github.com/arut/nginx-rtmp-module/issues/77

When network gets disconnected the way you have described tcp connection
usually hangs for a long time until keepalive/retransmission timeout expires.
RTMP ping fixes that.

もし、切断したとしてもサーバがちゃんと切断を判別し、すぐにエンコーダが再接続してくれれば大きな問題は出ないのではないか。

やっぱりだめ2時間で切れた。切れた時の特徴として、エンコーダは切断されていて、再度接続しようとしても接続できない。エンコーダは再接続フェーズを繰り返すnginxのログには

 

つまり、切れたことになっていないって事。
この問題についてping 10;は効力がない。

サーバが切断を検知しない時
https://github.com/arut/nginx-rtmp-module/issues/430
Problem is completely solved by using drop_idle_publisher directive.
This is issue can be closed.

https://github.com/arut/nginx-rtmp-module/wiki/Directives#drop_idle_publisher

Drop publisher connection which has been idle (no audio/video data) within specified time. Default is off. Note this only works when connection is in publish mode (after sending publish command).

指定した秒数の間、音声、映像がこなければサーバ側で切断と判定する。

OBSの設定にて

特定バッファサイズを使用にチェック 25000
遅延配信有効にする20s
自動的に再接続有効にするにチェック 再試行の遅延10s

バッファを15sストックして、映像と音声が10s来なかったら切断として判断し、エンコーダからの再接続を受け付ける。

正解に一歩近づいたかもしれない。エンコーダは動き続けてる。現在4時間経過。ただ、3時間を過ぎたあたりで1回切断があった。それを再接続できるようになった。これは設定による想定内だけど、残念なのは、映像受信クライアント側で一度受信し直さなければならなかったこと。できれば、エンコーダが切断しても、受信側では何が起こったのか分からないように処理を継続したい。

が効いた。無事再接続でき、エンコーダはずっと動き続ける。後は、映像受信クライアントに気づかせないで再接続できるようになればパーフェクト。

組み合わせ設定5(切れてもいいや、すぐに再接続されて映像受信クライアントで映像が止まらなければ)

組み合わせ4で問題が出たのはエンコーダ側の遅延配信が問題なのではないか?再接続された時に遅延配信設定が悪さをし、なかなか映像が送られず、サーバ側に映像がない空白時間が増えすぎて、結果としてクライアントで映像を見ていると途切れてしまうのではないか。(仮定)という事から、遅延配信設定を無効にする。

OBSの設定にて

特定バッファサイズを使用にチェック 25000
動的に再接続有効にするにチェック 再試行の遅延10s

配信遅延設定は関係なかったかもしれない。エンコーダは動き続けているが、映像受信クライアントでは映像が止まってしまった。現在VideoJSでweb受信していたのだが、m3u8を直接受信できるwebクライアント(Egdeとか、スマホ系ブラウザとか)で受信すると、プレーヤが停止することなく受信できるようなった。

ただ、映像受信中にコマが停止したりする事があった。エンコーダが再接続する時には受信クライアント側で映像が止まってしまうのが問題。

https://coopulous.blogspot.jp/2016/06/OBSSettings.html

Preserve Cutoff point when reconnecting: If you get disconnected from streaming and have the option to auto reconnect enabled you can ensure no one misses out on anything which would have occurred during that time period. You must have stream delay enabled for this to work.

特に、遅延配信有効にして、カットオフポイントも入れておくと、エンコーダが再接続した時に変な動きにならないかも。切断された時も、誰もそれが起こった時に逃さないって表現になっているし。

https://www.speedrun.com/Reboot/allposts

OBS Multiplatform 0.12.0 (not yet released) will add stream delay with a new feature “Preserve cutoff point” which will make sure no video is lost, when you have a disconnect.
Additionally, a new option was added to preserve stream cutoff point on disconnections/reconnections, so that if you get disconnected while streaming, when it reconnects, it will reconnect right at the point where it left off. This will probably be quite useful for a number of
applications … for example, a critical stream such as a tournament stream from getting any of its stream data cut off.

もっと直接的な表現で、Preserve cutoff point にチェックを入れておくと、切断し、エンコーダが再接続した時に正しい再接続ポイントで接続してくれるので、映像受信者は切断に気づかず配信が可能なはず。

Preserve cutoff point
日本語版の機能名は「再接続時にカットオフポイントを保持する」

とりあえず、設定項目を洗って、下記の組み合わせが良いというところまできた。
極力、受信側に切断を気づかせたくない。そのためのバッファの長さなどを少しずつ変えてベストを探る。

OBSの設定にて

特定バッファサイズを使用にチェック 25000
遅延配信有効にする20s 再接続時にカットオフポイントを保持する
動的に再接続有効にするにチェック 再試行の遅延7s

映像受信クライアントにて
m3u8に直接アクセスする。

nginx側でも、ある程度バッファを持つ30s
映像、音声のパケットが届かない時間を5s続けたら切断とみなし、切断する。
切断され、7s後に再接続する

エンコーダ側に通知してやると、さらに切断、接続が明確になって継続性・再接続がスムーズに進むのでは?
Send NetStream.Play.PublishNotify and NetStream.Play.UnpublishNotify to subscribers. Defaults to off.
publish_notify on;

 

 

youtube live ストーリーミング キーフレームは4秒以下

何と、これで解決した。OBSでキーフレームを4sにしたところ、全く切れなくなった。すごいな。

低ビットレートでも、バッファなどを設定しない標準状態だと、やはり時々切れる事がある。
その時のネットワーク状態がどのような状態か、Ping試験をし続けロスを調べた。

エンコーダからサーバに向けてPing結果

2016/10/03 14:37:20.89

video03.hanako.or.jp [153.149.98.165]に ping を送信しています 32 バイトのデータ:
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =15ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =15ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =13ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =15ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =64ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =364ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =23ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =23ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =401ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =14ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =21ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =18ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =49ms TTL=52
要求がタイムアウトしました。
要求がタイムアウトしました。
153.149.98.165 からの応答: バイト数 =32 時間 =2737ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =22ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =16ms TTL=52
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
153.149.98.165 からの応答: バイト数 =32 時間 =17ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =36ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =2402ms TTL=52
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
153.149.98.165 からの応答: バイト数 =32 時間 =160ms TTL=52
153.149.98.165 からの応答: バイト数 =32 時間 =17ms TTL=52

切断時Nginxサーバのログ

Server: nginx/1.11.4
Date: Mon, 03 Oct 2016 05:38:44 GMT
Content-Type: application/vnd.apple.mpegurl
Content-Length: 237
Last-Modified: Mon, 03 Oct 2016 05:38:20 GMT
Connection: keep-alive
ETag: “57f1eecc-ed”
Accept-Ranges: bytes

2016/10/03 14:38:44 [debug] 16471#0: epoll timer: 44139
2016/10/03 14:38:45 [debug] 16471#0: epoll: fd:14 ev:201D d:00007F763ADE6499
2016/10/03 14:38:45 [debug] 16471#0: epoll_wait() error on fd:14 ev:201D
2016/10/03 14:38:45 [debug] 16471#0: *13 http keepalive handler
2016/10/03 14:38:45 [debug] 16471#0: *13 malloc: 0000000001FB3400:1024
2016/10/03 14:38:45 [debug] 16471#0: *13 recv: eof:1, avail:1
2016/10/03 14:38:45 [debug] 16471#0: *13 recv: fd:14 -1 of 1024
2016/10/03 14:38:45 [info] 16471#0: *13 client 124.96.82.97 closed keepalive connection (104: Connection reset by peer)