5

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

$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]));

stream_get_contents现在,这段代码将导致 PHP 无限期挂起(我是否使用fgetsor来代替并不重要stream_select,行为是一致的)。

其原因(我怀疑)是,虽然 STDOUT 流成功打开,但该进程不会向其写入任何内容(即使在 cmd 中运行相同的命令会显示输出),因此,尝试从此类流中读取,会导致与此处描述的相同的问题,因此 - 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)。

此外,我真的不想等待一整分钟来检查该过程(例如 - 从命令行转换给定的视频需要 <10 秒),而且我会有需要更多时间来转换的视频。


根据@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 的结果是无缓冲且同步的。

4

1 回答 1

4

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

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

如果您会正确使用流;这不是问题;但你stream_get_contents($pipes[1])改为打电话。这导致 PHP 等待来自 stdout 的输出,而该输出永远不会到达。这个修复很简单;而是从 stderr 读取,stream_get_contents($pipes[2])脚本将在进程结束后立即退出

扩展您在问题中添加 stream_select 的内容;stream_select 没有在 php 中的 windows 上实现,它在手册中这样说:

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

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

于 2015-07-07T10:53:36.637 回答