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

我在
Windows上尝试使用ffmpeg转换wmv文件(到flv)时遇到了proc_open的问题,但我怀疑在遇到某些情况时我会遇到相同的情况.

基本上我的代码如下:

$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我将使用fgets或stream_select,行为是一致的,这无关紧要).

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

但是(额外的乐趣),设置stream_set_timeout或stream_set_blocking没有任何效果.

因此 – 有人可以确认/否认正在发生的事情,并且如果可能的话,表明我如何能够满足这种情况?我看过PHP错误,并且所有proc_open挂起似乎都已修复.

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

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

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

    $timeout--;
}

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

>我将拥有运行超过一分钟的进程 – 这些进程将被错误地报告为上述类型
>我想知道ffmpeg何时完成转换视频 – 目前我只知道该进程在一分钟后仍在运行,我无法做任何事情来检查是否有任何输出(因为它会挂起PHP) .

此外,我真的不想等待整整一分钟来检查进程(例如 – 从命令行转换给定视频需要< 10s),并且我将拥有需要更多时间进行转换的视频. 来自@Sjon的评论,这里是我正在使用的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的结果是无缓冲和同步的.

最佳答案 这需要一些时间来重现,但我发现了你的问题.您运行的命令在运行时输出一些诊断信息;但它不会输出到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

如果你愿意properly use streams;这不是问题;但你改为调用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,您仍然可以读取进程的输出,并且可能会更快地完成(无需等待不可修改的超时)

点赞