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

标签 php ffmpeg proc-open

当尝试使用 ffmpeg 将 wmv 文件(转换为 flv)转换为 flv 时,我在 Windows 上遇到了 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 无限期挂起(如果我使用 fgetsstream_select 而不是 stream_get_contents 并不重要>,行为是一致的)。

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

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

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

目前我已经实现了这样的解决方案:

$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 的结果是无缓冲和同步的。

最佳答案

这需要一些时间来重现,但我发现了你的问题。您运行的命令会在运行时输出一些诊断信息;但它不输出到 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; stream_select 没有在 windows 中的 php 中实现,它在手册中是这样说的:

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 附加到您的命令,您仍然可以从进程中读取输出并且它可能会更快地完成(无需等待不可修改的超时)

关于php - 尝试从流中读取时 proc_open 挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31194152/

相关文章:

php - Kohana V3 Auth 模块 user_token 功能

ffmpeg - 我尝试使用 ffmpeg 将屏幕流式传输到多个源并出现错误

PHP:试图让 fgets() 在 CRLF、CR 和 LF 上触发

php - 在php中连接进程的管道

java - 使用 PHP 或 Java 将带有foreignObject 标签的 svg 导出为图像

javascript - 是否可以将js变量写入纯html?

php - Symfony3 : is it possible to change the name of a form?

php - x264 [错误] : baseline profile doesn't support 4:2:2 , 与 ffmpeg php

php - 为什么 exec 和 proc_open 在 IIS 上以不同的用户运行?