视频切片

视频切片

本文记录如何使用 FFmpeg 将 MP4 视频切片为 HLS 格式,适用于 Web 播放、点播视频分发、移动端播放等场景。

1. 目标

将一个输入视频:

1
input.mp4

转换为 HLS 点播资源:

1
2
3
4
5
6
index.m3u8
init.mp4
seg_00000.m4s
seg_00001.m4s
seg_00002.m4s
...

其中:

  • index.m3u8 是 HLS 播放列表文件

  • init.mp4 是 fMP4 初始化文件

  • seg_00000.m4sseg_00001.m4s 等是视频切片文件

最终可以通过支持 HLS 的播放器播放 index.m3u8


2. FFmpeg 切片命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ffmpeg -y -i input.mp4 \
-map 0:v:0 -map "0:a:0?" \
-r 30 -fps_mode cfr \
-c:v libx264 -preset veryfast -crf 23 \
-bf 0 -refs 1 \
-g 180 -keyint_min 180 -sc_threshold 0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_flags independent_segments \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "seg_%05d.m4s" \
index.m3u8

3. 参数说明

参数 说明
-y 输出文件已存在时直接覆盖
-i input.mp4 指定输入视频文件
-map 0:v:0 选择第一个视频流
-map "0:a:0?" 选择第一个音频流,? 表示没有音频时不报错
-r 30 输出帧率设置为 30 FPS
-fps_mode cfr 使用恒定帧率 CFR,便于切片和播放兼容
-c:v libx264 使用 H.264 编码
-preset veryfast 编码速度预设,速度较快,压缩率适中
-crf 23 视频质量参数,数值越小质量越高、文件越大
-bf 0 禁用 B 帧,提升兼容性和降低播放延迟
-refs 1 设置参考帧数量为 1,提升兼容性
-g 180 GOP 长度为 180 帧
-keyint_min 180 最小关键帧间隔为 180 帧
-sc_threshold 0 禁用场景切换自动插入关键帧
-pix_fmt yuv420p 使用兼容性最好的像素格式
-c:a aac 音频编码为 AAC
-b:a 128k 音频码率为 128kbps
-f hls 输出格式为 HLS
-hls_time 6 每个切片目标时长为 6 秒
-hls_playlist_type vod 生成点播类型播放列表
-hls_flags independent_segments 每个切片都是独立可解码片段
-hls_segment_type fmp4 使用 fragmented MP4 切片格式
-hls_fmp4_init_filename init.mp4 指定 fMP4 初始化文件名
-hls_segment_filename "seg_%05d.m4s" 指定切片文件命名格式
index.m3u8 输出的 HLS 主播放列表

4. GOP 与切片时长的关系

当前命令中:

1
2
3
-r 30
-g 180
-hls_time 6

含义是:

1
30 FPS × 6 秒 = 180 帧

也就是说:

  • 视频帧率为 30 FPS

  • 每个 HLS 切片目标时长为 6 秒

  • 每 180 帧插入一个关键帧

  • 每个切片基本都从关键帧开始

这样可以保证切片边界稳定,播放器更容易从任意切片开始播放。

如果要改成 4 秒一个切片:

1
30 FPS × 4 秒 = 120 帧

对应参数应调整为:

1
2
3
4
-r 30
-g 120
-keyint_min 120
-hls_time 4

如果要改成 10 秒一个切片:

1
30 FPS × 10 秒 = 300 帧

对应参数应调整为:

1
2
3
4
-r 30
-g 300
-keyint_min 300
-hls_time 10

5. 输出目录示例

建议将输出文件放到单独目录中:

1
mkdir -p output

完整命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ffmpeg -y -i input.mp4 \
-map 0:v:0 -map "0:a:0?" \
-r 30 -fps_mode cfr \
-c:v libx264 -preset veryfast -crf 23 \
-bf 0 -refs 1 \
-g 180 -keyint_min 180 -sc_threshold 0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_flags independent_segments \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "output/seg_%05d.m4s" \
output/index.m3u8

输出结果:

1
2
3
4
5
6
7
output/
├── index.m3u8
├── init.mp4
├── seg_00000.m4s
├── seg_00001.m4s
├── seg_00002.m4s
└── ...

注意:如果发现 init.mp4 没有生成在 output/ 目录,可以将参数改为:

1
-hls_fmp4_init_filename "output/init.mp4"

6. TS 切片版本

