php不处理视频编解码,仅调度和权限控制;转码等必须交ffmpeg;调用需proc_open()、设超时、用绝对路径、加-y参数;流式输出易阻塞fpm,应交由nginx或cdn处理,并确保mp4含-movflags +faststart。

PHP 本身不处理视频编解码,只做调度、权限控制和流式响应;所有转码、切片、封装工作必须交给 FFmpeg 等外部工具完成。硬让 PHP 直接读写视频帧,等于拿螺丝刀当电钻用——能动,但会崩。
PHP如何正确调用FFmpeg执行转码任务
PHP 不是音视频引擎,它的角色是“发号施令者”。关键在于用对执行函数、捕获错误、防止阻塞。
-
proc_open()是首选:可控制 stdin/stdout/stderr,实时获取 FFmpeg 进度与错误,避免exec()黑盒超时 - 必须设置超时与资源限制:
set_time_limit(0)不代表放任不管,需配合ulimit -t或容器 CPU 限制防失控 - 输出路径不能写相对路径:
output_720p.mp4很可能写到 webroot 下被直接下载,应写绝对路径如/data/transcode/output_720p.mp4 - FFmpeg 命令中务必加
-y覆盖确认,否则卡在 stdin 等交互输入,PHP 进程永久挂起
为什么PHP直接输出大视频文件会崩
不是代码写得不对,而是 PHP-FPM 进程模型天然不适合长连接流式传输。一个 500MB 视频持续输出 3 分钟,就占住一个 FPM worker,100 个并发=100 个卡死进程。
- 内存不会爆(分块
fread()可控),但连接数会耗尽,新请求排队或 503 -
readfile()看似简洁,实则绕过所有缓冲控制,无法响应Range请求,拖动即失败 -
ob_flush()和flush()必须成对出现,且 Nginx/Apache 需禁用代理缓冲(proxy_buffering off),否则数据攒在 Web 服务器里不往下推
HTTP Range请求支持的三个致命细节
浏览器拖动进度条时发的是 Range: bytes=123456-789012,PHP 若返回 200 而非 206,或 Content-Range 格式错一位,Chrome 就静音播放、Safari 直接报错。
- 必须用
preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)解析,不能靠explode('-', ...)—— 有些客户端发bytes=123-(末尾无数字) -
Content-Range的格式必须严格为bytes 123456-789012/5000000,空格、单位、总长度缺一不可 - 若请求
Range: bytes=999999999-(超出文件大小),不能硬返回 206,应降级为 200 全量响应,或 416 Range Not Satisfiable
高并发下必须绕开PHP处理静态视频流
哪怕你把 Range 支持写得再完美,PHP 进程仍是瓶颈。真正工业级方案,是让它只做鉴权,文件传输交给更擅长的组件。
- Nginx
x-accel-redirect:PHP 校验 token 后 header 返回X-Accel-Redirect: /internal/transcoded/xxx.mp4,Nginx 内部跳转并输出,不经过 PHP 内存 - CDN 回源鉴权:URL 带签名参数(如
?token=abc&exp=1749999999),CDN 校验通过后直取 OSS,PHP 完全不碰文件流 - 绝对不要在 PHP 中用
fopen()+fread()处理 >100MB 的文件——即使逻辑正确,也会因 I/O 等待拖垮整个 FPM 池
最常被忽略的一点:FFmpeg 输出的 MP4 文件,必须包含 moov atom 在前(-movflags +faststart),否则即使 PHP 流式响应正确,浏览器也无法边下边播——它得先读完几MB元数据才敢开始解码。这个 flag 不加,前面所有优化都白搭。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











