web-dev-qa-db-ja.com

ffmpegでストリームをフレームに変換する際のバッファリング

Ffmpegを使用してudpストリームをフレームに変換しようとしています。次のコマンドを実行します。

ffmpeg -loglevel debug -strict 2 -re -i "udp://192.168.15.50:3200?fifo_size=1000000&overrun_nonfatal=1" -r 8 -vf scale=432:243 -f image2pipe -vcodec ppm pipe:1

これは、mpeg2videoとh264のさまざまなストリームタイプで発生します。この特定のストリームをコア処理するためのCPU負荷は30%未満であり、解像度が640x576の低品質のsdストリームです。

ほとんどの場合は正常に機能しますが、場合によっては遅延が発生し、フレームが遅れて到着することがあります。だから私は正確に8fpsが欲しいのですが、時々私はより少なく、時にはより多くなります。

なぜこの待ち時間が発生するのですか?どうすればそれを減らすことができますか?

更新:私はそれを次のように変更しようとしました:

ffmpeg -loglevel debug -i "udp://192.168.15.50:3200?fifo_size=1000000&overrun_nonfatal=1" -r 8 -preset ultrafast -fflags nobuffer -vf scale=432:243 -f image2pipe -vcodec ppm pipe:1

しかし、私はまだ問題を抱えています。たとえば、ffmpegログで次のようになります。

[2016/02/11 13:32:30] frame= 7477 fps=8.0 q=-0.0 size= 2299638kB time=00:15:34.62 bitrate=20156.4kbits/s dup=7 drop=15867 ^M*** dropping frame 7477 from stream 0 at ts 7475
[2016/02/11 13:32:30] ***dropping frame 7477 from stream 0 at ts 7476
[2016/02/11 13:32:30] ***dropping frame 7478 from stream 0 at ts 7476
[2016/02/11 13:32:32] Last message repeated 1 times
[2016/02/11 13:32:32] frame= 7479 fps=8.0 q=-0.0 size= 2300253kB time=00:15:34.87 bitrate=20156.4kbits/s dup=7 drop=15871 ^M*** dropping frame 7479 from stream 0 at ts 7477

ご覧のとおり、2番目の31の間、フレームは出力されません...そしてffmpegが報告した2つのフレーム間の時間は0.25秒です

8
Pavel K.

質問に投稿されたffmpegコマンドは通常、別のバイナリにパイプされます。そのバイナリはffmpegによって提供されたフレームを保存し、それらに対していくつかの処理を行います。

最初は"fifo_size=1000000&overrun_nonfatal=1"オプションを使用していなかったので、ffmpegから次のエラーが発生していました。

[udp @ 0x4ceb8a0] Circular buffer overrun. To avoid, increase fifo_size URL option. To survive in such case, use overrun_nonfatal option
udp://192.168.15.50:3200: Input/output error

そしてffmpegがクラッシュします。それを避けるために、ffmpegが示唆するように:"fifo_size=1000000&overrun_nonfatal=1"を追加しました。

ただし、これらのパラメーターを使用した後、質問で説明されているようにタイムシフトが発生し、フレームにアーティファクトが含まれることもあります。

前述のように、CPUに問題はなかったため、最初はudpストリーム、特にudpバッファサイズを疑っていました。

https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Web_Platform/5/html/Administration_And_Configuration_Guide/jgroups-perf-udpbuffer.html

そのため、udpバッファサイズを次のように変更しました。

sysctl -w net.core.rmem_max=26214400

ffmpegコマンドを「udp://231.20.20.8:2005?buffer_size = 26214400」に変更しました

ただし、これで問題が解決するわけではありません。 ffmpegはまだ「循環バッファオーバーラン」を取得してクラッシュします。そして、この循環バッファオーバーランを再現できませんでした。ランダムに発生しただけです。

次のことを見つけたので、次の考えはパイプバッファサイズでした。

http://blog.dataart.com/linux-pipes-tips-tricks/

カーネルバージョン2.6.11以降のバッファのサイズは65536バイト(64K)であり、古いカーネルのページメモリと同じです。空のバッファから読み取ろうとすると、データが表示されるまで読み取りプロセスがブロックされます。
同様に、フルバッファーに書き込もうとすると、必要な容量が使用可能になるまで記録プロセスがブロックされます。

http://ffmpeg.gusari.org/viewtopic.php?f=12&t=624 [リンクは現在無効です]

Poster1:これらの循環バッファオーバーランの原因は何ですか?私の仮定では、ffmpegが入力ストリームを前述の循環バッファーに読み込んでおり、コードが出力ストリームを生成して、同じバッファーからも読み取ります。オーバーランは、出力を生成するコードがバッファーに書き込まれる速度に追いついていない場合に発生しますよね?
Poster2:ソースコードを見ると、入力が速すぎるか出力が遅すぎる(CPUが遅い?)ためにバッファがオーバーフローしているようです。 )。あなたの仮定は正しいです。

したがって、理論では、バイナリはパイプを十分に速く読み取れません。その結果、パイプがブロックされ、ffmpegがパイプに書き込むことができなくなり、udp fifoバッファオーバーランが発生します(ffmpegはudp INTO FIFOを読み取り続けますが、FROMをパイプに書き込むことはできません)。

私は(別々の端末で)実行することによってこの理論を証明することができました:

mkfifo mypipe
ffmpeg -loglevel debug -i "udp://192.168.15.50:3200?fifo_size=1000000&overrun_nonfatal=1" -r 8 -preset ultrafast -fflags nobuffer -vf scale=432:243 -f image2pipe -vcodec ppm pipe:1 > mypipe
cat < mypipe > /dev/null # run this for 10 seconds, allowing ffmpeg to start. then pause it with CTRL-Z and see ffmpeg crashing because it cannot read more udp stream

次に、バイナリがパイプの読み取りを停止する理由を調査しました。通常、パイプに何かが来た直後にメモリに読み込まれるため、理由はないようです。

ただし、フレームをハードドライブに保存することもあり、ある時点(12分、15時間)では、読み取り/書き込み操作のためにディスク操作が遅くなりました(bcache(SSDとHDDハイブリッド、キャッシュとしてSSDを使用) ))。デバッグのためにこのドライブから数百万のファイルを並行して削除していたときに、この事実をランダムに捉えました。

そのため、ビジー状態のハードドライブにファイルを書き込むと、バイナリが入力パイプを読み取ることが一時的にブロックされます。

Udp循環バッファオーバーランの問題と最終的なタイムシフトの理由はHDDであり、理論的な解決策はSSDです。

この調査には約3週間かかりました。そのため、少なくとも部分的には将来誰かに役立つことを願って、これらすべてを投稿します。

更新

また、後でこの同じ問題を引き起こす別のボトルネックを検出しました(HDDの交換では不十分でした)。これは、バックエンドでのpostgres挿入によって引き起こされるtcpソケットバッファオーバーフローでした。

パイプライン全体は次のようになります。

udp_videostream-> ffmpeg-> linux_pipe-> our_client_side_binary-> tcp-> our_server_side_binary-> postgres

Postgresクエリが遅い場合があり、サーバーの読み取りが遅くなりましたTCPソケットはour_binaryがプッシュするよりも遅くなりました。その結果、tcpソケットがブロックされ(最大4Mb)、その結果、クライアントは入力パイプをブロックし、その結果、ffmpegはこのCBOエラーでクラッシュします。

14
Pavel K.