---
title: 流媒体协议
url: https://doc.liz6.com/networking/10-real-time-communication/02-streaming-protocols
locale: zh
area: networking
tags:
- networking
- 实时通信
date: 2026-06-30
modified: 2026-07-16
description: HLS 把视频切成 ts 片段用 HTTP 分发(简单、CDN 友好),DASH 是它的 codec 无关版本。WebRTC 走另一条路——低延迟、P2P、适合互动场景。三种协议在延迟和扩展性之间的不同取舍。
---

# 流媒体协议

> 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 取哪一档。

```mermaid
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 决策有两大流派：

1. **基于吞吐**（throughput-based）：用最近几个 segment 的下载速度估带宽，选一档码率低于估计带宽乘以安全系数。问题在于分段下载是 on-off 模式——下完一段就闲置，朴素测速会系统性低估带宽。
2. **基于缓冲**（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）**协议至今统治：几乎所有编码器和平台都默认支持它。

```mermaid
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 上下文），不是可缓存的文件。规模化只能靠服务端拓扑：

```mermaid
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](/networking/10-real-time-communication/01-WebRTC.md)。

## 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 客户端），把"实时渲染的游戏画面"当成超低延迟视频流。

```mermaid
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*
