卡死根源在FFmpeg进程阻塞,PHP未及时读取stderr导致假死;须用proc_open持续读stderr、加eval=frame启用动态表达式、降分辨率/线程数防4K内存溢出。

PHP 8.5 本身不会导致加水印卡死,卡死的根源几乎都在 FFmpeg 进程——它被阻塞、等待输入、内存耗尽,或在 filtergraph 表达式里陷入死循环。PHP 只是没及时感知和处理这个状态。
proc_open 没读 stderr 导致 ffmpeg 假死
FFmpeg 在遇到参数错误(比如 overlay 坐标写成 x=w-w-10)、找不到字体、或 alpha 格式不匹配时,会往 stderr 打印警告并可能暂停;但如果你只用 shell_exec() 或没主动读取 stderr 流,PHP 就会一直等它结束,而 FFmpeg 卡在缓冲区满(PIPE full)上不动。
- 必须用
proc_open(),显式声明stdin/stdout/stderr三路流 - 在循环中用
fread($pipes[2], 4096)持续读stderr,哪怕只是丢掉:while (@fread($pipes[2], 1024)) {} - 配合
stream_set_blocking($pipes[2], false)防止阻塞读 - 别依赖
proc_get_status()的running字段——它可能返回 true,但进程早已卡在 I/O 上
filter_complex 表达式里 t 被 eval=init 锁死
贴纸本该上下浮动,结果一动不动,或者只在第 1 帧闪一下就消失——大概率是 overlay 滤镜用了 eval=init(默认值),导致 t(时间戳)只在初始化时算一次,后续帧全用同一个 x/y 值。
- 动态位置必须显式加
eval=frame:overlay=x=W-w-10:y=H-h-10+15*sin(2*PI*t/3):eval=frame - 漏掉
eval=frame是“看起来命令跑通、输出有画面、但动画完全失效”的最常见原因 - 如果表达式里含逗号(如
between(t,1,5)),整个enable字符串必须用单引号包裹,并在 PHP 中用双引号拼接时注意转义:"enable='between(t,1,5)'"
4K 视频 + overlay 导致内存爆满卡住
3840×2160 原始帧单帧约 12 MB,叠加滤镜链再乘以线程数,很容易突破 512 MB。PHP 进程没报错,但系统 OOM Killer 已静默杀掉 ffmpeg 子进程,proc_close() 却还在等一个永远不会回来的退出码。
立即学习“PHP免费学习笔记(深入)”;
- 先用
pmap -x [ffmpeg_pid]实测峰值内存,确认是不是真爆了 - 加
-threads 1强制单线程,立刻降低 60%+ 内存压力 - 前置缩放主视频:
-vf "scale=1920:-2:flags=lanczos",再叠水印,比直接在 4K 上 overlay 省 3 倍内存 - 避免
-shortest和eof_action=repeat同时出现——水印 PNG 被无限 repeat,ffmpeg 缓冲区持续膨胀
真正难调试的卡死,往往发生在 FFmpeg 把错误信息写进 stderr 后就停住,而 PHP 没打开那条管道去“听”它说了什么——不是命令写得不够酷,是少了一行 fread($pipes[2], 4096)。



















