HLS 的三个参数决定切片长度、回看窗口、切片存放位置。 但切片长度不是 hls_fragment 一个人说了算。
11.4.3 讲拉流,这一节讲落盘。 11.5 换成 WebRTC,把延迟压到最低那一档。
一、三个参数
① hls_fragment:一个切片多长,调大切片少、首屏慢。
② hls_window:索引里留几秒,调大回看窗口长、索引大。
③ hls_path:切片写到哪,必须和 http_server 的 dir 对得上。
三个里最容易搞错的是 hls_path:
它和 HTTP 根目录不一致时,切片照样写出来,但访问不到。
表现是「磁盘上有文件,播放器一直转圈」。
配起来就是这样:
vhost __defaultVhost__ {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 10;
hls_window 60;
}
}
二、真切片:切片数掉到五分之一
同一段十秒的画面,把 hls_fragment 从 1 秒调到 5 秒:
切片数从 10 掉到 2,索引从 348 字节掉到 140 字节。
但切片合计几乎没变——画面还是那十秒,只是切成了不同的块。 变的只有边界,不是数据量。
真正变的是延迟:观众必须等一整片写完才能开始播。 所以首屏延迟的下限就是「一片的长度加索引刷新间隔」。 1 秒切片大约 2 秒出画,5 秒切片大约 6 秒出画。 想再快就只能换协议,HLS 这条路到头了。
三、切片长度为什么对不上
设定 1 秒,切出来却是 1.92 秒;设定 2 秒,切出来 2.88 秒。 这不是配置错了,原因在关键帧。
HLS 的切片只能落在关键帧上,这是规范的硬要求: 每一片必须能独立解码,所以开头必须是一个 I 帧。 于是切片长度只能是「关键帧间隔的整数倍」。
而关键帧不止 g 决定:编码器默认开着场景切换检测,
画面一变它就自己插一个 I 帧,把节奏打乱。
把场景切换关掉(-sc_threshold 0),关键帧才严格按 g 出现。
关掉之后再切,三档全部精确对齐:1 秒、2 秒、5 秒。
所以「切片不是我要的长度」的排查顺序是: ① 关键帧间隔能不能整除切片长度;② 场景切换关没关; ③ 最后才怀疑 HLS 的配置。前两步占九成原因。
四、动手 Demo:hls.conf——切片长度换来了什么
只改切片长度切三遍,再把场景切换关掉切三遍, 对比每片的边界到底由什么决定。
def one(frag, sc):
opts = {"hls_time": str(frag),
"hls_list_size": "0",
"hls_segment_filename": seg_path}
c = av.open(idx_path, "w", format="hls", options=opts)
st = c.add_stream("libx264", rate=25)
st.options = {"g": "25", "crf": "23"}
if sc:
st.options["sc_threshold"] = "0" # 关掉场景切换
for i in range(250): # 10 秒
c.mux(st.encode(frame(i)))
c.close()
durs = [float(x[8:].split(",")[0])
for x in open(idx_path) if x.startswith("#EXTINF:")]
return len(durs), durs
for frag in (1, 2, 5):
print(frag, one(frag, False)) # 默认:场景切换开着
print(frag, one(frag, True)) # 关掉之后
实跑输出:
--------------------------------------------------------------------
hls.conf——切片长度换来了什么
--------------------------------------------------------------------
**① 三个参数决定一切**
参数 管什么 调大以后
----------------------------------------------------------
hls_fragment 一个切片多长 切片少、首屏慢
hls_window 索引里留几秒 回看窗口长、索引大
hls_path 切片写到哪 和 HTTP 根目录要一致
**这三个里最容易搞错的是 `hls_path`:**
**它必须和 `http_server` 的 `dir` 对得上,否则切片写出来了却访问不到。**
**② 真按不同 hls_fragment 切一遍**
**同一段 10 秒的画面,只改切片长度,看切出几片。**
hls_fragment 切片数 索引字节 切片合计 首片时长
------------------------------------------------------------------
1 秒 10 348 193264 1.92 秒
2 秒 5 218 192324 2.88 秒
5 秒 2 140 191760 5.76 秒
**切片长度翻五倍,切片数就掉到五分之一,索引也跟着变小。**
**但切片合计几乎没变**——画面还是那 10 秒,只是被切成了不同的块。**
**注意首片时长那一栏:没有一栏等于设定值,全都偏大。**
**设定 1 秒切出 1.92 秒,设定 2 秒切出 2.88 秒。**
**这不是配置写错了,原因在下一段。**
**③ 为什么切片长度对不上设定值**
**HLS 的切片只能落在关键帧上,这是规范的硬要求:**
**每一片必须能独立解码,所以开头必须是一个 I 帧。**
**于是切片长度只能是「关键帧间隔的整数倍」,不会是任意值。**
**而关键帧不止 `g` 决定:编码器默认开着场景切换检测,**
**画面一变,它就自己插一个 I 帧,把节奏打乱。**
**把场景切换关掉(`-sc_threshold 0`),关键帧才会严格按 `g` 出现。**
hls_fragment 每片帧数 切片数 首片时长
-----------------------------------------------------
1 秒 25 帧 10 1.00 秒
2 秒 50 帧 5 2.00 秒
5 秒 125 帧 2 5.00 秒
**关掉之后再切,三档全都精确对齐:1.0 秒、2.0 秒、5.0 秒。**
**切片数一点没变(10 片/5 片/2 片),变的只是每片的边界。**
**所以「切片不是我要的长度」的正确排查顺序是:**
**① 看关键帧间隔是不是能整除切片长度;② 看场景切换有没有关;**
**③ 最后才怀疑 HLS 的配置。**
**这三步里,前两步占九成原因。**
**顺带回答另一个问题:首屏延迟的下限就是「一片的长度 + 索引刷新间隔」。**
**所以 1 秒切片大约 2 秒出画,5 秒切片大约 6 秒出画。**
**想再快就只能换协议,HLS 这条路到头了。**
默认参数下三档全偏大:1.92、2.88、5.76 秒。
关掉场景切换后三档全对齐:1.00、2.00、5.00 秒。
切片数一点没变(10/5/2),变的只是每片的边界。
所以切片长度的真正决定者是关键帧,hls_fragment 只是下限。
30 秒实验
- 把
g从 25 改成 50,猜 1 秒切片会切出什么——只能切出 2 秒一片,因为关键帧两秒才一个。 hls_list_size设成 3,看旧切片文件会不会被删——索引只留 3 条,文件是否删除要看实现,别指望自动清理。- 把
hls_path指到一个 HTTP 根目录之外,猜什么现象——切片照写,地址取不到,播放器一直转圈。
常见误区
- 「切片长度等于
hls_fragment」——那只是下限,实际由关键帧决定。 - 「切片越短总数据越少」——总量差不多,短切片反而头更多。
- 「HLS 能到 1 秒以内」——不能,一片加刷新就是下限。
自测三问(点开对答案)
1. 切片只能切在哪种帧上? —— **关键帧(I 帧)**,因为每片必须能独立解码。 2. 为什么设定 2 秒却切出 2.88 秒? —— **切片落在关键帧上,场景切换又插了额外的 I 帧**,节奏被打乱。 3. `hls_path` 配错是什么现象? —— **文件生成了但 HTTP 取不到**,播放器一直转圈。- ☐ (本章上半完,下一节 11.5.1 编译支持 WebRTC 的 SRS)