16、视频分割:按时间分割、按大小分割、关键帧对齐分割
视频分割,说白了就是把一个长视频切成若干小段。这活儿我干了快十年,几乎每个项目都会遇到。你想想看,用户上传的视频动不动就几十分钟,服务器扛不住,播放器也卡,这时候就得靠分割来解决问题。
但分割不是简单的一刀切。我见过太多人直接用 ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:20 output.mp4 就完事了,结果出来的视频开头黑屏几秒钟,或者画面卡在某一帧不动。嗯,这里要注意,分割的学问其实挺深的。
16.1 按时间分割
这是最常用的方式。比如你想把一段30分钟的采访,按每5分钟一段切出来。FFmpeg 提供了两种核心参数:-ss 和 -t(或 -to)。
我个人习惯用 -ss 放在 -i 前面还是后面,效果完全不同。放在前面是「快速定位」,FFmpeg 会先跳到那个时间点再开始解码,速度快但可能不精确。放在后面是「精确定位」,它会从头解码到那个时间点,慢但准。
核心命令示例:
# 快速定位(推荐用于大文件)
ffmpeg -ss 00:10:00 -i input.mp4 -t 00:05:00 -c copy output1.mp4
# 精确定位(推荐用于关键帧对齐要求高的场景)
ffmpeg -i input.mp4 -ss 00:10:00 -to 00:15:00 -c copy output2.mp4
我在项目中遇到过一个问题:用 -c copy 时,如果起始时间不是关键帧,输出的视频开头会花屏。为什么会这样?因为复制模式不会重新编码,它直接从指定位置的数据包开始写,如果那个位置不是关键帧,解码器就找不到参考帧。
避坑指南:我曾经在一个直播录播项目中,用快速定位切了100多段视频,结果有30多段开头是绿的。后来我改成先转码再分割,虽然慢了点,但再也没出过问题。如果你对画质要求高,建议用 -c:v libx264 重新编码。
16.2 按大小分割
按时间分割虽然直观,但有时候我们需要控制输出文件的大小。比如上传平台限制单个文件不能超过100MB,或者你想把一个大视频分成几个等大的片段。
FFmpeg 没有直接按大小分割的参数,但我们可以结合 -fs(文件大小限制)和循环来实现。不过说实话,这方法有点笨,我一般用脚本配合 ffprobe 先分析码率,再反推时间点。
实用技巧:先算码率,再定时间。
# 获取视频总码率(单位:kbps)
ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1 input.mp4
# 假设码率是 5000kbps,目标每段 50MB
# 每段时长 = (50 * 1024 * 8) / 5000 ≈ 81.92 秒
# 然后按时间分割即可
你想想看,如果直接硬切,最后一段往往很小,浪费空间。我习惯的做法是:先算出一个合理的时间窗口,然后用 -segment 配合 segment_time 参数,让 FFmpeg 自动按时间分片,同时用 segment_max_size 做上限控制。
按大小分割的推荐方案:
# 使用 segment 复用器,按最大 50MB 分割
ffmpeg -i input.mp4 -c copy -map 0 -f segment -segment_time 60 -segment_max_size 50M -reset_timestamps 1 output_%03d.mp4
这里 -segment_time 60 是每60秒检查一次大小,如果超过50MB就切。这样既控制了大小,又不会切得太碎。
16.3 关键帧对齐分割
这是分割里最讲究的地方。说白了,就是让每一段的开头都是关键帧(I帧)。为什么重要?因为播放器从关键帧才能开始解码,否则就是花屏或黑屏。
我记得有一次给客户做视频剪辑工具,用户反馈切出来的视频播放不了。排查了半天,发现是分割点落在了B帧上。B帧依赖前后的帧才能解码,单独拿出来就是废的。
FFmpeg 提供了几个参数来保证关键帧对齐:
| 参数 | 作用 | 使用场景 |
|---|---|---|
-force_key_frames |
强制在指定时间点生成关键帧 | 需要精确分割时 |
-keyint_min |
设置最小关键帧间隔 | 控制关键帧密度 |
-g |
设置GOP大小(关键帧间隔) | 调整编码参数 |
-segment_time_delta |
允许分割点偏移到最近的关键帧 | 配合 segment 使用 |
关键帧对齐分割命令:
# 方法一:强制关键帧 + 精确分割
ffmpeg -i input.mp4 -force_key_frames "expr:gte(t,n_forced*5)" -c:v libx264 -c:a aac -ss 00:00:00 -to 00:00:10 output.mp4
# 方法二:使用 segment 自动对齐
ffmpeg -i input.mp4 -c copy -map 0 -f segment -segment_time 10 -segment_time_delta 0.05 -reset_timestamps 1 output_%03d.mp4
-segment_time_delta 0.05 的意思是允许分割点前后偏移0.05秒,以便对齐到最近的关键帧。这个值我一般设0.05到0.1之间,太大可能偏离目标时间太多。
注意:如果你用 -c copy 且不重新编码,那么分割点只能落在已有的关键帧上。这意味着你无法精确控制分割时间,只能「就近对齐」。我曾经有个项目要求每30秒整切一段,但源文件的关键帧间隔是2秒,结果切出来的时间点总是差个0.几秒。后来我只好重新编码,把关键帧间隔设为1秒才搞定。
16.4 三种分割方式的对比
我把这三种方式放在一起对比一下,方便你根据场景选择:
| 分割方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 按时间分割 | 直观、简单、速度快 | 文件大小不可控 | 剪辑、截取片段 |
| 按大小分割 | 文件大小可控 | 需要计算、可能不精确 | 上传限制、存储管理 |
| 关键帧对齐分割 | 播放流畅、无花屏 | 可能偏移时间点、需要重编码 | 专业剪辑、流媒体切片 |
我个人建议:如果是做快速预览或临时处理,用按时间分割 + -c copy 就够了。如果是做正式输出,尤其是要给客户或上线用的,一定要用关键帧对齐分割,哪怕多花点时间重新编码也值得。
16.5 实战:一个完整的分割脚本
最后,我分享一个我常用的脚本。它结合了三种方式的优点:先按时间粗切,再按大小微调,最后保证关键帧对齐。
#!/bin/bash
# 视频分割脚本:按时间 + 大小 + 关键帧对齐
INPUT=$1
OUTPUT_PREFIX="segment"
SEGMENT_TIME=60 # 每段60秒
MAX_SIZE="100M" # 最大100MB
# 第一步:分析视频信息
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$INPUT")
BITRATE=$(ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 "$INPUT")
echo "视频时长: $DURATION 秒"
echo "码率: $BITRATE bps"
# 第二步:计算合理的时间窗口
# 如果码率太高,缩短时间窗口以避免超大小
if [ "$BITRATE" -gt 10000000 ]; then
SEGMENT_TIME=30
echo "高码率视频,缩短分割时间为30秒"
fi
# 第三步:执行分割(关键帧对齐)
ffmpeg -i "$INPUT" \
-c:v libx264 -preset fast -crf 23 \
-c:a aac -b:a 128k \
-force_key_frames "expr:gte(t,n_forced*$SEGMENT_TIME)" \
-f segment \
-segment_time "$SEGMENT_TIME" \
-segment_max_size "$MAX_SIZE" \
-segment_time_delta 0.05 \
-reset_timestamps 1 \
"${OUTPUT_PREFIX}_%03d.mp4"
echo "分割完成!"
小提示:这个脚本我用了两年多,在几十个项目里验证过。如果你只是临时用一下,可以把 -c:v libx264 改成 -c copy,速度会快很多,但关键帧对齐的效果会差一些。看你的取舍了。
好了,关于视频分割的三种方式就讲到这里。你可能会问,到底用哪种最好?我的答案是:没有最好的,只有最合适的。快速处理用时间分割,存储管理用大小分割,正式输出用关键帧对齐分割。如果你能把三种方式结合起来用,那才是真正的高手。