"想从第 38 秒开始剪,导出来却从 35 秒开始。"用 FFmpeg 剪切的人大多数被这个问题坑过。原因往往不是命令写错,而是没搞清楚 -ss 放哪个位置、配不配 -c copy,对精度的意义完全不同。这篇把 seek 和"无损剪辑"的边界讲透。
-ss 放前放后:两种 seek 语义
-ss(seek,快进定位)可以放在输入文件前,也可以放在输出文件前,两者行为不同:
输入 seek(-ss 00:00:38 -i input.mp4):快速跳转到目标位置附近,只认关键帧、不逐帧处理。FFmpeg 2.1 之后,如果后面紧跟重编码,它默认开启 accurate_seek:跳到关键帧后会把目标点之前的多余内容解码出来再丢弃,从而做到帧精确——快,又准,是推荐写法。
输出 seek(-i input.mp4 -ss 00:00:38):把输入从头解码,直到时间戳到达目标才输出。帧精确,但前面每一帧都要解一遍,大视频会非常慢。
| 写法 | 速度 | 精度 |
|---|---|---|
-ss 放输入前(重编码) | 快 | 帧精确 |
-ss 放输出前 | 慢 | 帧精确 |
-ss 放输入前 + -c copy | 快 | 只到关键帧 |
无损剪辑的边界:-c copy 不能帧精确
-c copy 的意思是"不重编码,直接复制已编码的字节",速度快,也避免了二次量化带来的画质劣化。但它有个硬约束:只能从关键帧(I 帧)开始作为片段起点。
H.264 这类长 GOP 编码里,只有少数帧能独立作为解码起点,其他帧都依赖前面的参考帧。假设你想从 10.000s 开始剪,而前一个关键帧在 9.600s——-c copy 只能从 9.600s 切出去,第一帧不是你点的那帧,起点被悄悄"前移"了。误差多少完全取决于关键帧的间隔,不是看视频时长能推出来的。
所以"无损"和"精确"在 -c copy 这里是两件事,常常打架:
- 无所谓差几个关键帧、只求快 →
-ss放输入前 +-c copy - 必须精确到帧 → 重编码
常用命令:
# 快速粗剪,只切开头/结尾,不重编码(起点可能前移到关键帧)
ffmpeg -ss 00:00:38 -i input.mp4 -t 00:00:15 -c copy out.mp4
# 精确剪辑,重编码
ffmpeg -ss 00:00:38.450 -i input.mp4 -t 00:00:14.500 \
-c:v libx264 -crf 20 -preset medium -c:a aac -b:a 192k out.mp4
从中间剪:避免负起始时间戳
从中间 cut 出来时,新视频的起始时间戳可能是负数(相对剪辑起点而言),有些播放器不认,表现为黑屏或无法正确定位。MP4 里可以用 -avoid_negative_ts make_zero 把起点对齐到 0:
ffmpeg -ss 00:00:38 -i input.mp4 -t 30 -c copy -avoid_negative_ts make_zero out.mp4
-t 是时长,-to 是结束时间戳,二选一,别同时用。
拼接:concat demuxer 不是想拼就能拼
站内视频合并默认走 -f concat -c copy,快、无损。但它有个隐藏前提:所有片段必须在编码层面完全一致——编码器、profile、分辨率、帧率、像素格式(yuv420p)、sar、时间基、音频采样率和声道,哪一项对不上都可能出现 Non-monotonous DTS 报错,或拼出来音画错位、后半段无声。
来源不同(不同软件 / 设备录的)的片段,直接拼大概率出问题。正解是先归一化再拼:
# 先把每段统一成同一规格
ffmpeg -i a.mp4 -c:v libx264 -r 30 -s 1920x1080 -pix_fmt yuv420p -c:a aac -ar 48000 -ac 2 a_std.mp4
ffmpeg -i b.mp4 ... b_std.mp4
# 再按清单无损拼接
printf "file 'a_std.mp4'\nfile 'b_std.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4
如果各段实在统一不了,就得用 concat filter 重新编码,接受一次画质损失。
动手试
视频裁剪拖时间轴就能精确切片段;视频合并把几段拼成一个——都在浏览器本地完成,文件不上传。
FAQ
Q:-c copy 就是无损吗? A:对,数据不重编码,避免了二次量化损失。但"画质无损"≠"剪辑精确"——它不是从你点的帧开始的,可能多出开头几秒。这两件事要分开看。
Q:把 -ss 放前面为什么也剪不准?
A:当配了 -c copy 时,-ss 的 seek 会被关键帧限制住,起点只能落在关键帧上。想准就要重编码,或先用精确 seek 定位再 copy。
Q:为什么拼接后半段没声音? A:最常见是各段音频采样率或声道数不一致,concat demuxer 直接拼处理不了这种差异。先把各段统一成同采样率、同声道再拼。