直接拼接MP4会失败,因MP4的moov atom通常位于文件末尾,拼接后元数据丢失或错位,导致播放卡顿或报错;安全拼接需所有视频为faststart格式且编码参数完全一致。

为什么直接用 ffmpeg 命令拼接 MP4 会失败?
因为 MP4 是基于 moov atom 的容器格式,大多数编码器(如 x264)默认把关键元数据(如时长、轨道信息)写在文件末尾。直接用 cat 或 copy /b 拼接,会导致第二个文件的 moov 被丢弃或错位,播放时卡在第一个片段结尾,或者报错 moov atom not found。
真正能安全拼接的前提是:所有输入视频必须是「faststart」格式(即 moov 在开头),且编码参数(分辨率、帧率、profile、level、keyint)完全一致。
- 用
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4预处理每个源文件 - 检查是否对齐:运行
ffprobe -v quiet -show_entries stream=width,height,r_frame_rate,codec_name,profile -of csv=p=0 input.mp4,逐项比对输出 - 若帧率不一致(如 25 vs 30),不能用
-c copy,必须重编码统一为-r 30并加-vsync cfr
PHP 中调用 ffmpeg 合并多个视频的可靠方式
别用 exec() 拼接字符串传参——路径含空格、特殊字符时极易出错;也别依赖 shell_exec() 的返回值判断成功,FFmpeg 错误常写在 stderr 而非 stdout。
推荐用 proc_open() 捕获完整 stderr,并设置超时与资源限制:
立即学习“PHP免费学习笔记(深入)”;
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$process = proc_open(
'ffmpeg -f concat -safe 0 -i /tmp/list.txt -c copy -movflags +faststart /tmp/output.mp4',
$descriptors,
$pipes,
'/tmp',
[],
['timeout' => 300]
);
if (!is_resource($process)) { /* 启动失败 */ }
$status = proc_get_status($process);
if ($status['exitcode'] !== 0) {
$error = stream_get_contents($pipes[2]);
// 解析 $error 中是否含 "Invalid data" 或 "No such file"
}
-
/tmp/list.txt必须是 Unix 换行符(LF),内容形如:file '/tmp/a.mp4'\nfile '/tmp/b.mp4' -
-safe 0允许绝对路径,但确保 PHP 进程有对应目录读取权限 -
-c copy仅当所有视频参数严格一致才可用;否则换成-c:v libx264 -c:a aac并指定-preset fast控制速度
合并不同分辨率/帧率视频时的关键取舍
强行用 -c copy 必然失败;重编码是唯一通路,但代价明确:CPU 占用高、耗时长、画质有损。没有“无损又兼容”的方案。
常见组合策略:
- 统一尺寸:用
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2"居中缩放+黑边填充 - 统一帧率:加
-r 30 -vsync vfr(避免重复帧)或-r 30 -vsync cfr(强制 CFR,适合直播流) - 音频同步:若音视频时长偏差 > 0.5s,需加
-async 1或先用ffmpeg -i in.mp4 -af "adelay=500|500" out.mp4手动对齐 - 关键帧对齐:用
-g 60(GOP=2s @30fps)确保每个片段关键帧位置可预测,减少拼接处花屏
PHP 执行失败却没报错?重点查这三处
FFmpeg 在 CLI 下能跑,PHP 里静默退出,90% 是环境差异导致的——不是代码问题,而是权限、路径或环境变量没对齐。
- 确认 PHP 进程用户(如
www-data)能执行ffmpeg:运行sudo -u www-data which ffmpeg和sudo -u www-data ffmpeg -version - 临时关闭 SELinux 或 AppArmor(尤其 CentOS/Ubuntu Server),它们可能拦截子进程访问 /tmp 或网络设备
- FFmpeg 编译时若带
--enable-libfdk-aac,但系统没装libfdk-aac运行时库,PHP 调用会直接崩溃无日志;改用-c:a aac -aac_coder twoloop规避
最易被忽略的是工作目录和相对路径——proc_open() 的第四个参数(cwd)必须显式设为绝对路径,否则 list.txt 里的文件路径全按 Web 服务器根目录解析。



















