本页目录
流媒体协议
HLS 把视频切成 ts 片段用 HTTP 分发(简单、CDN 友好),DASH 是它的 codec 无关版本。WebRTC 走另一条路——低延迟、P2P、适合互动场景。三种协议在延迟和扩展性之间的不同取舍。
概述
流媒体的本质是一个延迟 vs 规模的权衡。一端是 VOD(点播)和大规模直播:把媒体切成 2-10s 的 segment,用普通 HTTP 分发,借 CDN 缓存推到千万级观众,代价是几秒到几十秒的延迟;另一端是实时互动(视频会议、云游戏):用 RTP/UDP 直接推帧,延迟压到几十毫秒,但每条连接都是有状态的,难以用 CDN 摊开。
理解这条光谱上各协议的位置,比记住它们的字段更重要:
| 延迟 | 协议 | 典型场景 | 分发方式 |
|---|---|---|---|
| 20-40ms | Moonlight / WebRTC (LAN) | 云游戏 / 远程桌面 | P2P / 专线 |
| <500ms | WebRTC | 视频会议 / 互动直播 | SFU(有状态) |
| 0.5-2s | SRT / LL-HLS / LL-DASH | 赛事低延迟直播 / 推流回传 | 边缘 + HTTP |
| 3-10s | HLS / DASH(调优后) | 普通直播 | CDN(HTTP 缓存) |
| 10-30s | HLS / DASH(默认) | 点播 / 大规模直播 | CDN(HTTP 缓存) |
为什么基于 HTTP 的分段协议(HLS/DASH)成了规模直播与点播的主流?因为它把"流"退化成了"一串可缓存的小文件":CDN 不需要理解视频,普通 HTTP 缓存就能工作;穿透防火墙/NAT 零成本(就是 GET);客户端逻辑简单。代价是延迟——延迟基本等于 segment 时长 × 播放器缓冲的 segment 数,这也是后面 LL-HLS/LL-DASH 要解决的核心矛盾。
自适应码率 (ABR) — 分段流的心脏
分段之所以存在,不是为了"方便下载",而是为了在 segment 边界上无缝切换清晰度。服务端为同一内容编码出一条"码率阶梯"(encoding ladder),每条流切成时间对齐的 segment;客户端每下完一两个 segment 就重估网络,决定下一个 segment 取哪一档。
flowchart LR
subgraph ladder["码率阶梯 — segment 时间对齐"]
A["1080p @ 5 Mbps"]
B["720p @ 2.8 Mbps"]
C["480p @ 1.4 Mbps"]
D["360p @ 0.8 Mbps"]
end
P["🎬 <b>播放器 ABR</b><br><small>每 1-2 段重估网络</small>"] ==>|"带宽充裕,缓冲深"| A
P -.->|"网络变差 → 在<br>segment 边界降档"| C
classDef player fill:#4493f826,stroke:#4493f8,stroke-width:2.5px
classDef hi fill:#3fb9501f,stroke:#3fb950,stroke-width:2px
classDef lo fill:#d299221f,stroke:#d29922,stroke-width:2px
classDef rung fill:#64748b14,stroke:#64748b
class P player
class A hi
class C lo
class B,D rung
所有档的 segment N 都覆盖同一时间区间 [t, t+6s),切档只发生在边界上,解码器才能无缝衔接。
ABR 决策有两大流派:
- 基于吞吐(throughput-based):用最近几个 segment 的下载速度估带宽,选一档码率低于估计带宽乘以安全系数。问题在于分段下载是 on-off 模式——下完一段就闲置,朴素测速会系统性低估带宽。
- 基于缓冲(buffer-based,如 BOLA):把"选哪一档"建模成缓冲水位的函数——缓冲深就敢冲高码率,缓冲浅就保守降档防卡顿。
生产实现(dash.js / hls.js)多为两者混合,外加启动期特殊处理(低档快速起播)。ABR 是流媒体体验的真正决定因素:卡顿、画质突变、起播慢,几乎都是 ABR 策略问题,而非协议本身。
HLS
Apple 2009 年发布,RFC 8216 标准化。纯文本 .m3u8 playlist + 媒体 segment(早期 MPEG-TS .ts,现代多用 fMP4/CMAF)。两层清单结构:
Master playlist — 只列各档 variant:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080
high.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720
medium.m3u8
Media playlist — 某一档的 segment 列表:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6 ← segment 最大时长 (取整)
#EXT-X-MEDIA-SEQUENCE:0 ← 首个 segment 的序号 (live 下递增)
#EXT-X-PLAYLIST-TYPE:VOD ← VOD/EVENT, live 则省略
#EXTINF:6.000,
segment-000.ts
#EXTINF:6.000,
segment-001.ts
#EXT-X-ENDLIST ← VOD 专有: 标记流结束
播放流程:取 master → 按带宽/分辨率选 variant → 取其 media playlist → 顺序 fetch segment → 每 1-2 段后重估带宽并可能切档。
VOD vs LIVE 的区别全在 playlist 怎么变:
- VOD:playlist 一次给全,带
EXT-X-ENDLIST,客户端可随意 seek。 - EVENT:只在末尾追加 segment(回看完整历史,如演唱会)。
- LIVE:滑动窗口——新 segment 追加、老 segment 移除,无
ENDLIST;客户端需定时(约每 target duration)重新拉 playlist 才能拿到新段。这个"轮询 playlist"正是 HLS 延迟高的根源之一。
加密:#EXT-X-KEY:METHOD=AES-128,URI="key.bin",IV=0x...,segment 用 AES-128-CBC 加密;SAMPLE-AES 配合 FairPlay/Widevine 做 DRM。
LL-HLS (低延迟 HLS)
两个动作把延迟从 10-30s 压到 1-2s:把 segment 再切成 partial segment(约 200-500ms)边编码边发布;用阻塞式 playlist 重载消掉轮询延迟——客户端请求 playlist?_HLS_msn=5&_HLS_part=2,服务端 hold 住请求,等 part 5.2 真正生成后才返回,轮询空转就没了。
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0
#EXT-X-PART-INF:PART-TARGET=0.33
...
#EXT-X-PART:DURATION=0.33,URI="seg5.0.ts"
#EXT-X-PART:DURATION=0.33,URI="seg5.1.ts"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg5.2.ts" ← 提示下一个 part, 客户端可提前请求
MPEG-DASH
ISO/IEC 23009-1,与厂商和编解码无关(HLS 历史上绑 Apple 生态,DASH 一开始就 codec-agnostic)。清单是 XML 的 MPD (Media Presentation Description):
<MPD type="dynamic" minBufferTime="PT1.5S" ← dynamic=直播, static=点播
profiles="urn:mpeg:dash:profile:isoff-live:2011">
<Period>
<AdaptationSet mimeType="video/mp4" codecs="avc1.640028">
<Representation id="720p" bandwidth="2000000" width="1280" height="720">
<SegmentTemplate timescale="90000" duration="180000" ← 180000/90000 = 2s
media="seg-$Number$.m4s" initialization="init.mp4"/>
</Representation>
<Representation id="1080p" bandwidth="5000000" width="1920" height="1080">...</Representation>
</AdaptationSet>
</Period>
</MPD>
SegmentTemplate 用 $Number$(编号递增)或 $Time$(时间戳)寻址 segment,无需在清单里逐段列出——这是 DASH 比 HLS media playlist 更紧凑的地方。低延迟 DASH(LL-DASH)走 CMAF chunk + HTTP chunked transfer:一个 segment 内部再分 chunk,编码出一个 chunk 就用分块传输立刻推给客户端,不必等整段编完。
CMAF 的意义:HLS 和 DASH 长期各用各的 segment 格式,CDN 要存两份。CMAF (Common Media Application Format, fMP4) 让两者共用同一套媒体 segment,只是清单不同(.m3u8 vs .mpd)→ 存储/缓存减半。这是当下的事实收敛方向。
RTMP/RTMPS — 推流的活化石
Adobe 为 Flash 设计,跑在 TCP 1935 端口。Flash 2020 年寿终,RTMP 作为播放协议已死,但作为推流(ingest)协议至今统治:几乎所有编码器和平台都默认支持它。
flowchart LR
OBS["🎥 OBS / 手机 App<br><small>主播侧 1 路</small>"] -->|"RTMP(S) ingest"| T["⚙️ <b>转码服务</b><br><small>转出多档码率<br>延迟和成本的大头</small>"]
T -->|"打包"| PKG["HLS / DASH"]
PKG --> CDN["🌍 CDN"] ==> V["千万级观众"]
classDef src fill:#64748b1f,stroke:#64748b,stroke-width:2px
classDef hot fill:#d2992226,stroke:#d29922,stroke-width:2.5px
classDef dist fill:#4493f81f,stroke:#4493f8,stroke-width:2px
class OBS src
class T hot
class PKG,CDN,V dist
转码这一段是整条链路延迟和成本的大头。协议本身的要点:
- 握手:C0/C1/C2 ↔ S0/S1/S2,交换版本 + 随机时间戳、校验回声。
- 分块:chunk stream 把 audio/video/metadata 多路复用进一条 TCP 连接(默认 128B chunk)。
- 元数据/RPC:AMF (Action Message Format) 编码
connect/publish/onMetaData等。 - RTMPS = RTMP over TLS,推流到平台时的加密形态。
它仍占据 ingest,靠的是生态惯性而非技术先进。SRT 与 WebRTC 正在这个位置上蚕食它。
WebRTC — 亚秒级实时
为浏览器实时通信而生,是视频会议和互动直播的事实标准,端到端延迟可低于 500ms。它不是单一协议,而是一套栈:
- 信令(带外,自己实现):SDP offer/answer 交换编解码能力与 ICE 候选。
- NAT 穿透:ICE 框架——STUN 问公网"我的外部地址是啥"(多数情况能直连),TURN 做直连失败时的中继兜底(有带宽成本)。
- 加密:DTLS 握手协商密钥 → SRTP 加密媒体。强制加密,没有明文模式。
- 媒体:RTP/RTCP 传音视频;拥塞控制用 GCC(Google Congestion Control),基于延迟梯度而非丢包。
为什么 WebRTC 难像 HLS 那样铺 CDN:每条连接有状态(ICE/DTLS/SRTP 上下文),不是可缓存的文件。规模化只能靠服务端拓扑:
flowchart TB
subgraph mesh ["😵 P2P mesh — N² 条流,仅适合 3-4 人"]
m1((A)) --- m2((B)) --- m3((C)) --- m1
end
subgraph sfu ["✅ SFU — 主流方案"]
s1((A)) -->|"上行 1 路"| S["<b>SFU</b><br><small>只转发不解码</small>"]
s2((B)) --> S
S ==>|"按需下发"| s3((C))
end
subgraph mcu ["💸 MCU — CPU 极贵,渐被弃用"]
u1((A)) --> M["<b>MCU</b><br><small>解码 + 合成一路</small>"]
u2((B)) --> M
M -->|"混流 1 路"| u3((C))
end
classDef good fill:#3fb95026,stroke:#3fb950,stroke-width:2.5px
classDef bad fill:#f851491f,stroke:#f85149,stroke-width:2px
classDef peer fill:#64748b1f,stroke:#64748b
class S good
class M bad
class m1,m2,m3,s1,s2,s3,u1,u2,u3 peer
| 拓扑 | 流数量 | 服务器成本 | 结论 |
|---|---|---|---|
| P2P mesh | N 人互连 N² 条流 | 无服务器 | 3-4 人以上崩溃 |
| SFU(选择性转发) | 每人上行 1 路、按需下发 | 只转发,CPU 低 | 主流,可扩展 |
| MCU(混流) | 每人 1 上 1 下 | 解码合成,CPU 极贵 | 省下行带宽,渐被弃用 |
细节展开见 WebRTC。
SRT — 不可靠网络上的可靠推流
Haivision 开源,基于 UDT(UDP-based Data Transfer),定位是跨公网/广域网的可靠低延迟传输,正在取代卫星和 RTMP 做赛事回传(contribution)。
核心机制是 ARQ(选择性重传)+ 一个可配置的延迟缓冲:发送侧维护重传窗口,接收侧检测丢包后发 NAK,仅重传丢失的包;延迟缓冲(如 120ms)就是给重传留的时间预算——用"多花一点延迟"换"丢包链路上的画质"。这个 latency 旋钮是 SRT 的精髓:链路越烂,调越大。
- 加密:AES-128/256。
- 连接模式:Caller(主动)/ Listener(被动)/ Rendezvous(双向同时发起,用于穿 NAT)。
相比 RTMP(TCP,丢包即队头阻塞、延迟飙升),SRT 在丢包链路上表现稳定得多,这是它做 ingest/contribution 的根本优势。
Moonlight/Sunshine — 云游戏/串流
NVIDIA GameStream 的开源实现(Sunshine 服务端 + Moonlight 客户端),把"实时渲染的游戏画面"当成超低延迟视频流。
flowchart LR
G["🎮 游戏渲染<br><small>5-10ms</small>"] --> E["GPU 编码<br><small>NVENC/AMF/VAAPI<br>5-10ms</small>"]
E ==>|"RTP over UDP<br>+ FEC"| D["客户端解码<br><small>5-10ms</small>"]
D --> V["🖥️ 送显 VSYNC<br><small>5-15ms</small>"]
V -.->|"手柄/键鼠输入、震动反馈"| G
classDef host fill:#d299221f,stroke:#d29922,stroke-width:2px
classDef net fill:#4493f826,stroke:#4493f8,stroke-width:2px
classDef client fill:#3fb9501f,stroke:#3fb950,stroke-width:2px
class G,E host
class D,V client
LAN 理想情况下端到端 20-40ms——低于一帧半的时间。
为什么走 UDP + FEC 而非重传:实时帧的截止时间只有一帧(60fps → 16ms),等一个重传往返必然错过送显,重传回来的帧已经没用了。所以宁可用前向纠错(FEC,Reed-Solomon):多发冗余包,少量丢包直接就地恢复,零往返。再配合帧节奏控制(pacing)平滑送显、避免抖动。
协议选型
| 需求 | 首选 | 关键理由 |
|---|---|---|
| 点播 / 大规模直播 | HLS 或 DASH (CMAF) | CDN 可缓存,成本最低 |
| 苹果生态优先 | HLS | 原生支持最好 |
| 低延迟赛事直播 (1-2s) | LL-HLS / LL-DASH | 保留 CDN,牺牲少量延迟 |
| 视频会议 / 互动 (<500ms) | WebRTC (SFU) | 真实时,但有状态难 CDN |
| 跨公网推流回传 | SRT | 丢包链路上稳,延迟可调 |
| 推流到直播平台 (ingest) | RTMP(兼容)/ SRT | 生态惯性 / 抗丢包 |
| 云游戏 / 远程桌面 | Moonlight / WebRTC | 帧级延迟 + FEC |
参考
- HLS: developer.apple.com/streaming (含 LL-HLS spec)
- DASH: dashif.org · ISO/IEC 23009-1
- WebRTC: webrtc.org · RFC 8825 系列
- SRT: github.com/Haivision/srt
- Sunshine: github.com/LizardByte/Sunshine
- 参考实现: hls.js · dash.js (ABR 算法的工业标准实现, 值得读源码)
Keywords: HLS, LL-HLS, DASH, CMAF, ABR, BOLA, RTMP, WebRTC, SFU, ICE, SRT, ARQ, Moonlight, Sunshine, NVENC, FEC, adaptive bitrate