视频合成——合成、音频、编码与验证 是做什么的?
FFmpeg 是交付层,不是设计工具。它应接收已设计好的场景,并生成干净的母版及平台编码。
阅读 references/FFMPEG-COOKBOOK.md 获取可直接使用的命令,阅读 scripts/validate.sh 获取交付前检查脚本。
零号规则:先探测。 基于假设的流属性构建滤镜图是导致输出异常的最常见原因。
ffprobe -v error -show_streams -show_format -of json input.mp4
1. 合并前先标准化
不兼容的输入会导致画面抖动、静音、音画不同步和光晕。在进行任何多源合成之前,让每个输入在以下属性上保持一致:
| 属性 | 目标值 |
|---|---|
| 分辨率 | 项目母版分辨率(例如 1920×1080) |
| 像素宽高比 | 1:1 |
| 帧率 | 项目帧率,恒定 |
| 时间基 | 一致 |
| 像素格式 | 交付用 yuv420p,含 Alpha 用 yuva444p10le/PNG |
| 色彩空间/传递函数 | 统一标准;并添加标签 |
| 音频 | 48 kHz,立体声,编码器一致 |
ffmpeg -i in.mp4 -vf "scale=1920:1080:flags=lanczos,fps=30,setsar=1" \
-c:v libx264 -crf 14 -preset fast -pix_fmt yuv420p \
-c:a aac -ar 48000 -ac 2 -b:a 256k normalized.mp4
保持中间件高质量(CRF 12–16 或 ProRes)。只对最终交付版进行一次编码。
2. 拼接
相同编码器、分辨率、帧率、像素格式和音频布局 → 流复制,快速且无损:
printf "file 'shot_01.mp4'\nfile 'shot_02.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp4
其他情况 → 使用滤镜图:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,fps=30,setsar=1[v0]; \
[1:v]scale=1920:1080,fps=30,setsar=1[v1]; \
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 16 -pix_fmt yuv420p -c:a aac final.mp4
切勿对不匹配的输入使用流复制拼接。它会“成功”但产生有问题的播放。
3. 叠加、Alpha 与画中画
# 透明元素叠加到背景上
ffmpeg -i bg.mp4 -i logo.mov -filter_complex \
"[0:v][1:v]overlay=W-w-48:48:format=auto" -c:v libx264 -crf 16 out.mp4
# 画中画,缩放并圆角源
ffmpeg -i main.mp4 -i insert.mp4 -filter_complex \
"[1:v]scale=640:-2[pip];[0:v][pip]overlay=W-w-40:H-h-40" out.mp4
Alpha 规则:
- H.264/MP4 无法携带 Alpha。请使用 PNG 序列、ProRes 4444 或支持 Alpha 的 WebM/VP9。
- 在每个阶段后,在对比鲜明的背景上验证 Alpha。
- 留意暗边缘上预乘 Alpha 与直通 Alpha 导致的光晕;需与源渲染器的约定保持一致。
4. 音频
# 背景音乐,带淡入淡出,并在语音下方自动闪避
ffmpeg -i video.mp4 -i music.mp3 -i voice.wav -filter_complex \
"[1:a]volume=0.35,afade=t=in:st=0:d=1,afade=t=out:st=28:d=2[bed]; \
[2:a]volume=1.0[vo]; \
[bed][vo]sidechaincompress=threshold=0.08:ratio=6:attack=8:release=250[duck]; \
[duck][vo]amix=inputs=2:duration=first:dropout_transition=0[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k out.mp4
# 测量响度,然后标准化
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1:LRA=11:print_format=json -f null -
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1:LRA=11 -c:v copy out.mp4
目标值因平台而异(大多数网络平台为 -14 LUFS,广播电视为 -23/-24)。先测量,再选择。避免对已经达标的文件进行第二次标准化。
5. 字幕
# 烧录(硬字幕)
ffmpeg -i in.mp4 -vf "subtitles=captions.srt:force_style='FontName=Inter,FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H80000000,BorderStyle=3,Outline=1,Shadow=0'" \
-c:v libx264 -crf 18 -c:a copy out.mp4
# 软件字幕(可移动轨道)
ffmpeg -i in.mp4 -i captions.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng out.mp4
检查 RTL(从右到左)语言:使用支持字符 shaping 的字体,正确的 Alignment,并检查渲染后的帧。将字幕保持在安全区域和平台 UI 之上。
6. 不同平台的编码
# 横屏 YouTube,1080p30
ffmpeg -i master.mov -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p \
-profile:v high -level 4.1 -movflags +faststart \
-c:a aac -b:a 192k -ar 48000 out_1080p.mp4
# 竖屏社交平台,1080x1920
ffmpeg -i master.mov -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k out_vertical.mp4
# 高质量中间件,用于进一步合成
ffmpeg -i master.mov -c:v prores_ks -profile:v 3 -pix_fmt yuv422p10le -c:a pcm_s24le master.mov
检查编码器是否存在:ffmpeg -encoders | grep -E "libx264|prores|nvenc"。硬件编码器速度快,但通常每比特质量较低;出货前请对比。
7. 帧、联系表、GIF
# 指定时间点截图
ffmpeg -ss 00:00:03.5 -i in.mp4 -frames:v 1 frame.png
# 联系表
ffmpeg -i in.mp4 -vf "fps=1,scale=320:-1,tile=4x4" -frames:v 1 sheet.jpg
# 高质量 GIF(使用 palettegen/paletteuse)
ffmpeg -i in.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i in.mp4 -i palette.png -lavfi "fps=15,scale=640:-1:flags=lanczos[x];[x][1:v]paletteuse" out.gif
8. 验证(不可跳过)
ffprobe -v error \
-show_entries format=duration,size,bit_rate,format_name \
-show_entries stream=index,codec_type,codec_name,width,height,r_frame_rate,avg_frame_rate,pix_fmt,channels,sample_rate,nb_frames \
-of json final.mp4
自动化检查:
# 黑场片段(意外的黑帧)
ffmpeg -i final.mp4 -vf "blackdetect=d=0.1:pix_th=0.05" -an -f null - 2>&1 | grep blackdetect || true
# 静帧片段(静止画面过长)
ffmpeg -i final.mp4 -vf "freezedetect=n=0.003:d=2" -an -f null - 2>&1 | grep freeze || true
# 音频存在及峰值电平
ffmpeg -i final.mp4 -af volumedetect -f null - 2>&1 | grep -E "mean_volume|max_volume"
然后检查第一帧、最后一帧、每个转场以及文字较多的帧。数字证明文件格式正确,但无法证明画面表现良好。
9. 帧精度陷阱
- VFR 源(屏幕录制、手机视频)会相对 CFR 时间线产生偏移。使用
fps=30标准化并检查时长。 - 差一帧错误:30 fps 的 N 帧正好是 N/30 秒;300 帧的渲染应为 10.0 秒。如果时长为 9.97 秒,则说明丢了一帧。
- 截断的音频比视频短 → 结尾处会出现静音;应有意地填充或裁剪。
- 旋转的手机视频带有显示矩阵;只有在你确定需要时才使用
-noautorotate。 - 编码后的色偏:检查
color_primaries/transfer标签;浏览器预览不是可靠的色彩参考。 - 重复编码会降低质量;请保留高质量母版,并以其为基础进行编码。
10. 交付包
output/
final_1080p.mp4 # 主要文件
final_vertical.mp4 # 平台变体(如有需要)
final_master.mov # ProRes/H.264 CRF 14,保留以供将来编码
thumbnail.png # 1280x720 或 1080x1920
contact-sheet.jpg
captions.srt
project-report.md # 工具版本、设置、已执行的检查
从 ffprobe 报告准确的属性;切勿猜测。