前回のffmpeg 4.0 をlibsrtを有効にしてビルドするの続き。
ffmpeg 4.0のlibsrtのサポート。軽い気持ちで試して見ようとしたら、いきなりバグを踏んだらしい。試行錯誤の末、ffmpeg側にパッチをあてることで動いた。本来ならバグレポートするところだけど、現状あまりにSRTのことをわかっていないので、とりあえずブログにメモを残そう。
— 組み込みの人。 (@tetsu_koba) 2018年4月27日
SRTとは
"SRT is an open source video transport protocol and technology stack that optimizes streaming performance across unpredictable networks with secure streams and easy firewall traversal, bringing the best quality live video over the worst networks."
「SRTはオープンソースの映像伝送プロトコルとテクノロジースタックです。それは不確定なネットワークでストリーミング性能を最適化します。セキュアなストリームと簡単なファイアーウォール越えもあります。最悪のネットワークで最高の品質のライブビデオをもたらします。」
このサイトのデモビデオがわかりやすい。
https://www.srtalliance.org/
https://github.com/Haivision/srt
試したこと
とりあえず、映像の送信側と受信側は同一のマシン上でやった。
映像の受信側
ffplayで受け取った映像をすぐ画面に表示する。こちらがサーバ側になるので、ソケットをlistenする。
ffplay -i 'srt://localhost:3000?mode=listener'
映像の送信側
テストパターンを送信する。
ffmpeg -f lavfi -i testsrc=s=320x240:r=30 -f mpegts 'srt://localhost:3000'
これはうまくいった。
ffplayが映像を表示し始めるまで数秒の遅延がある。これはffplay側でのバッファリングを減らすようにオプション指定すれば改善すると思う。
バグを踏んだ!
次にテストパターンの解像度を720p に上げてみた。
ffmpeg -f lavfi -i testsrc=s=1280x720:r=30 -f mpegts 'srt://localhost:3000'
そうすると以下のようなエラーで終了してしまう。
15:57:34.596232/ffmpeg*E: SRT.c: LiveSmoother: payload size: 32768 exceeds maximum allowed 1316
[srt @ 0x2f4c580] Operation not supported: Incorrect use of Message API (sendmsg/recvmsg)..
av_interleaved_write_frame(): Unknown error occurred
Error writing trailer of srt://localhost:3000: Unknown error occurred
frame= 2 fps=0.0 q=5.3 Lsize= 33kB time=-00:00:00.03 bitrate=N/A speed=N/A
video:30kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 9.295226%
Conversion failed!
最初のエラーである以下のログはlibsrt.so の中から出ている。
15:57:34.596232/ffmpeg*E: SRT.c: LiveSmoother: payload size: 32768 exceeds maximum allowed 1316
何か追加のオプション設定が必要なのかとドキュメントをあさったり、ソースコードを追いかけたり。
どうもffmpegからlibsrtの書き込みの関数を呼ぶ時のサイズが1316を超えているのが悪いらしいが、ffmpeg側ではそれを考慮しているような気配が無い。というわけで、クイックハック。
diff --git a/libavformat/libsrt.c b/libavformat/libsrt.c
index 0f9529d..073261b 100644
--- a/libavformat/libsrt.c
+++ b/libavformat/libsrt.c
@@ -466,6 +466,7 @@ static int libsrt_open(URLContext *h, const char *uri, int flags)
}
}
}
+ h->max_packet_size = 1316;
return libsrt_setup(h, uri, flags);
}
これで書き込み関数を呼ぶときのサイズの上限が1316バイトになる。
この修正を入れたffmpeg では720pのサイズのテストパターンも表示できるようになった。
フレームドロップ
しばらく動かしていたら、以下のようなログが大量に出るようになった。
15:24:04.249068/ffmpeg*E: SRT.d: SND-DROPPED 2 packets - lost delaying for 1024ms
15:24:04.278017/ffmpeg*E: SRT.d: SND-DROPPED 18 packets - lost delaying for 1025ms
15:24:04.291874/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1024ms
15:24:04.319628/ffmpeg*E: SRT.d: SND-DROPPED 4 packets - lost delaying for 1025ms
15:24:04.349290/ffmpeg*E: SRT.d: SND-DROPPED 3 packets - lost delaying for 1023ms
15:24:04.378073/ffmpeg*E: SRT.d: SND-DROPPED 2 packets - lost delaying for 1025ms
15:24:04.406149/ffmpeg*E: SRT.d: SND-DROPPED 4 packets - lost delaying for 1027ms
15:24:04.422419/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1029ms
15:24:04.450959/ffmpeg*E: SRT.d: SND-DROPPED 18 packets - lost delaying for 1027ms
15:24:04.465044/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1026ms
15:24:04.480542/ffmpeg*E: SRT.d: SND-DROPPED 2 packets - lost delaying for 1029ms
15:24:04.508143/ffmpeg*E: SRT.d: SND-DROPPED 2 packets - lost delaying for 1030ms
15:24:04.535793/ffmpeg*E: SRT.d: SND-DROPPED 3 packets - lost delaying for 1030ms
15:24:04.549798/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1029ms
15:24:04.577249/ffmpeg*E: SRT.d: SND-DROPPED 4 packets - lost delaying for 1031ms
15:24:04.593026/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1031ms
15:24:04.619995/ffmpeg*E: SRT.d: SND-DROPPED 18 packets - lost delaying for 1028ms
15:24:04.634104/ffmpeg*E: SRT.d: SND-DROPPED 1 packets - lost delaying for 1029ms
15:24:04.647519/ffmpeg*E: SRT.d: SND-DROPPED 2 packets - lost delaying for 1028ms
同一マシン内での転送なので、ネットワーク速度が足りないとは思えないが、非力なマシンで720p 30fpsのH.264のエンコードとデコードを同時にやっているのでCPUの処理が間に合わないときがあるのかな。
SRTでは受信側の状況が送信側に知らされるようになっているので、ネットワークの通信速度を把握できる。それで間に合いそうにないフレームは落とすという判断をするそうだが、このログはまさにそれが起こっているのだと思われる。
この状態であってもffplayで再生している動画は流れている。本来30fpsのところが、少しフレームレートが落ちているのだと思うが、見ていて不快な感じはしない。
いままでの方法だとネットワークが混雑すると動画の動きがカクカクすることがあって、これは見ていて不快であった。
最近のライブ配信のトレンドは「低遅延」であるが、そのためにバッファリングする量を減らすとどうしてもネットワークの速度のゆらぎの影響を受けやすくなる。SRTという新しい映像伝送プロトコルでは状況の悪いときには自動的にフレームを間引くことで対応することができるということが理解できた。
追記 (2018.5.2)
SND-DROPPED のログが出ていたのは、テストパターンを速く送りすぎたために受信側のバッファがあふれてしまうためとわかりました。テストパターンの送信を30fpsの速度で送るようにするためには -re
のオプションを追加します。
また、映像のコーデックを指定していなかったので、mpeg2が選択されていました。H.264でエンコードするには -c:v libx264
を追加します。
よって、テストパターンを送信するときのコマンドは以下のように変更します。
ffmpeg -re -f lavfi -i testsrc=s=1280x720:r=30 -c:v libx264 -f mpegts 'srt://localhost:3000'
追記 (2018.5.4)
今回見つけたバグはffmpegに報告しました。
https://trac.ffmpeg.org/ticket/7187
追記 (2018.11.12)
このバグはffmpeg 4.1 で解消しました。
https://qiita.com/tetsu_koba/items/cc1ecb9c2f5ae2901aa9