如何停止perl缓冲ffmpeg输出

3
我想让一个Perl程序处理ffmpeg编码的输出,但我的测试程序似乎只能以定期块接收ffmpeg的输出,因此我假设存在某种缓冲。如何使其实时处理?
我的测试程序(tr命令在那里,是因为我认为ffmpeg的回车符可能会导致perl看到一个很长的行或其他东西):
#!/usr/bin/perl

$i = "test.mkv"; # big file, long encode time
$o = "test.mp4";

open(F, "-|", "ffmpeg -y -i '$i' '$o' 2>&1 | tr '\r' '\n'")
        or die "oh no";

while(<F>) {
        print "A12345: $_"; # some random text so i know the output was processed in perl
}

当我用这个脚本替换ffmpeg命令时,一切都正常工作:

#!/bin/bash

echo "hello";

for i in `seq 1 10`; do
        sleep 1;
        echo "hello $i";
done

echo "bye";

当使用上述脚本时,我会看到每秒的输出情况。使用ffmpeg时,需要等待大约5-10秒才会有输出,并且有时会输出100行左右。
我尝试在命令调用中在ffmpeg之前使用程序unbuffer,但似乎没有效果。也许是2>&1导致了缓冲?
非常感谢您的帮助。
如果您不熟悉ffmpeg的输出,它会将一堆文件信息和其他东西输出到STDOUT,然后在编码期间输出类似以下的行:
frame=  332 fps= 93 q=28.0 size=     528kB time=00:00:13.33 bitrate= 324.2kbits/s speed=3.75x

这里的tr指的是以回车符而非换行符开头的文本,它出现在STDERR上(因此使用了2>&1)。


1
@SebastianKing:我对ffmpeg很熟悉,但是在你的问题中并没有告诉我为什么你需要实时看到输出。那个输出是用来控制台使用的,而且如果你通过程序运行转换,那么你肯定只需要知道每个文件是否成功转换了吧? - Borodin
1
这是一种用于更新当前行的 "技巧"。// 如果他们不替换 <>,他们就不能使用 <>。(没关系,他们可以使用 read 代替。)// 这不是使用 unbuffer 的方式。 - ikegami
1
@SebastianKing:当然,但我的问题仍然是,为什么你想要看“实时统计数据”?这不像有人可以跳进去,修复源视频,让火车继续运行。这不就像看洗衣机旋转一样吗? - Borodin
1
@Borodin 我的实时统计数据一直存在,我会想办法解决。unbuffer 不能被管道传输,正确的用法是 unbuffer <bin> <args>。当我这样做时,我可以实时输出,直到回车符号部分,此时我将不再有输出,直到编码完成。这就是为什么我使用了 tr,但现在我们看起来问题好像并不在那里。 - Sebastian King
@Borodin 如果您愿意提供一个答案,我将非常乐意接受。我还用 stdbuf -i0 -o0 -eL 替换了 unbuffer -p-eL 使其现在是行缓冲,我认为稍微更有效),以消除对相当大的 expect 包的依赖。 - Sebastian King
显示剩余18条评论
2个回答

1
问题在于tr正在缓冲输出,并且unbuffer仅被应用于命令开头的ffmpeg。这意味着unbuffer无法影响到管道到perl的部分。

可以使用unbuffer -p来解决这个问题,它是用于取消缓冲管道命令的(<cmd1> | unbuffer -p <cmd2>取消缓冲cmd1cmd2)。

但是,我还将unbuffer替换为stdbuf -i0 -o0 -eL,以消除对expect软件包的依赖,因为当stdbuf作为标准时,它是一个庞大且不必要的软件包。此外,-eL选项使stdbuf成为行缓冲,我认为这应该比没有缓冲更加CPU高效。完整的工作代码如下:

#!/usr/bin/perl

$i = "test.mkv"; # big file, long encode time
$o = "test.mp4";

open(F, "-|", "ffmpeg -y -i '$i' '$o' 2>&1 | stdbuf -i0 -o0 -eL tr '\r' '\n'")
        or die "oh no";

while(<F>) {
        print "A12345: $_"; # some random text so i know the output was processed in perl
}

由于这个问题在评论中得到了回答,没有正确的答案被发布,所以我把它变成了一个维基答案,应该归功于@Borodin。


1
谢谢您。我本来想写一个解决方案,但这比我写的要有用得多。您应该尽快接受您的答案以标记问题已关闭。 - Borodin
好的,虽然我是直接从bash运行而不是perl,并且我正在使用管道到sed而不是tr,但这对我不起作用。 - Michael

0

首先,输出缓冲是由执行输出的人执行的。

大多数程序都会缓冲它们的输出,除非它们连接到终端,因此,除非您有极少数提供禁用缓冲的选项的程序之一,否则唯一可用的选项是使用伪终端而不是管道来欺骗它们。这就是 unbuffer 的作用。

在这种情况下的问题是有两个程序执行输出缓冲:ffmpegtr。您需要说服两者都避免缓冲。您需要为每个程序使用 unbuffer!希望这会使它们不再缓冲。

不幸的是,... | unbuffer ... 不起作用。(我不知道为什么。)所以通过使用 sysread 而不是 <> 来摆脱 tr

my $buf = '';
while (1) {
   my $rv = sysread($PIPE, $buf, 64*1024, length($buf));
   die $! if !defined($rv);
   last if !$rv;

   while ($buf =~ s/^([^\r\n]*)[\r\n]//) {
      my $line = $1;
      print("[$line]\n");
   }
}

print("[$buf]\n") if $buf ne "";

当然,所有这些都是基于假设的,即在发送回车后,ffmpeg会刷新其缓冲区,因为unbuffer可能对此无济于事。(当然,如果是这种情况,可能根本不需要unbuffer。只需通过转换到sysread来避免使用tr应该就可以解决问题了。)

使用 unbuffer ffmpeg -y -i '$i' '$o' 2>&1 | unbuffer tr '\r' '\n' 即使在 shell 中运行也没有任何输出,我甚至尝试了 echo "hi" | unbuffer tr '\r' '\n',但是没有得到输出,我做错了什么?使用 echo 命令时它不会返回,只是输出空白并保持运行状态。 - Sebastian King
确实。 (使用“echo“ hi”| unbuffer perl -pe1”进行测试,以确保它与“tr”无关。)请参见更新。 - ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接