本页目录

流媒体协议

HLS 把视频切成 ts 片段用 HTTP 分发(简单、CDN 友好),DASH 是它的 codec 无关版本。WebRTC 走另一条路——低延迟、P2P、适合互动场景。三种协议在延迟和扩展性之间的不同取舍。

概述

流媒体的本质是一个延迟 vs 规模的权衡。一端是 VOD(点播)和大规模直播:把媒体切成 2-10s 的 segment,用普通 HTTP 分发,借 CDN 缓存推到千万级观众,代价是几秒到几十秒的延迟;另一端是实时互动(视频会议、云游戏):用 RTP/UDP 直接推帧,延迟压到几十毫秒,但每条连接都是有状态的,难以用 CDN 摊开。

理解这条光谱上各协议的位置,比记住它们的字段更重要:

延迟协议典型场景分发方式
20-40msMoonlight / WebRTC (LAN)云游戏 / 远程桌面P2P / 专线
<500msWebRTC视频会议 / 互动直播SFU(有状态)
0.5-2sSRT / LL-HLS / LL-DASH赛事低延迟直播 / 推流回传边缘 + HTTP
3-10sHLS / DASH(调优后)普通直播CDN(HTTP 缓存)
10-30sHLS / 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 决策有两大流派:

  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)⁠协议至今统治:几乎所有编码器和平台都默认支持它。

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 meshN 人互连 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