直播产品最常被问到的一个问题是「延迟多少」。这个问题的答案从来不是一个数字,而是一条链路上一串环节的累加:采集、编码、上行、服务端处理、分发、播放器缓冲。每个环节贡献几十毫秒到几秒不等,任何一个环节没优化到位,整体延迟就下不来。
传统的 HLS 用「分片加缓冲」换来了极致的兼容性与 CDN 友好性,代价是 6 到 30 秒的延迟。低延迟直播(LL-HLS)把分片切得更细并用阻塞加载抢时间,能把延迟压到 2 到 4 秒。而 WebRTC 直播彻底放弃分片模型,直接走 RTP,能到 300 毫秒以内,但要付出服务端成本与弱网体验的代价。
本文按「延迟从哪来 → 三种方案怎么省 → 大房间怎么扩 → 指标怎么量」的顺序展开,最后对比直播与会议的架构差异。理解这条链路之后,「延迟多少」这个问题就能被拆成一张可以逐项优化的清单。
目录
- 直播延迟的来源与分级
- 传统 HLS 的分片与缓冲代价
- LL-HLS 的阻塞加载与部分分片
- WebRTC 直播的秒开与拥塞响应
- 大房间观众端扩展
- CDN 边缘分发与回源
- 首帧时间与卡顿率指标
- 直播与会议的架构差异
- 混合架构:WebRTC 上行加 HLS 下行
- 延迟与成本的权衡
1. 直播延迟的来源与分级
先把延迟拆开,每一段都有明确的量级与优化手段:
| 环节 | 典型延迟 | 优化手段 |
|---|---|---|
| 采集与编码缓冲 | 30~100 ms | 降低编码器 lookahead,关闭 B 帧 |
| 上行传输 | 20~200 ms | 就近接入,BBR 类拥塞控制 |
| 服务端处理 | 0~500 ms | 避免转码,SFU 只转发 |
| 分发与回源 | 10~2000 ms | CDN 边缘命中,减少回源跳数 |
| 播放器缓冲 | 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 的实测收益与代价
| 指标 | HLS | LL-HLS |
|---|---|---|
| 端到端延迟 | 6~30 s | 2~4 s |
| 起播时间 | 1~3 s | 0.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 转 HLS | 2~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-1 | 1(或少数几路) |
| 扩展重点 | 上行与转发 | 下行分发 |
| 延迟要求 | 双向 < 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. 延迟与成本的权衡
| 方案 | 延迟 | 单观众成本 | 兼容性 | 推荐场景 |
|---|---|---|---|---|
| HLS | 6~30 s | 极低 | 全平台 | 点播化直播、录播 |
| LL-HLS | 2~4 s | 低 | 较新播放器 | 电商、赛事(非互动) |
| WebRTC 直播 | 0.2~0.8 s | 高 | 现代浏览器 | 连麦、在线教育、拍卖 |
| 混合架构 | 0.5~3 s | 中 | 好 | 大多数互动直播 |
一条实用的决策规则:先问「延迟对业务意味着什么」。如果延迟 3 秒不影响转化与互动,LL-HLS 是性价比最高的选择;如果延迟直接影响成交(如拍卖、抢购),就必须上 WebRTC。
权衡取舍
| 取舍点 | LL-HLS | WebRTC 直播 |
|---|---|---|
| 延迟 | 2~4 s | < 1 s |
| 服务端成本 | 低(CDN 缓存) | 高(每观众一条连接) |
| 弱网表现 | 好(可缓存重试) | 一般(依赖拥塞控制) |
| 扩展上限 | 几乎无限 | 受 SFU 出口带宽限制 |
| 播放器复杂度 | 低(成熟 SDK) | 中(需处理 ICE、协商) |
| 首帧时间 | 0.5~1.5 s | 0.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 秒以内并保持弱网可用,代价是服务端成本;混合架构通过观众分层同时拿到了两者。
架构选型确定之后,剩下的工作就是测量与回归。首帧时间、卡顿率与延迟抖动这三个指标,配合一套完整的埋点体系,才能让「延迟多少」这个问题的回答从估算变成事实。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。