【问题标题】:How to stop perl buffering ffmpeg output如何停止perl缓冲ffmpeg输出
【发布时间】:2017-02-05 01:38:21
【问题描述】:

我正在尝试让 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&gt;&amp;1 可能正在缓冲? 非常感谢任何帮助。

如果你不熟悉 ffmpeg 的输出,它会输出一堆文件信息和东西到STDOUT,然后在编码过程中输出类似

frame=  332 fps= 93 q=28.0 size=     528kB time=00:00:13.33 bitrate= 324.2kbits/s speed=3.75x

STDERR(因此2&gt;&amp;1)上以回车而不是新行(因此tr)开头。

【问题讨论】:

  • 试试open(F,'-|:unix',"ffmpeg......")
  • @SebastianKing:我对ffmpeg 很熟悉,但我没有在您的问题中看到任何可以告诉我为什么您需要实时查看输出的内容。该输出是供控制台使用的,如果您通过程序运行转换,那么您肯定只需要知道每个文件是否已成功转换?
  • 这是一个用于更新当前行的“技巧”。 // 如果他们不替换它们,他们就不能使用&lt;&gt;。 (这很好;他们可以改用read。) // 这不是使用unbuffer 的方式。
  • @SebastianKing:当然,但我的问题仍然存在,你为什么要查看“实时统计数据”?不是有人可以跳进去,修复源视频,让火车继续运行。这不就像看洗衣机旋转一样吗?
  • @Borodin 我的实时统计数据保持不变,我会以某种方式找到解决方案。 unbuffer 不能通过管道传送到,正确的用法是unbuffer &lt;bin&gt; &lt;args&gt;。当我这样做时,我会得到一个实时输出,直到回车部分,在编码完成之前我没有输出。这就是我有tr 的原因,但现在我们看到它似乎确实存在问题。

标签: perl ffmpeg


【解决方案1】:

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

这可以使用unbuffer -p 修复,它用于取消缓冲管道命令(&lt;cmd1&gt; | unbuffer -p &lt;cmd2&gt; 取消缓冲cmd1cmd2)。

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

#!/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
}

由于这个问题在 cmets 中得到了回答,并且没有发布正确的答案,因此我将其设为 wiki 答案,应归功于 @Borodin

【讨论】:

  • 谢谢。我打算写一个解决方案,但这比我写的要有用得多。您应该尽快接受您的回答,将问题标记为已关闭。
  • 好吧,这对我不起作用,虽然我是直接从 bash 而不是 perl 运行的,而且我正在管道到 sed,而不是 tr。
【解决方案2】:

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

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

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

很遗憾,... | unbuffer ... 不起作用。 (我不知道为什么。)所以通过使用sysread 而不是&lt;&gt; 来摆脱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&gt;&amp;1 | unbuffer tr '\r' '\n' 不会给我任何输出,即使我在shell 中运行它,我什至尝试echo "hi" | unbuffer tr '\r' '\n' 并且没有得到输出,我做错了什么?使用 echo 命令它不会返回,它只是不输出任何内容并保持运行。
  • 确实如此。 (用echo "hi" | unbuffer perl -pe1 测试以确保它与tr 无关。)查看更新。
猜你喜欢
  • 1970-01-01
  • 2015-08-20
  • 1970-01-01
  • 2011-05-14
  • 1970-01-01
  • 2013-01-05
  • 2023-03-20
  • 1970-01-01
  • 2012-09-20
相关资源
最近更新 更多