低延迟直播:LL-HLS 与 WebRTC 直播

逐段拆解直播延迟的来源与分级,讲清传统 HLS 的分片缓冲代价、LL-HLS 的部分分片与阻塞加载、WebRTC 直播的秒开与拥塞响应,并给出 CDN 边缘分发与回源、边缘 SFU 树的大房间观众端扩展、首帧时间与卡顿率指标、直播与会议架构差异的选型依据。

直播产品最常被问到的一个问题是「延迟多少」。这个问题的答案从来不是一个数字,而是一条链路上一串环节的累加:采集、编码、上行、服务端处理、分发、播放器缓冲。每个环节贡献几十毫秒到几秒不等,任何一个环节没优化到位,整体延迟就下不来。

传统的 HLS 用「分片加缓冲」换来了极致的兼容性与 CDN 友好性,代价是 6 到 30 秒的延迟。低延迟直播(LL-HLS)把分片切得更细并用阻塞加载抢时间,能把延迟压到 2 到 4 秒。而 WebRTC 直播彻底放弃分片模型,直接走 RTP,能到 300 毫秒以内,但要付出服务端成本与弱网体验的代价。

本文按「延迟从哪来 → 三种方案怎么省 → 大房间怎么扩 → 指标怎么量」的顺序展开,最后对比直播与会议的架构差异。理解这条链路之后,「延迟多少」这个问题就能被拆成一张可以逐项优化的清单。

目录

  1. 直播延迟的来源与分级
  2. 传统 HLS 的分片与缓冲代价
  3. LL-HLS 的阻塞加载与部分分片
  4. WebRTC 直播的秒开与拥塞响应
  5. 大房间观众端扩展
  6. CDN 边缘分发与回源
  7. 首帧时间与卡顿率指标
  8. 直播与会议的架构差异
  9. 混合架构:WebRTC 上行加 HLS 下行
  10. 延迟与成本的权衡

1. 直播延迟的来源与分级

先把延迟拆开,每一段都有明确的量级与优化手段:

环节典型延迟优化手段
采集与编码缓冲30~100 ms降低编码器 lookahead,关闭 B 帧
上行传输20~200 ms就近接入,BBR 类拥塞控制
服务端处理0~500 ms避免转码,SFU 只转发
分发与回源10~2000 msCDN 边缘命中,减少回源跳数
播放器缓冲0~10000 ms减小目标缓冲,加快追赶速度

延迟分级大致是:传统 HLS 6 到 30 秒,LL-HLS 2 到 4 秒,WebRTC 0.2 到 0.8 秒。值得注意的是播放器缓冲这一项,它在传统 HLS 中往往是最大的一块,而它的大小是为了抗抖动,不是技术限制。

2. 传统 HLS 的分片与缓冲代价

HLS 把直播流切成固定时长的分片(通常 6 秒),播放器下载完整分片后解码播放。延迟的下界是「分片时长 × 缓冲分片数」:

最小延迟 ≈ 分片时长 × (1 + 缓冲分片数) + 编码延迟 + 网络 RTT
        ≈ 6s × (1 + 2) + 0.3s + 0.1s ≈ 18.4s

要降低延迟,最直接的办法是把分片时长从 6 秒降到 1 秒,但这样会让请求数增加 6 倍,且每个分片必须等待完整生成才能发布,编码器必须关闭 lookahead 与 B 帧,画质与压缩率都会下降。

另一个隐性成本是分片边界。播放器必须等到分片完整才能开始解码,这意味着每个分片都引入一次「等待完整」的延迟。分片越短,这个等待越频繁但每次越短;分片越长,等待次数少但每次代价大。这是 LL-HLS 要解决的核心问题。

3. LL-HLS 的阻塞加载与部分分片

3.1 部分分片(Partial Segment)

LL-HLS 的关键改动是把每个分片再切成若干个部分分片(通常 200 到 500 ms),并允许在分片尚未完整时就开始传输。播放器不必等整个分片生成完,拿到第一个部分分片就能开始解码。

传统 HLS:   |------- 6s 分片 -------|------- 6s 分片 -------|
LL-HLS:     |--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|
             200ms 一个部分分片,边生成边下发

