proc_open在尝试从流中读取时挂起

时间:2015-07-02 20:33:51

标签: php ffmpeg proc-open

我在使用proc_open尝试将wmv文件转换为flv时遇到了ffmpeg在Windows上的问题,但我怀疑每当某些条件时我都会遇到相同的情况发生。
基本上我的代码如下:

$descriptorspec = array
(
    array("pipe", "r"),
    array("pipe", "w"),
    array("pipe", "w")
);

$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/project/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/project/Wildlife.flv"', $descriptorspec, $pipes);
var_dump(stream_get_contents($pipes[1]));

现在,这段代码会导致PHP无限期挂起(如果不是stream_get_contents而是使用fgetsstream_select,则行为是一致的,这无关紧要。< / p>

它的原因(我怀疑)是,当STDOUT流成功打开时,该进程不会向其写入任何内容(即使在cmd显示输出中运行相同的命令),因此,尝试从这样的流会引起与here所描述的相同的问题,因此 - PHP等待流中包含任何内容,进程不会向其写入任何内容。

然而(额外的乐趣),设置stream_set_timeoutstream_set_blocking没有任何效果。

因此 - 有人可以确认/否认正在发生的事情,并且如果可能的话,表明我该如何迎合这种情况?我看过PHP错误,所有proc_open hangs似乎都已修复。

暂时我实施了这样的解决方案:

$timeout = 60;
while (true) {
    sleep(1);

    $status = proc_get_status($procedure);
    if (!$status['running'] || $timeout == 0) break;

    $timeout--;
}

但是,我真的不想依赖这样的事情:

  1. 我的流程运行时间超过一分钟 - 此类流程将被错误地报告为上述类型
  2. 我想知道ffmpeg何时完成转换视频 - 目前我只知道该过程在一分钟后仍在运行,我无法做任何事情来检查是否有任何输出(因为它会挂起) PHP)。
  3. 另外,我真的不想等待整整一分钟来检查进程(例如 - 从命令行转换给定的视频需要&lt; 10s),我会有更多时间的视频转换。

    来自@Sjon的评论,这是{I}我正在使用的stream_select,由于同样的问题阻塞了 - STDOUT没有被写入:

    $descriptorspec = array
    (
        array("pipe", "r"),
        array("pipe", "w"),
        array("pipe", "w")
    );
    
    $pipes = array();
    $procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv"', $descriptorspec, $pipes);
    
    $read = array($pipes[0]);
    $write = array($pipes[1], $pipes[2]);
    $except = array();
    
    while(true)
    if(($num_changed_streams = stream_select($read, $write, $except, 10)) !== false)
    {
        foreach($write as $stream)
            var_dump(stream_get_contents($stream));
    
        exit;
    }
    else
        break;
    

    与@Sjon进行对话 - 从Windows上的缓冲流中读取内容已被破坏。最后的解决方案是通过shell使用流重定向,然后读取创建的文件 - 如此

    $descriptorspec = array
    (
        array("pipe", "r"),
        array("pipe", "w"),
        array("pipe", "w")
    );
    
    $pipes = array();
    $procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.mp4" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv" > C:/stdout.log 2> C:/stderr.log', $descriptorspec, $pipes);
    
    proc_close($procedure);
    
    $output = file_get_contents("C:/stdout.log");
    $error = file_get_contents("C:/stderr.log");
    
    unlink("C:/stdout.log");
    unlink("C:/stderr.log");
    

    当流被缓冲时,在文件中我们将得到无缓冲的输出(我之后也是如此)。我们不需要检查文件是否更改,因为shell的结果是无缓冲且同步的。

1 个答案:

答案 0 :(得分:2)

这需要一些时间来重现,但我发现了你的问题。您运行的命令在运行时输出一些诊断信息;但它不会输出到stdout,而是输出到stderr。 man stderr

中解释了这一点的原因
  

在正常情况下,每个UNIX程序在启动时都会为它打开三个流,一个用于输入,一个用于输出,另一个用于打印诊断或错误消息

如果你愿意properly use streams;这不是问题;但是你打电话给stream_get_contents($pipes[1])。这导致PHP等待stdout的输出,它永远不会到来。这个修复很简单;从stderr stream_get_contents($pipes[2])读取,脚本将在进程结束后立即退出

将您的stream_select添加到问题中;在php中的windows上没有实现stream_select,它在手册中也是这样说的:

  

对proc_open()返回的文件描述符使用stream_select()将失败并在Windows下返回FALSE。

所以如果上面发布的代码不起作用;我不确定会发生什么。您是否考虑过放弃您的流解决方案,转而使用简单的exec() - 调用?如果将>%TEMP%/out.log 2>%TEMP%/err.log附加到命令中,您仍然可以读取进程的输出,并且可以更快地完成(无需等待不可修改的超时)