最可靠的方式是用 exec() 直接调用系统已安装的 ffmpeg,因其绕过封装层、控制权完全在开发者手中;需确保路径权限、加 -y 参数、捕获完整输出,并针对 mov/flv 特性处理时间戳、moov atom 等问题。

为什么直接用 exec() 调用 ffmpeg 最可靠
PHP 自带的扩展(如 FFMpeg 类库)封装了部分功能,但实际项目中常因版本兼容、依赖缺失或参数透传失败导致转码中断。最稳的方式是绕过抽象层,用 exec() 直接调用系统已安装的 ffmpeg,控制权完全在你手上。
常见错误现象:exec() 返回空字符串、ffmpeg 命令不执行、输出文件无内容——多数是路径权限或 shell 环境限制所致。
- 确保
ffmpeg已全局可执行:which ffmpeg能返回路径(如/usr/bin/ffmpeg),否则在exec()中用绝对路径 - PHP 进程用户(如
www-data)必须对输入文件、输出目录有读写权限;临时目录建议显式指定:-y -threads 2 - 务必加
-y参数自动确认覆盖,否则卡住无响应 - 用
shell_exec()捕获完整输出,便于排查:$output = shell_exec('ffmpeg -i input.mov -c:v libx264 -c:a aac output.mp4 2>&1');
MOV 转 MP4 必须重映射时间戳,否则播放卡顿
MOV 容器默认使用 edit list(编辑列表)管理帧顺序,而 MP4 解析器可能忽略它,导致音画不同步或开头黑屏几秒。单纯复制流(-c copy)无法解决,必须重新编码或重写时间戳。
推荐方案是强制重置 PTS/DTS:-vsync vfr -avoid_negative_ts make_zero,再配合关键帧对齐。
- 安全做法:用
-c:v libx264 -c:a aac全量转码,兼容性最强 - 若需快速流转(如预览),加
-fflags +genpts强制生成新时间戳,避免依赖源文件 edit list - 注意 MOV 的音频编码常为
alac或qdm2,MP4 不支持,必须转成aac,否则ffmpeg会静默跳过音频流
FLV 转 MP4 需手动补全关键帧和元数据
FLV 文件通常缺少 moov atom(MP4 的元数据头),直接转码后可能无法拖动、快进失效,甚至被某些播放器拒绝加载。这不是 PHP 的问题,而是容器格式特性决定的。
ffmpeg 默认把 moov 放在文件末尾,需用 -movflags +faststart 提前写入头部。
- 必须加
-movflags +faststart,否则前端播放器(尤其是移动端)加载缓慢或报错Invalid MP4 atom - FLV 常含 H.264 流但无 B-frame 标记,转码时建议显式指定 profile:
-profile:v baseline,提升兼容性 - 若源 FLV 是 RTMP 推流录制,可能存在非单调时间戳,加
-vsync 1强制帧率同步
PHP 中判断转码是否真正成功,不能只看返回值
exec() 返回空或 0 并不代表视频能正常播放。很多情况下 ffmpeg 写出损坏的 MP4(比如只有 1KB、无音频、关键帧缺失),但退出码仍是 0。
必须验证输出文件有效性:是否存在、大小是否合理、能否解析出流信息。
- 检查文件大小:
if (filesize($output_path) - 用
ffprobe验证流结构:ffprobe -v quiet -show_entries stream=codec_type,width,height,duration -of default $output_path 2>/dev/null - 关键字段必须存在:
codec_type=video和codec_type=audio都要出现,且duration> 0 - 不要依赖
get_headers()或is_file(),它们无法识别损坏的 MP4 文件头
真正麻烦的是时间戳错位和音频丢帧——这些只能靠抽帧比对或播放测试发现,自动化校验成本高,上线前务必用 VLC 和 iOS Safari 实测几个典型样本。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











