PHP无法直接处理大视频加水印,必须用FFmpeg抽帧,PHP仅做单帧叠加;GD内存占用高且易泄漏,推荐Imagick批量处理,关键在资源释放与流程分工。

直接处理大视频文件加水印在 PHP 中不可行——PHP 本身不支持视频帧级操作,所谓“给视频加水印”,实际是抽帧→逐帧加图水印→再编码合成。这个过程必然涉及大量图像加载与 GD/Imagick 资源占用,内存溢出是默认行为,不是配置没调够。
为什么 file_get_contents + GD 加水印会崩在视频帧上
常见错误是把视频当图片处理:用 getimagesize() 判断类型、imagecreatefromjpeg() 尝试加载 MP4 文件,结果直接报错或返回 false;更隐蔽的是先用 FFmpeg 抽帧生成 JPEG 序列,但后续批量循环中仍用 file_get_contents($frame) + imagecreatefromstring(),每帧加载即占几 MB 内存,100 帧轻松突破 512M 限制。
- GD 库加载一张 1920×1080 的 JPEG 帧,解码后内存占用通常达 6–8MB(RGB 三通道 × 像素数)
-
imagecreatefromstring()不校验输入是否真为图像二进制,传入损坏帧或非图像数据会静默失败,后续imagecopy()操作触发致命错误 - 未调用
imagedestroy()释放帧资源,循环中 GD 资源持续累积,memory_get_usage()看不出明显增长,但gd_info()['FreeMemory']会快速见底
必须用 FFmpeg 预处理,PHP 只负责帧级水印
PHP 不是视频处理工具,强行在它里面做缩放、编码、合流只会失控。正确分工是:FFmpeg 负责 I/O 密集型任务(抽帧、编码参数控制、音频流保留),PHP 只做 CPU 密集但轻量的单帧叠加。
- 抽帧命令示例:
ffmpeg -i input.mp4 -vf fps=1 frame_%04d.jpg -y(每秒一帧,避免生成过多) - 用
glob('frame_*.jpg')获取帧列表,**不要**用scandir()后手动过滤,避免隐式加载隐藏文件 - 对每一帧路径,用
fopen($path, 'rb')+fread()流式读取二进制,再传给imagecreatefromstring(),比file_get_contents()内存峰值低 15–20% - 水印 PNG 必须带 alpha 通道,叠加前务必调用
imagealphablending($dst, false)和imagesavealpha($dst, true),否则透明区域变黑块
循环中防内存泄漏的关键操作
即使单帧处理逻辑正确,不做资源清理,100 帧跑完内存也不会回落——GD 图像资源不会被自动 GC,尤其在 CLI 模式下。
立即学习“PHP免费学习笔记(深入)”;
- 每帧处理完必须显式调用
imagedestroy($src)和imagedestroy($watermark),顺序不能反(先销毁水印图,再销毁原图) - 避免在循环内重复
imagecreatefrompng($watermark_path):把它提到循环外,复用同一资源;若需不同透明度,用imagecopymerge()替代imagecopy() - 用
gc_collect_cycles()主动触发垃圾回收,放在每 10 帧处理后,能缓解 GD 内部引用堆积 - 检查
memory_get_peak_usage(true)是否随帧数线性上涨,若上涨斜率不变,说明有资源未释放;若到某帧后突增,大概率是某次imagecreatefromxxx()失败但没exit,后续操作继续往空资源写
真正省内存的替代方案:改用 Imagick 批量接口
GD 是单帧单资源模型,Imagick 支持序列帧原生操作,内存压力小得多,且自带水印合成指令,无需手动计算坐标。
- 加载帧序列:
$imagick = new Imagick(['frame_*.jpg']),内部按需加载,不全驻内存 - 添加水印:
$imagick->compositeImage($watermark, Imagick::COMPOSITE_OVER, $x, $y),支持平铺、缩放、透明度参数 - 导出视频:
$imagick->writeImages('output.webm', true)(注意:仅限无音频的纯视频;有音频需 FFmpeg 合成) - 关键优势:
Imagick对大图 resize 更快,compositeImage比 GD 的imagecopyresampled内存开销低 30%+,且自动管理中间资源
最后提醒:所有“PHP 给视频加水印”的方案,本质都是胶水脚本。真正压测时崩溃点永远不在 PHP 代码行,而在 FFmpeg 进程是否被 kill、临时目录磁盘是否写满、或者 Imagick 缓存区被系统 OOM killer 干掉——这些都得进 dmesg 查,而不是只盯着 PHP 错误日志。



