3.2 阻塞式播放列表请求

第二部分是阻塞加载(Blocking Playlist Reload):播放器请求播放列表时带上 _HLS_msn 与 _HLS_part 参数,服务端在对应分片尚未生成时挂起请求,一旦生成立刻返回。

GET /live/index.m3u8?_HLS_msn=273&_HLS_part=3
  → 若分片 273 的第 3 个部分分片尚未就绪,服务端保持连接不返回
  → 生成后立即响应,播放器无需轮询

这消除了轮询间隔带来的平均延迟(传统方案里,轮询间隔的一半会成为额外延迟)。配合 HTTP/2 或 HTTP/3 的多路复用,多个阻塞请求不会互相挤占连接。

location /live/ {
    proxy_pass http://origin;
    proxy_buffering off;
    proxy_read_timeout 30s;      # 阻塞请求需要较长的读超时
    add_header Cache-Control "no-cache";
}

3.3 LL-HLS 的实测收益与代价

指标HLSLL-HLS
端到端延迟6~30 s2~4 s
起播时间1~3 s0.5~1.5 s
请求数低高(部分分片 + 阻塞请求)
兼容性全平台需较新的播放器
CDN 友好度极好好(需支持 chunked 回源)

代价主要在 CDN 侧:阻塞请求会长时间占用连接,部分分片的 chunked 回源对边缘节点有额外要求,缓存命中率也会下降。因此 LL-HLS 的 CDN 成本通常比传统 HLS 高 20% 到 50%。

4. WebRTC 直播的秒开与拥塞响应

WebRTC 直播放弃了分片模型,直接用 RTP 传输,播放器拿到第一个关键帧即可解码播放。延迟构成里没有「等待分片完整」这一项,因此能做到 300 毫秒以内。

WebRTC 直播链路:
  主播 --RTP--> 边缘 SFU --RTP--> 观众
  延迟 ≈ 采集编码 60ms + 上行 40ms + 转发 5ms + 下行 40ms + 播放缓冲 100ms
       ≈ 245 ms

4.1 秒开的关键

秒开(首帧时间小于 1 秒)依赖三件事:

  • 关键帧请求:观众加入时立即向发布端请求 IDR 帧,而不是等下一个自然关键帧(可能等 2 秒)。RTCP 的 PLI / FIR 就是干这个的。
  • 就近接入:观众连到地理最近的边缘节点,把下行 RTT 压到 40 ms 以内。
  • 解码器预热:提前创建 RTCPeerConnection 与解码器,用户点击时只做绑定。
// 观众端:提前建连,点击即播放
const pc = new RTCPeerConnection({ iceServers: [...] });
const transceiver = pc.addTransceiver("video", { direction: "recvonly" });

pc.ontrack = ({ streams }) => {
  const video = document.querySelector("#live");
  video.srcObject = streams[0];
  video.play();     // 需要用户手势,因此提前建连、点击即 play
};

// 关键帧请求由接收端自动触发 RTCP PLI,服务端 SFU 收到后向发布者转发
// 若使用 mediasoup 等 SFU,可在 Consumer 侧显式请求:
//   await consumer.requestKeyFrame();
// 浏览器端无需手工调用,但要在 SFU 侧确认 PLI 被正确转发

4.2 拥塞响应

WebRTC 直播的观众通常只订阅不发布,因此拥塞控制主要体现在下行。SFU 会根据观众的带宽估计选择 Simulcast 层或 SVC 层,这一机制与会议场景完全相同,细节见 弱网对抗与自适应码率 。

直播场景与会议的区别在于:观众数量远大于发布者,且观众对画质下降的容忍度更低(看的是内容而非交流)。因此策略上应该优先降帧率而非降分辨率,避免文字与细节糊掉。

5. 大房间观众端扩展

一场 10 万人观看的直播,不可能给每人一条独立的 RTP 连接。观众端扩展有三条路径:

方案延迟成本适用规模
SFU 级联< 500 ms高数千人
WebRTC 转 HLS2~30 s低无上限
边缘 SFU 树< 800 ms中高数万到数十万

5.1 边缘树与回源

