一个 mp4 过来,要压字幕、切片段、抽音轨、转码给不同端播放——图形界面也能点出来,但"批量、可重复、在没桌面的服务器上无人值守跑完",只有命令行这条路。ffmpeg 负责改,ffprobe 负责看。跳过后者直接猜参数,结果通常是切点偏了、画面躺倒、字幕变方块。
顺序:先立起输入输出模型,再用 ffprobe 摸清底账,然后才是操作、批量化与并发。
1. 输入输出模型:-i 只声明输入,输出靠位置区分
ffmpeg 的命令行是一串"选项 + 位置参数",选项只对紧随其后的那个文件生效。理解这一点,"-ss 该放前面还是后面"就不再需要背。
| 成分 | 写法 | 说明 |
|---|---|---|
| 输入 | -i INPUT.mp4 |
可出现多次,每次声明一个输入 |
| 输入专属选项 | 写在对应 -i 之前 |
-ss、-t、-f、-readrate |
| 输出 | 直接给文件名,无前缀 | 最后一个位置参数通常是输出 |
| 输出专属选项 | 写在输出文件名之前 | -c:v、-crf、-y |
| 全局选项 | 任意位置 | -hide_banner、-nostdin、-loglevel |
# 最朴素的一次调用
ffmpeg -i input.mp4 output.mkv
# 两个输入:输入 0 的视频 + 输入 1 的音频
ffmpeg -i video.mp4 -i audio.m4a -map 0:v:0 -map 1:a:0 -c copy merged.mp4
-map 是流选择的核心。不写 -map 时走默认流选择:每个类型挑一条"最佳"流,于是多音轨、多字幕的容器常丢掉你要的那条。
-map 写法 |
含义 |
|---|---|
-map 0 |
第 0 个输入的全部流 |
-map 0:v / -map 0:a |
只挑视频 / 只挑音频 |
-map 0:a:1 |
第 0 个输入的第 1 条音频(下标从 0 起) |
-map -0:s |
排除第 0 个输入的字幕流(负号即排除) |
-map 0:v -map 0:a? |
问号表示该类型不存在也不报错 |
-c 是 -c:v / -c:a / -c:s 的通用写法。-c copy 表示所有流原样复制,此时不能对画面做任何滤镜或缩放。
2. ffprobe 看什么:容器、流、时长、帧率
默认输出是给人看的一坨文本,真正好用的是把结构序列化出来。
# 容器层:格式名、总时长、总码率、元数据
ffprobe -v error -show_format input.mp4
# 流层:每一条流的编码参数
ffprobe -v error -show_streams input.mp4
# 结构化输出,便于脚本解析
ffprobe -v error -print_format json -show_format -show_streams input.mp4
| 想看的东西 | 取值方式 | 常见形态 |
|---|---|---|
| 容器格式 | format.format_name |
mov,mp4,m4a,3gp,3g2,mj2 |
| 时长 | format.duration(秒,字符串) |
123.456000 |
| 视频编码 | streams[].codec_name |
h264 / hevc / av1 |
| 分辨率 | width × height |
可能被旋转元数据翻转 |
| 帧率 | r_frame_rate / avg_frame_rate |
30000/1001 这类分数 |
| 像素格式 | pix_fmt |
yuv420p / yuvj420p |
# 只问视频流的几个关键字段,输出稳定,适合塞进脚本
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt \
-of csv=p=0 input.mp4
# 看旋转元数据:手机竖拍视频几乎都有
ffprobe -v error -select_streams v:0 -show_entries side_data=rotation -of default=nw=1 in.mp4
帧率是分数:30000/1001 约等于 29.97;r_frame_rate 是名义值,avg_frame_rate 是实际平均,可变帧率素材上两者不一致。rotation 出现在 side_data 里,常见 -90、90、180——播放器读到它会把画面转过来,但像素本身没转;一旦这个标记没被写进输出,画面就"躺"下来了。
3. 常用操作:八条命令覆盖八成需求
# 1) 转码:H.264 + CRF 质量模式 + yuv420p 保证兼容
ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k out.mp4
# 2) 截取片段:-ss 在 -i 前为快速定位
ffmpeg -ss 00:01:30 -i in.mp4 -t 20 -c copy clip.mp4
# 3) 抽音轨,不重编码
ffmpeg -i in.mp4 -map 0:a:0 -c:a copy out.m4a
# 4) 抽帧:每秒一张
ffmpeg -i in.mp4 -vf fps=1 frames/frame-%04d.jpg
# 5) 缩放与裁剪
ffmpeg -i in.mp4 -vf "scale=1280:-2" out.mp4
ffmpeg -i in.mp4 -vf "crop=640:480:100:50" out.mp4
# 6) 压字幕:软字幕(可关)与硬字幕(烧进画面)
ffmpeg -i in.mp4 -i sub.srt -map 0 -map 1 -c copy -c:s mov_text soft.mp4
ffmpeg -i in.mp4 -vf "subtitles=sub.srt" -c:a copy hard.mp4
# 7) 拼接:先统一格式,再走 concat 解复用器
printf "file 'a.mp4'\nfile 'b.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
| 需求 | 关键参数 | 备注 |
|---|---|---|
| 压字幕(可开关) | -c:s mov_text |
mp4 容器只认 mov_text |
| 压字幕(烧画面) | -vf "subtitles=file.srt" |
必须重编码视频 |
| 抽帧 | -vf fps=1 |
也可 -vf select='eq(n\,100)' 抽第 100 帧 |
| 缩放 | scale=1280:-2 |
-2 自动算高且保证偶数,-1 不保证 |
| 拼接 | concat 解复用器 |
各段编码参数须一致,否则先统一转码 |
| 只换容器 | -c copy |
最快,但受关键帧与编码兼容性限制 |
4. 转码三要素:编码器、码率控制、分辨率
| 要素 | 可选值 | 影响 |
|---|---|---|
| 编码器 | libx264、libx265、libsvtav1、硬件编码器 |
兼容性 vs 体积 vs 速度 |
| 码率控制 | -crf、-b:v、-qp |
决定文件多大、质量稳不稳 |
| 分辨率 | scale= |
像素量按面积变化,是体积的第一杠杆 |
-crf 是质量优先模式:值越小画质越好、文件越大,常用区间 18–28。它的问题是码率随内容浮动,动作场面可能飙高、静止画面又低得离谱;要落进固定码率约束就得配 -maxrate / -bufsize。
-preset 控制编码速度与压缩效率的取舍,ultrafast → veryslow 依次变慢、压缩率变高,默认 medium;同一 -crf 下换 preset 画质基本不变、体积变化。它只对软件编码器有意义。
5. -c copy:无损、极快,但有硬约束
# 换容器不重编码
ffmpeg -i in.mkv -c copy out.mp4
# 截取时保持无损
ffmpeg -ss 00:05:00 -i in.mp4 -to 00:05:30 -c copy clip.mp4
| 约束 | 表现 | 处理 |
|---|---|---|
| 切点必须落在关键帧 | -c copy 截取会吸附到前一个关键帧,可能提前几秒 |
去掉 -c copy 重编码截取 |
| 编码与容器兼容性 | H.265 塞进老设备只认的 mp4,播放器黑屏 | 换编码器重编码 |
| 字幕格式 | mov_text 以外的字幕塞 mp4 失败 | 先转格式或换 mkv |
| 元数据丢失 | 旋转标记、章节可能不落袋 | 需要时显式处理或先转正像素 |
| 无法应用滤镜 | 任何 -vf / -af 都要重编码 |
去掉 -c copy |
切点偏移不是 bug,是容器结构的必然:非关键帧必须依赖前面的关键帧才能解码,无损复制不能凭空造出关键帧。
6. 批量处理脚本:命名规范 + 失败跳过
#!/usr/bin/env bash
set -uo pipefail
SRC_DIR="input"; DST_DIR="output"; LOG="$DST_DIR/batch.log"
mkdir -p "$DST_DIR"; ok=0; fail=0
for f in "$SRC_DIR"/*.mp4; do
[ -e "$f" ] || continue # 目录为空时跳过
base=$(basename "$f" .mp4)
out="$DST_DIR/${base}.h264.crf23.mp4"
[ -s "$out" ] && { echo "skip $out" >> "$LOG"; continue; }
tmp="$out.part"
if ffmpeg -hide_banner -loglevel error -nostdin -y -i "$f" \
-c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p \
-c:a aac -b:a 128k \
"$tmp" >> "$LOG" 2>&1; then
mv -f "$tmp" "$out"; ok=$((ok+1))
else
rm -f "$tmp"; fail=$((fail+1)); echo "FAIL $f" >> "$LOG"
fi
done
echo "done ok=$ok fail=$fail" >> "$LOG"
要点:-nostdin 阻止 ffmpeg 抢标准输入(循环里没有它,任务偶尔会卡住);先判断输出非空可断点续跑;失败时删掉半成品,否则下次会被"已存在"逻辑跳过;输出先写 .part 再 mv,保证磁盘上不出现半截文件。命名建议带"来源 + 处理特征",如 原始名.h264.crf23.1280.mp4,日后能直接看出是哪个参数生成的。
'ffmpeg -hide_banner -loglevel error -nostdin -y -i "$1" -c:v libx264 -crf 23 -c:a aac \
7. 性能:硬件加速的有无与并发度
# 能力自检:本机 ffmpeg 编入了哪些编码器与硬件加速
ffmpeg -hide_banner -encoders | grep -Ei 'nvenc|vaapi|qsv|videotoolbox'
ffmpeg -hide_banner -hwaccels
# 显式指定线程数
ffmpeg -i in.mp4 -c:v libx264 -threads 8 -crf 23 out.mp4
| 维度 | 观察 | 处理方向 |
|---|---|---|
| 软件编码 | 默认只吃一部分核心,libx265 明显更慢 |
调 -threads,或用更快的 -preset |
| 硬件编码 | h264_nvenc、h264_vaapi、h264_videotoolbox 等 |
用上面的自检命令确认是否编入 |
| 解码瓶颈 | 高分辨率解码同样吃 CPU | 配 -hwaccel cuda / -hwaccel vaapi 卸载 |
| 并发度 | 单进程未必吃满核 | 多进程并行,进程数控制在核数 1–2 倍内 |
硬件加速的收益取决于素材、驱动与编码器实现,需在本机用真实素材对比、按当前版本核对,不要照搬别处结论。
8. 排错:六个高频现场
| 现象 | 原因 | 处理 |
|---|---|---|
| 截取起点比预期早 | -c copy 只能从关键帧开始 |
去掉 -c copy 重编码,或把 -ss 放到 -i 之后精确解码定位 |
| 转码后画面躺倒 | 旋转元数据未保留 | 用 transpose 滤镜把像素转正,或显式重建 rotation 元数据 |
| 字幕变方块或乱码 | 字幕文件非 UTF-8,或字体缺失 | 转成 UTF-8;硬字幕用 subtitles=sub.srt:force_style='FontName=字体' |
| 拼接后音画不同步 | 各段参数不一致 | 先统一编码、分辨率、帧率、采样率再 concat |
| 脚本执行到某个文件卡住 | 默认不覆盖输出,等待交互确认 | 加 -y,并配 -nostdin |
9. 边界:什么场景该用剪辑软件
| 场景 | 更适合 | 原因 |
|---|---|---|
| 逐帧精确剪点、多轨混音 | 剪辑软件 | 关键帧约束与滤镜链表达力有限 |
| 批量、无人值守、参数固定 | ffmpeg | 可脚本化、可复现 |
| 抽帧、切封面、生成预览图 | ffmpeg | 一行命令,无需 GUI |
一句话:人要盯画面就用剪辑软件,机器要反复跑就用 ffmpeg。
10. 自测
-ss写在-i前面和后面,行为差别在哪?- 一个 mp4 有两条音轨,用
-c copy转 mkv 后只剩一条,最可能是什么原因? r_frame_rate显示30000/1001,实际帧率是多少?- 用
-c copy截取 30 秒片段,为什么实际时长常常不等于 30 秒? - 批量脚本里为什么建议加
-nostdin? - 手机竖拍视频转码后画面躺倒,该从哪个字段入手排查?