php同步执行ffmpeg超时因max_execution_time默认30秒且web服务器会强制终止进程,导致文件损坏;应改用proc_open后台执行或redis队列异步处理,并添加-movflags +faststart确保moov头在文件开头。

PHP同步执行ffmpeg命令为什么总超时
因为 PHP 默认的 max_execution_time 通常设为 30 秒,而一个 10 分钟的 MP4 转码可能耗时 2–8 分钟(取决于分辨率、码率和服务器负载),远超限制。更关键的是,Web 服务器(如 Nginx + PHP-FPM)在请求超时后会主动 kill 掉整个 PHP 进程,导致 FFmpeg 子进程被中断,生成的 MP4 文件损坏或不完整。
常见错误现象包括:504 Gateway Timeout、file_get_contents(): failed to open stream: HTTP request failed!、生成的 MP4 文件无法播放(moov atom not found 错误)、exec() 返回空数组但 $returnCode 是 127(命令未找到)或 1(执行失败)。
- 不要在 Web 请求中直接调用
exec("ffmpeg -i ...")处理 >30 秒任务 - 不要依赖
set_time_limit(0)—— 它只对 PHP 执行时间有效,不影响 Web 服务器层的超时策略 - 避免用
shell_exec()捕获大量 FFmpeg 日志,容易内存溢出或阻塞输出缓冲
用proc_open()启动后台FFmpeg进程并立即返回
相比 exec(),proc_open() 可显式分离标准输入/输出流,让 FFmpeg 在后台运行且不阻塞 PHP 主线程。关键是配置好描述符并忽略 stdout/stderr,同时用 fflush() 和 fclose() 确保管道关闭,防止子进程僵死。
示例代码片段(省略路径校验与 escapeshellarg):
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$cmd = 'ffmpeg -y -i ' . escapeshellarg($input) .
' -c:v libx264 -preset fast -b:v 1500k ' .
' -c:a aac -b:a 128k ' .
escapeshellarg($output) . ' 2>/dev/null &';
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
// 此刻立即返回响应,FFmpeg 已在后台运行
-
2>/dev/null &是关键:重定向 stderr 并后台运行,否则proc_open()会等待其结束 - 必须手动
fclose()所有管道,否则某些系统下子进程无法真正脱离父进程 - 该方式仍属“伪异步”——没任务队列、无失败重试、无状态跟踪,仅适合低频轻量场景
接入Redis队列实现真异步转码调度
生产环境必须用消息队列解耦上传与转码。Redis 的 LPUSH + BRPOP 组合足够轻量,比 RabbitMQ 更易部署,也避免了数据库轮询的性能损耗。
用户上传后,PHP 只做三件事:保存原始文件 → 构造转码参数数组 → LPUSH 到 redis 队列。Worker 脚本独立运行,持续监听队列并调用 FFmpeg。
- 队列消息结构建议用 JSON:
{"input":"/tmp/u123.mp4","output":"/cdn/123_720p.mp4","preset":"fast","bitrate":"1500k"} - Worker 必须用 CLI 模式运行(
php worker.php),不能走 Web 服务器;它应常驻或由 systemd/cron 拉起 - 务必在 Worker 中检查
ffmpeg -version和目标路径写权限,失败时推入failed_tasks队列供人工干预 - 不要在 Worker 中用
sleep(1)轮询,改用BRPOP queue_name 0实现阻塞式等待,节省 CPU
为什么转码完成却提示“MP4无法播放”
最常见原因是 FFmpeg 输出的 MP4 文件 moov box(元数据头)位于文件末尾,而浏览器/移动端播放器要求它必须在开头。同步调用时若未加 -movflags +faststart,或异步执行后没等 FFmpeg 完全退出就返回,都会导致该问题。
验证方法:ffprobe -v quiet -show_entries format=duration your.mp4 —— 若报错 Invalid data found when processing input,基本可判定 moov 缺失或错位。
- 所有 FFmpeg 命令末尾必须加上
-movflags +faststart,尤其是面向 Web 播放的 MP4 - 异步执行后,不能靠“延时几秒”来判断完成,而要轮询文件大小是否稳定,或用
inotifywait监听文件关闭写入事件 - 生成完成后,用
stat -c "%Y" output.mp4检查 mtime 是否更新,再配合ffprobe验证可读性,双重保险
moov 位置不是玄学,是 HTTP 流式播放的硬性前提;跳过这步,前端再怎么优化加载逻辑也没用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











