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

Buffering while converting stream to frames with 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 流。

它在大部分时间都运行良好,但有时,偶尔会出现延迟,帧会延迟到达。所以我想要 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

但我还是遇到了问题。例如,在 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

如您所见,在第 31 秒期间,没有帧输出...并且 ffmpeg 报告两帧之间的时间为 0.25s

问题中发布的 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 会崩溃。为了避免它,我添加了:"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

并将 ffmpeg 命令更改为 "udp://231.20.20.8:2005?buffer_size=26214400"

但是,这并没有解决问题。 ffmpeg 仍然会得到 "Circular buffer overrun" 并崩溃。而且我无法重现这个循环缓冲区溢出,它只是随机发生的。

我的下一个想法是管道缓冲区大小,因为我发现了以下内容:

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

The size of the buffer since kernel version 2.6.11 is 65536 bytes (64K) and is equal to the page memory in older kernels. When attempting to read from an empty buffer, the read process is blocked until data appears.
Similarly, if you attempt to write to a full buffer, the recording process will be blocked until the necessary amount of space is available.

http://ffmpeg.gusari.org/viewtopic.php?f=12&t=624[link现已死亡]

Poster1: What causes these circular buffer overruns? My assumption is that ffmpeg is reading the input stream into the aforementioned circular buffer, and the code then generates the output stream also reads from that same buffer. The overrun would happen when the code that generates the output doesn't keep up with the rate at which it's being written to the buffer, right?
Poster2: Looking at the source code it appears that the buffer gets overflowed either by too fast input or too slow output (slow cpu?). Your assumption is correct.

所以理论上我们的二进制文件读取管道的速度不够快。结果管道被阻塞,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

下一步是调查为什么我们的二进制文件在某个时候停止读取管道。好像没有什么原因,因为通常它会在有东西传到管道后立即读入内存。

但是,它也将帧保存到硬盘驱动器,并且在某些时候(有时 12 分钟,有时 15 小时),磁盘操作会由于 read/write 操作而变慢(它是 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 套接字的速度比 our_binary 推送到它的速度慢。结果,tcp 套接字将被阻塞(最大 4Mb),结果,客户端将阻塞其输入管道,结果 ffmpeg 将因此 CBO 错误而崩溃。