好几段视频想拼成一条?彻底搞懂视频合并(MP4 合并实战与坑全解)
拍了一段开场、一段正片、一段结尾,想拼成一条完整视频发出去——看似简单,新手却常遇到"合并后只有第一段能播""中间黑屏""音画错位""顺序乱了"。这篇文章把 MP4 合并的几种方式、能用和不能用的边界、以及所有坑一次讲透,并给出批量脚本和工程级建议。
一、为什么 MP4 不能直接 cat 拼接
TS 流设计上能直接首尾相接(每个包 188 字节自带同步字节),但 MP4 不行。MP4 的索引(moov atom)写在文件头或尾,描述"每一帧数据在哪"。直接 cat a.mp4 b.mp4 > c.mp4 会得到两份 moov,播放器只认第一份,于是第二段直接错乱或播放器认为文件结束。所以 MP4 合并必须借助工具重新组织索引或重编码。
二、方法一:concat demuxer(推荐,满足条件时无损)
如果所有片段编码参数完全一致(同编码、同分辨率、同帧率、同码率、同色彩空间),可以这样:
# 建列表(注意顺序)
cat > list.txt <<'EOF'
file 'part1.mp4'
file 'part2.mp4'
file 'part3.mp4'
EOF
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
-c copy 不重编码,秒级完成,画质零损失。条件是"参数一致",否则报错或播放异常。
三、方法二:concat 滤镜(万能,但重编码)
当片段参数不一致(不同分辨率、不同编码、不同帧率),用滤镜先解码再统一编码:
ffmpeg -i part1.mp4 -i part2.mp4 -i part3.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[outv][outa]" \
-map "[outv]" -map "[outa]" -c:v libx264 -crf 18 -c:a aac output.mp4
n=3 表示 3 段。它会把所有片段重编码成统一参数,兼容性最好,代价是慢、轻微损画质。
四、方法三:先转成统一格式再 demuxer(折中)
片段来自不同设备、参数杂乱时,先各自统一成相同规格,再用方法一无损拼:
# 统一转成相同规格
ffmpeg -i part1.mp4 -c:v libx264 -crf 23 -c:a aac -r 30 -s 1280x720 p1.mp4
# 对 part2/part3 同样处理...
# 再用 concat demuxer 合并
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
适合"参数乱但想保持无损合并"的场景。
五、能不能混不同分辨率/编码
- 同编码不同分辨率:demuxer 会失败,必须滤镜法(它会统一到第一个或指定尺寸,可能缩放);
- 不同编码(H.264 + H.265):demuxer 直接报错,必须滤镜法重编码;
- 不同帧率:建议统一帧率避免跳变或丢帧;
- 不同色彩空间/HDR vs SDR:务必先统一(见色彩空间专题),否则画面异常。
六、音频流数量不一致怎么办
某段有双音轨、某段只有单音轨,concat 滤镜会报错"流数不匹配"。处理:统一音轨数(用 -map 或 pan 合并/丢弃指定轨),再合并:
# 只取每段的第一条音轨,避免数量不一致
ffmpeg -i part1.mp4 -i part2.mp4 \
-filter_complex "[0:v][0:a:0][1:v][1:a:0]concat=n=2:v=1:a=1[outv][outa]" \
-map "[outv]" -map "[outa]" out.mp4
七、批量把目录下所有片段按顺序合并
ls *.mp4 | sed "s/^/file '/;s/$/'/" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
注意文件名排序(自然数排序),避免 1,10,11,2 的乱序。更稳妥用 sort -V 或脚本按数字排序。
八、合并方式对比
| 方式 | 速度 | 画质 | 条件 | 适用 |
|---|---|---|---|---|
| concat demuxer | 最快 | 无损 | 参数完全一致 | 同设备分段 |
| concat 滤镜 | 慢 | 轻微损 | 无要求 | 参数杂乱 |
| 先统一再 demuxer | 中 | 无损 | 可先转统一 | 折中 |
九、常见翻车现场
| 现象 | 根因 | 解法 |
|---|---|---|
| 合并后只有第一段 | 直接 cat 或损坏的 moov | 用 concat demuxer/filter |
| 中间黑屏 | 参数不一致 + demuxer | 改用 filter 重编码 |
| 音画错位 | 音频流数不一致 | 统一音轨数后再合并 |
| 顺序错乱 | 文件名排序问题 | 用自然排序重命名 |
| 合并极慢 | 用滤镜重编码 | 统一参数后用 demuxer 无损 |
十、工程级建议
- 拍摄时统一参数(分辨率/帧率/编码)能从根上避免合并麻烦;
- 合并前先
ffprobe各片段确认参数,再决定用 demuxer 还是 filter; - 大工程用滤镜法虽然慢,但产出最稳,配合
-crf 18画质几乎无损; - 结尾加
faststart(-movflags +faststart)让网络播放秒开。
十一、常见问题
Q:手机拍的几段能直接拼吗? 大概率参数一致,demuxer 能直接拼;不一致就滤镜法。
Q:能不能加转场? 简单淡入淡出可在滤镜里加 fade;复杂转场得用剪辑软件。
Q:合并会损画质吗? demuxer 无损;filter 重编码有轻微损失,用 -crf 18 之类保持高画质。
Q:TS 切片怎么拼? 见专门的 TS 合并专题,TS 可直接 cat 或 demuxer,友好得多。
十二、端到端实战:把 3 段不同设备拍的素材拼成一条
假设 part1 是手机竖拍 1080x1920、part2 是相机 1920x1080、part3 是录屏 1280x720,全要拼成横屏 1280x720:
# 1) 统一规格
ffmpeg -i part1.mp4 -vf "scale=720:1280,pad=1280:720:(ow-iw)/2:(oh-ih)/2" -c:v libx264 -crf 20 -c:a aac p1.mp4
ffmpeg -i part2.mp4 -vf "scale=1280:720" -c:v libx264 -crf 20 -c:a aac p2.mp4
ffmpeg -i part3.mp4 -vf "scale=1280:720" -c:v libx264 -crf 20 -c:a aac p3.mp4
# 2) 列出并合并
printf "file '%s'\n" p1.mp4 p2.mp4 p3.mp4 > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
关键是先统一分辨率/编码/帧率,否则 demuxer 会失败、或滤镜法需要处理尺寸变换。
十三、合并时加转场(基础淡入淡出)
要在两段之间做 0.5 秒交叉淡化,用 xfade:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0][1]xfade=transition=fade:duration=0.5:offset=4.5,format=yuv420p[v]" \
-map "[v]" -map 0:a out.mp4
offset 是转场开始时间(第一段的时长减 duration)。复杂转场(滑动、圆形)xfade 都支持,但注意音频在 xfade 处需单独处理(可用 acrossfade)。
十四、更多 FAQ
Q:合并后时长对不上? 可能是某段被截断或帧率不一致导致丢帧,统一帧率重试。
Q:能不能直接拖进剪辑软件拼? 能,但 ffmpeg 方案适合批量/自动化、无 GUI 环境。
Q:合并会改文件名顺序吗? 不会,顺序由 list.txt 或命令行参数决定,注意自然排序。
Q:TS 切片合并有什么特殊? TS 可直接 cat 或 demuxer,比 MP4 友好,详见 TS 合并专题。
小结
MP4 合并三选法:参数全一致用 concat demuxer(无损最快)、不一致用 concat 滤镜(重编码万能)、折中先统一参数再 demuxer。核心是先看清片段参数是否一致,再决定路数,别上来就 cat。