如果需要兼容更老的 HLS 播放器,可以使用 .ts 切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
ffmpeg -y -i input.mp4 \
-map 0:v:0 -map "0:a:0?" \
-r 30 -fps_mode cfr \
-c:v libx264 -preset veryfast -crf 23 \
-g 180 -keyint_min 180 -sc_threshold 0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_flags independent_segments \
-hls_segment_filename "output/seg_%05d.ts" \
output/index.m3u8

fMP4 和 TS 的区别:

类型 后缀 特点
fMP4 .m4s 更现代,适合新播放器和 DASH/HLS 统一分发
TS .ts 兼容性更老、更广,但文件开销相对更大

7. 播放测试

可以使用 ffplay 测试:

1
ffplay output/index.m3u8

也可以使用本地 HTTP 服务测试,避免浏览器直接打开文件时出现跨域或路径问题。

进入输出目录:

1
cd output

启动 HTTP 服务:

1
python3 -m http.server 8080

访问:

1
http://localhost:8080/index.m3u8

如果使用网页播放器,可以使用 hls.js 播放。


8. 常见问题

8.1 切片时长不是严格 6 秒

-hls_time 6 是目标切片时长,不一定绝对精确。HLS 通常会在关键帧处切片。

为了让切片接近 6 秒,需要保证:

1
-g = fps × hls_time

例如:

1
2
3
-r 30
-g 180
-hls_time 6

8.2 视频没有声音也能处理吗?

可以。

命令中:

1
-map "0:a:0?"

末尾的 ? 表示音频流可选。如果输入视频没有音频,不会导致命令失败。


8.3 为什么使用 yuv420p

1
-pix_fmt yuv420p

是为了提高播放器兼容性,尤其是浏览器、移动端和部分硬件播放器。


8.4 为什么禁用 B 帧?

1
-bf 0

禁用 B 帧可以:

  • 降低解码复杂度

  • 提升部分播放器兼容性

  • 减少低延迟播放时的问题

但代价是压缩效率可能略低。


8.5 CRF 应该怎么选?

CRF 质量 文件大小
18 很高 较大
20 偏大
23 默认常用 适中
26 较低 较小
28 更低 很小

常用建议:

  • 高质量归档:-crf 18

  • 普通点播:-crf 22~24

  • 节省带宽:-crf 26~28


9. 推荐脚本

可以保存为:

1
slice_hls.sh

脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash

set -e

INPUT="${1:-input.mp4}"
OUTPUT_DIR="${2:-output}"
FPS="${3:-30}"
SEG_TIME="${4:-6}"

GOP=$((FPS * SEG_TIME))

mkdir -p "$OUTPUT_DIR"

ffmpeg -y -i "$INPUT" \
-map 0:v:0 -map "0:a:0?" \
-r "$FPS" -fps_mode cfr \
-c:v libx264 -preset veryfast -crf 23 \
-bf 0 -refs 1 \
-g "$GOP" -keyint_min "$GOP" -sc_threshold 0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-f hls \
-hls_time "$SEG_TIME" \
-hls_playlist_type vod \
-hls_flags independent_segments \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "$OUTPUT_DIR/seg_%05d.m4s" \
"$OUTPUT_DIR/index.m3u8"

echo "HLS 切片完成:$OUTPUT_DIR/index.m3u8"

添加执行权限:

1
chmod +x slice_hls.sh

使用方式:

1
./slice_hls.sh input.mp4 output 30 6

参数含义:

1
./slice_hls.sh 输入文件 输出目录 帧率 切片时长

10. 总结

使用 FFmpeg 生成 HLS 视频切片的关键点:

  • 使用 -f hls 输出 HLS

  • 使用 -hls_time 控制切片目标时长

  • 使用 -g-keyint_min 控制关键帧间隔

  • 推荐让 GOP 等于 FPS × 切片秒数

  • 使用 -hls_playlist_type vod 生成点播列表

  • 使用 -hls_segment_type fmp4 生成 .m4s 切片

  • 如果需要老播放器兼容,可以改用 .ts 切片

当前推荐命令适合生成标准的 HLS 点播资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ffmpeg -y -i input.mp4 \
-map 0:v:0 -map "0:a:0?" \
-r 30 -fps_mode cfr \
-c:v libx264 -preset veryfast -crf 23 \
-bf 0 -refs 1 \
-g 180 -keyint_min 180 -sc_threshold 0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_flags independent_segments \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "seg_%05d.m4s" \
index.m3u8