🎬 第十一章 SRS搭建直播平台
📄 一句话概括

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 秒实验

  1. 把 g 从 25 改成 50,猜 1 秒切片会切出什么——只能切出 2 秒一片,因为关键帧两秒才一个。
  2. hls_list_size 设成 3,看旧切片文件会不会被删——索引只留 3 条,文件是否删除要看实现,别指望自动清理。
  3. 把 hls_path 指到一个 HTTP 根目录之外,猜什么现象——切片照写,地址取不到,播放器一直转圈。

常见误区

  1. 「切片长度等于 hls_fragment」——那只是下限,实际由关键帧决定。
  2. 「切片越短总数据越少」——总量差不多,短切片反而头更多。
  3. 「HLS 能到 1 秒以内」——不能,一片加刷新就是下限。
自测三问(点开对答案) 1. 切片只能切在哪种帧上? —— **关键帧(I 帧)**,因为每片必须能独立解码。 2. 为什么设定 2 秒却切出 2.88 秒? —— **切片落在关键帧上,场景切换又插了额外的 I 帧**,节奏被打乱。 3. `hls_path` 配错是什么现象? —— **文件生成了但 HTTP 取不到**,播放器一直转圈。

相关笔记