最实用的方案是「边缘 SFU 树」:源节点只向少量区域节点推送,区域节点再向下游边缘节点分发,边缘节点直接服务观众。每一跳增加约 20 到 50 ms 延迟,三层结构约 100 ms。

发布者 → 源 SFU → 区域 SFU × 8 → 边缘 SFU × 200 → 观众
                  每跳约 30ms,总增量约 60~90ms

关键设计是控制扇出:单个节点的扇出不要超过 20 到 30 路,否则出口带宽会成为瓶颈。按每路 1.5 Mbps 计算,扇出 30 路需要 45 Mbps 稳定出口。

5.2 观众侧的降级

对于绝大多数只观看、不互动的观众,WebRTC 的实时性价值有限,可以降级到 HLS 以降低成本。做法是按用户分层:需要连麦或实时互动的走 WebRTC,纯观看的走 LL-HLS。这种「同源双出」的架构在实践中非常常见。

6. CDN 边缘分发与回源

无论是 LL-HLS 还是 WebRTC 边缘节点,分发效率都取决于缓存与回源策略。核心指标是回源率:

回源率 = 回源请求数 / 边缘总请求数
LL-HLS 目标:< 5%
WebRTC 边缘树:不做缓存,靠节点扇出复用

LL-HLS 的分片一旦生成就内容固定,可以被 CDN 缓存,但阻塞请求不可缓存。因此实践中要把两类请求分开:

  • 部分分片请求:可缓存,设置较短的 max-age(如 2 秒)加 stale-while-revalidate。
  • 播放列表阻塞请求:不可缓存,必须回源,但可以用 HTTP/2 多路复用降低连接开销。
rules:
  - match: "/live/*/part*"
    ttl: 2
    stale_while_revalidate: 5
  - match: "/live/*.m3u8"
    ttl: 0            # 播放列表必须回源
    query_cache: false # 带 _HLS_msn 的请求不缓存

回源链路的协议选择也有影响:HTTP/3 的 QUIC 在弱网与高丢包下明显优于 TCP,能减少回源的排队延迟,其原理见 HTTP/3 与 QUIC 。更详细的边缘缓存与预热策略可参考 CDN 边缘优化 。

7. 首帧时间与卡顿率指标

低延迟直播的体验指标与会议不完全相同,重点在三个:

指标定义目标
首帧时间点击到第一帧渲染< 1 s(WebRTC)/ < 2 s(LL-HLS)
端到端延迟主播说话到观众听到按方案定档
卡顿率卡顿时长占观看时长比< 1%
延迟抖动延迟的方差越小越好

延迟抖动常被忽略但很重要:一场延迟平均 3 秒、方差 5 秒的直播,体验远差于稳定 5 秒延迟的直播。抖动意味着观众之间的进度不一致,互动(弹幕、竞猜)无法对齐。

// 用 getStats 估算端到端延迟:对比 RTP 时间戳与本地墙钟
async function estimateLatency(pc) {
  const stats = await pc.getStats();
  let rtt = 0;
  stats.forEach((r) => {
    if (r.type === "candidate-pair" && r.state === "succeeded") {
      rtt = r.currentRoundTripTime * 1000;
    }
  });
  // 真实端到端延迟还需加上编码缓冲与播放缓冲,通常按 1.5×RTT + 150ms 粗估
  return Math.round(rtt * 1.5 + 150);
}

指标的采集与上报体系可以直接复用 实时音视频的质量监控 中描述的埋点方案,只是维度上要额外区分「直播场次」与「观众分层」。

8. 直播与会议的架构差异

很多人以为直播就是「人多的会议」,两者在架构假设上其实差别很大:

维度会议直播
交互性双向,人人可发言单向为主,少量连麦
订阅关系每人订阅其余所有人观众只订阅主播
下行路数N-11(或少数几路)
扩展重点上行与转发下行分发
延迟要求双向 < 200 ms单向可放宽
分层策略Simulcast 适配弱网优先保画质
录制全员录制通常只录主播

最重要的差异是下行路数。会议里每人的下行是 N-1 路,直播里观众的下行只有 1 路,这让直播的容量规划简单得多:单机可服务观众数约等于「出口带宽 / 单路码率」。1 Gbps 出口、1.5 Mbps 单路,理论上可支撑约 660 名观众(按 50% 余量约 330 名)。

