PHP不能直接转MP4,只能调用ffmpeg等外部工具处理;禁用file_get_contents()避免内存溢出,应使用readfile()或fopen()+fpassthru()流式输出大文件,并确保HTTP头正确、moov box前置以支持浏览器播放。

PHP 本身不能“转”出 MP4,它只能调用 ffmpeg 处理原始数据、透传已有 MP4 文件,或协调异步任务。所谓“视频转 MP4”,本质是让 PHP 正确调度外部工具并规避自身内存模型的硬伤——超大文件直接进内存必崩。
为什么 file_get_contents + echo 会炸内存
这是最常踩的坑:把 file_get_contents() 用于几百 MB 甚至几 GB 的视频文件,等同于要求 PHP 在用户空间一次性分配等量物理内存。哪怕服务器有 64GB 内存,PHP 进程也会因 memory_limit(默认通常 128M)被强制终止,报错 Allowed memory size exhausted。
- 即使你临时用
ini_set('memory_limit', '-1')关掉限制,也掩盖不了问题本质:PHP 不是为长时大块 I/O 设计的 -
file_get_contents()返回的是完整字符串,后续任何str_replace、json_encode或拼接操作都会触发复制,内存翻倍风险极高 - Web 服务器(如 Nginx)可能在 PHP 还没吐完数据前就因超时或缓冲区满而切断连接,导致客户端拿到截断的 MP4,无法播放
大 MP4 文件输出必须用流式响应
当你要把一个已存在的大 MP4 文件(比如 4GB 的录制回放)发给前端,核心原则是:不加载、不拼接、不缓存,让数据从磁盘直通 socket。
- 绝对禁用
file_get_contents()和echo,改用readfile()—— 它内部调用系统sendfile()(Linux)或TransmitFile()(Windows),绕过 PHP 用户态内存 - 若需更细粒度控制(比如加权限校验、分段限速),则用
fopen()+fpassthru():$fp = fopen('/path/to/big.mp4', 'rb'); if ($fp) { header('Content-Type: video/mp4'); header('Content-Length: ' . filesize('/path/to/big.mp4')); header('Accept-Ranges: bytes'); fpassthru($fp); fclose($fp); } - 务必确保
header()调用前没有任何输出(包括 UTF-8 BOM、空行、echo ''),否则 HTTP 头发送失败,MP4 二进制流开头混入 HTML 字符,文件直接损坏
调用 ffmpeg 生成 MP4 时如何防卡死和溢出
PHP 只是 ffmpeg 的“遥控器”,真正干活的是子进程。如果让它在 Web 请求里干重活,轻则超时白屏,重则 fork 出一堆僵尸 ffmpeg 进程占满 CPU 和内存。
立即学习“PHP免费学习笔记(深入)”;
- 永远不要在
$_POST接收后直接exec('ffmpeg -i ...');必须移交到后台:写入 Redis 队列 → Worker 进程消费 → 调用proc_open()并设置timeout和limit - 用
proc_open()替代shell_exec(),可捕获 stderr 实时日志,并在超时时proc_terminate()强杀子进程,避免孤儿进程堆积 - 对输入源做预检查:用
finfo_file()确认上传文件真是视频,用ffprobe快速读取时长/码率,拒绝明显异常的输入(如 10GB 但只有 2 秒的“视频”) - FFmpeg 命令务必加资源限制参数:
-threads 2控制线程数,-max_muxing_queue_size 1024防止内存堆积,-y自动覆盖避免交互阻塞
真正的大文件场景:别让 PHP 碰原始字节
当视频源是摄像头 RTMP 流、分片上传的 50GB 录像包,或需要实时添加水印/字幕时,PHP 就不该再承担“读-处理-写”的角色。它的定位应是任务编排器。
- 前端分片上传 → 后端仅校验每片 SHA256 + 存入对象存储(如 MinIO/S3)→ 触发异步任务 → Worker 下载分片 →
ffmpeg -f concat合并 + 转码 → 上传结果回对象存储 - 所有大文件 I/O 操作都交给 C 扩展(如
swoole_file)、系统命令或专用服务(如 FFmpeg HTTP API),PHP 只管发指令、查状态、返回 URL - 若必须用 PHP 处理中间数据(如提取关键帧缩略图),优先用
yield生成器流式产出,而不是array累积;用stream_filter_append()在读取时就解码/转换,避免全量缓存
最容易被忽略的一点:MP4 的 moov box 必须在文件开头才能被浏览器正常播放。用 ffmpeg 合成时加 -movflags +faststart,否则即使文件生成成功,前端 Blob 创建的 URL 也无法直接 <video></video> 播放——这个细节和内存无关,但会让整个流程“看起来失败”。



















