尝试从流中读取时 proc_open 挂起

eit*_*hed 5 php ffmpeg proc-open

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]));
Run Code Online (Sandbox Code Playgroud)

现在,此代码将导致 PHP 无限期挂起(如果不是stream_get_contents我将使用fgetsor stream_select,行为是一致的,则无关紧要)。

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

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

因此 - 有人可以确认/否认正在发生的事情吗,如果可能的话,展示我如何应对这种情况?我查看了 PHP 错误,所有错误proc_open hangs似乎都已修复。

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

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

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

    $timeout--;
}
Run Code Online (Sandbox Code Playgroud)

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

  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;
Run Code Online (Sandbox Code Playgroud)

每次与@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");
Run Code Online (Sandbox Code Playgroud)

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

Sjo*_*jon 3

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

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

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

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

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

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