关于php:proc_open在尝试从流中读取时挂起

proc_open hangs when trying to read from a stream

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

1
2
3
4
5
6
7
8
9
10
$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无限期挂起(如果使用fgetsstream_select代替stream_get_contents,则行为是一致的,这并不重要)。

造成这种情况的原因(我怀疑)是,尽管STDOUT流已成功打开,但该进程未向其中写入任何内容(即使在cmd中运行了相同的命令也会显示输出),因此,试图从该流中读取内容,将会导致与此处所述相同的问题,因此-PHP等待流中包含任何内容,进程未向其中写入任何内容。

但是(额外的乐趣),设置stream_set_timeoutstream_set_blocking无效。

这样-有人可以确认/否认正在发生的事情,并在可能的情况下显示我如何应对这种情况?我看过PHP错误,所有proc_open hangs错误似乎都已修复。

我暂时已经实现了这样的解决方案:

1
2
3
4
5
6
7
8
9
$timeout = 60;
while (true) {
    sleep(1);

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

    $timeout--;
}

但是,我真的不想依赖这样的东西:

  • 我将运行超过一分钟的进程-这些进程将被错误地报告为上述类型
  • 我想知道ffmpeg何时完成了视频转换-目前,我仅会知道该过程在一分钟后仍在运行,并且我真的不能做任何事情来检查是否有任何输出(因为它将挂起PHP)。
  • 另外,我真的不想等待一整分钟的时间来检查该过程(例如,从命令行转换给定的视频需要不到10秒的时间),而我将需要花费更多时间来转换视频。

    根据@Sjon的评论,这是我使用的stream_select,由于相同的问题而被阻止-未将STDOUT写入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    $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上的缓冲流读取均已中断。最后的解决方案是通过外壳使用流重定向,然后读取创建的文件-这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $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");

    当流被缓冲时,在文件中,我们将获得无缓冲的输出(我也是这样做的)。而且,由于外壳程序的结果是无缓冲且同步的,因此我们不需要检查文件是否更改。


    复制花了一些时间,但我发现了您的问题。您运行的命令在运行时会输出一些诊断信息。但它不会输出到stdout,而是输出到stderr。 man stderr中对此原因进行了解释:

    Under normal circumstances every UNIX program has three streams opened for it when it starts up, one for input, one for output, and one for printing diagnostic or error messages

    如果您可以正确使用流;这不是问题。但是您改为调用stream_get_contents($pipes[1])。这将导致PHP等待stdout的输出,但它永远不会到达。此修复很简单;取而代之的是从stderr stream_get_contents($pipes[2])中读取,该脚本将在过程结束后立即退出

    要扩展对问题的添加stream_select;在php的Windows中未实现stream_select,它在手册中这样说:

    Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.

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