使用ffmpeg将流转换为帧时缓冲

Pav*_* K. 8 latency ffmpeg buffering video-streaming

我试图使用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
Run Code Online (Sandbox Code Playgroud)

它发生在不同的流类型,mpeg2video和h264.核心处理此特定流的CPU负载低于30%,其低质量sd流的分辨率为640x576.

它适用于大多数时间,但有时,偶尔会发生延迟,帧会在稍后到达.所以我想要8 fps,但有时我会更少,有时甚至更多.

为什么会出现这种延迟?如何减少延迟?

更新:我尝试将其更改为:

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
Run Code Online (Sandbox Code Playgroud)

但我仍然遇到了这个问题.例如,在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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在第二个31期间,没有输出帧......并且ffmpeg报告的两帧之间的时间是0.25秒

Pav*_* K. 14

在问题中发布的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
Run Code Online (Sandbox Code Playgroud)

然后ffmpeg会崩溃.为了避免它,我补充说:"fifo_size=1000000&overrun_nonfatal=1",正如ffmpeg建议的那样.

但是,在使用这些参数之后,我会按照问题中的描述获得时移,有时它也会在帧中出现伪像.

如前所述,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
Run Code Online (Sandbox Code Playgroud)

并将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,但无法将其写入我们的管道).

我设法通过运行(在不同的终端)证明这个理论:

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
Run Code Online (Sandbox Code Playgroud)

接下来是调查为什么我们的二进制文件在某些​​时候停止读取管道.似乎没有理由,因为通常它会在某些东西进入管道后立即读入内存.

然而,它也将帧保存到硬盘驱动器,并且在某些时候(有时12分钟,有时15个小时),磁盘操作会因读/写操作而减慢(这是bcache(SSD和HDD混合,使用SSD作为缓存) )).当我从这个驱动器中并行删除数百万个文件进行调试时,我随机地抓住了这个事实.

因此,将文件写入繁忙的硬盘会暂时阻止我们的二进制文件读取输入管道.

udp循环缓冲区溢出问题和最终时间转换的原因是HDD,理论解决方案是SSD.

这项调查花了大约3个星期,所以发布所有这些,希望它至少部分地,将来帮助某人.

更新:

我还发现了另一个导致同样问题的瓶颈(替换硬盘还不够),这是由后端postgres插入导致的tcp套接字缓冲区溢出.

整个管道看起来像这样:

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

Postgres查询有时很慢,这导致我们的服务器读取TCP套接字比my_binary推送它慢.结果,tcp套接字将被阻塞(它最大为4Mb),因此,客户端将阻塞其输入管道,并且由于ffmpeg将因此CBO错误而崩​​溃.