9. 混合架构:WebRTC 上行加 HLS 下行

实践中最常见的低延迟直播架构是混合式:

主播 --WebRTC(500ms)--> 源 SFU --> 转封装 --> LL-HLS/HLS --> CDN --> 观众
                            |
                            +--WebRTC--> 连麦观众 / 前排观众

这种架构的好处是各取所长:上行用 WebRTC 保证主播侧的低延迟与抗弱网;下行用 HLS 复用成熟 CDN,成本低、扩展性无上限;对少数需要实时互动的观众(连麦嘉宾、主持人)单独走 WebRTC。

转封装是这套架构的关键环节:把 RTP 流的 H.264 直接封装成 fMP4 分片(-c copy 不重新编码),只需处理时间戳与关键帧对齐。转封装进程的 CPU 开销远低于转码,一台机器可以支撑数百路。

10. 延迟与成本的权衡

方案延迟单观众成本兼容性推荐场景
HLS6~30 s极低全平台点播化直播、录播
LL-HLS2~4 s低较新播放器电商、赛事(非互动)
WebRTC 直播0.2~0.8 s高现代浏览器连麦、在线教育、拍卖
混合架构0.5~3 s中好大多数互动直播

一条实用的决策规则:先问「延迟对业务意味着什么」。如果延迟 3 秒不影响转化与互动,LL-HLS 是性价比最高的选择;如果延迟直接影响成交(如拍卖、抢购),就必须上 WebRTC。

权衡取舍

取舍点LL-HLSWebRTC 直播
延迟2~4 s< 1 s
服务端成本低(CDN 缓存)高(每观众一条连接)
弱网表现好(可缓存重试)一般(依赖拥塞控制)
扩展上限几乎无限受 SFU 出口带宽限制
播放器复杂度低(成熟 SDK)中(需处理 ICE、协商)
首帧时间0.5~1.5 s0.3~0.8 s

两者不是替代关系而是分层关系:同一场直播里,绝大多数观众走 LL-HLS,少数互动观众走 WebRTC,是当前最务实的架构。

常见坑清单

  • 把 LL-HLS 的 _HLS_msn 阻塞请求当普通请求缓存,导致观众拿到过期播放列表。
  • 反向代理对播放列表请求开启缓冲,阻塞加载变成「攒够一批才返回」,延迟反而更高。
  • 转封装时重新编码,CPU 成本暴涨且画质二次损失,其实 -c copy 就够了。
  • 观众加入时不请求关键帧,首帧时间被下一个自然 IDR 拖到 2 秒以上。
  • 边缘 SFU 扇出过大,单节点出口带宽成为瓶颈,观众端集体卡顿。
  • 只优化平均延迟而忽略抖动,观众之间进度不一致,互动功能无法使用。
  • 大房间不区分观众类型,所有观众都走 WebRTC,成本失控。
  • 直播场景沿用会议的降级策略,弱网时先降分辨率导致文字糊掉。
  • 用播放器的 readyState 判断首帧,忽略了渲染完成的实际时刻。
  • CDN 回源协议仍用 HTTP/1.1,弱网下回源排队成为延迟瓶颈。

小结

低延迟直播没有银弹,只有分层。延迟的每一段都有对应的优化手段,而优化的性价比依次递减:先干掉播放器缓冲与分片等待,再优化回源与边缘命中,最后才考虑换协议。

三条实用结论:LL-HLS 用部分分片与阻塞加载把 HLS 的延迟压到了 2 到 4 秒,是大多数直播的性价比最优解;WebRTC 直播的价值在于把延迟压到 1 秒以内并保持弱网可用,代价是服务端成本;混合架构通过观众分层同时拿到了两者。

架构选型确定之后,剩下的工作就是测量与回归。首帧时间、卡顿率与延迟抖动这三个指标,配合一套完整的埋点体系,才能让「延迟多少」这个问题的回答从估算变成事实。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「实时通信」更多文章

  1. 实时通信的端到端测试与压测
  2. 信令服务与房间水平扩展
  3. 实时音视频录制与转码