应使用 proc_open() 启动 ffmpeg 进程并重定向 stderr,配合 -progress pipe:1(≥4.3)获取 key=value 格式实时进度,避免依赖不稳定的时间戳日志解析。

PHP 调用 FFmpeg 时如何实时捕获进度信息
FFmpeg 本身不提供标准的 JSON 或 API 式进度接口,进度只能从 stderr 流中解析时间戳和帧数。PHP 的 exec()、shell_exec() 等函数默认吞掉 stderr,必须显式重定向才能拿到输出。
实操建议:
- 用
proc_open()启动 FFmpeg 进程,分别获取stdout(通常为空)和stderr(含进度日志)的读取句柄 - 命令末尾加上
-progress pipe:1(FFmpeg ≥ 4.3),它会以 key=value 形式向管道输出实时进度,比解析传统日志更稳定 - 若用旧版 FFmpeg(frame=.*?fps=.*?time=.*? 正则匹配
stderr行,但易受日志格式微调影响 - 务必设置
stream_set_blocking($pipes[2], false),否则读取会阻塞,导致 PHP 卡死
PHP 中解析 -progress pipe:1 输出的要点
-progress pipe:1 每次更新会输出多行 key=value,例如 out_time_ms=123456000、progress=end,每组更新以空行分隔。不能简单 fgets() 一行一处理。
实操建议:
- 用
fread($pipes[2], 1024)小块读取,累积到完整段落(遇到连续 \n\n 为止)再解析 - 对每段做
parse_str(str_replace("\n", "&", $chunk), $data),可快速转成关联数组 - 关键字段包括:
out_time_ms(毫秒)、fps、bitrate、total_size;目标时长需提前用ffprobe获取,如ffprobe -v quiet -show_entries format=duration -of csv=p=0 input.mp4 - 注意
out_time_ms可能为 "N/A" 或未初始化,需判空或跳过
前端显示进度条需要后端怎么配合
PHP 是无状态的,一次请求无法持续推送进度。常见错误是试图在单个 HTTP 请求里 while 循环输出进度——浏览器不会逐段渲染,且超时必然中断。
实操建议:
- 后端把进度写入临时文件(如
/tmp/ffmpeg_{$jobId}.json)或 Redis(SET ffmpeg:progress:{$jobId} '{"time":123000,"total":240000}'),每次更新覆盖 - 前端用
fetch()或axios定期轮询(如每 500ms)该 job ID 对应的进度接口,返回 JSON 即可 - 避免用 session 存进度——并发转码时 session 文件锁会导致请求排队
- 进程结束后,记得清理临时文件或设 Redis 过期时间(
EXPIRE ffmpeg:progress:{$jobId} 300)
为什么用 proc_open() 而不是 exec() + ob_flush()
exec() 类函数本质是同步等待结束,无法在运行中读取流;而 ob_flush() 和 flush() 对 CLI SAPI 无效,只在 Web SAPI 下部分生效,且依赖服务器配置(如 Nginx 的 fastcgi_buffering off),极不可靠。
实操建议:
- 坚持用
proc_open(),它提供真正的双向管道控制 - 子进程退出后,必须调用
proc_close()或proc_get_status()显式回收,否则僵尸进程堆积 - 若需超时控制,用
stream_select()配合$pipes[2],而非sleep()循环 - Linux 下注意 PHP 进程的 ulimit -n(文件描述符限制),大量并发转码时可能耗尽
最易被忽略的是 FFmpeg 进程的信号处理:用户刷新页面或关闭连接时,PHP 不会自动 kill 掉正在跑的 FFmpeg 子进程,得靠 job ID 查 pid 再 posix_kill(),否则服务器 CPU 会被悄悄吃满。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











