VOD 深度剖析(五):流媒体协议——HLS 和 DASH 到底是怎么工作的
为什么渐进式下载会失败、HLS 两级清单和 DASH MPD 如何工作、CMAF 双清单最佳实践、面向低延迟的 LL-HLS,以及何时该考虑 WebRTC。
这是 VOD 流媒体深度剖析 系列的第 5 篇。
为什么不能直接下载一个 MP4?
最简单的做法:把 video.mp4 放到 HTTP 服务器上,让用户直接下载。这就是渐进式下载(progressive download)。它有几个致命缺陷:
- 如果没有做 faststart,必须等整个文件下载完才能开始播放(参见第 4 篇)。
- 当带宽下降时,播放器无法切换到更低的清晰度——只能一直卡顿缓冲。
- 拖动进度条要在一个大文件上使用 HTTP Range 请求——对 CDN 缓存不友好。
- 你无法为不同设备提供不同的版本(一台老手机拿到的和 4K 电视一样是 1080p)。
解决方案:把视频切成很多个小片段,再写一个”目录文件”告诉播放器接下来该下载什么。这就是流媒体协议。
核心思路
现代流媒体由三个部分组成:
1. Cut the video into small segments
┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ...
│s1│ │s2│ │s3│ │s4│ │s5│
└──┘ └──┘ └──┘ └──┘ └──┘
Each 2-6 seconds
2. Produce a set of segments for each quality level
360p: ┌──┐ ┌──┐ ┌──┐ ...
720p: ┌──┐ ┌──┐ ┌──┐ ...
1080p: ┌──┐ ┌──┐ ┌──┐ ...
3. Write a manifest telling the player where to find everything
manifest:
"360p, 720p, and 1080p available"
"Each tier: seg_001.m4s through seg_100.m4s"
播放器读取清单后:
- 第 1 秒:选择一个保守的档位,开始下载
- 第 2 秒:测量实际吞吐量,决定下一个片段该用哪个档位
- 持续地:带宽好就升档,带宽差就降档,卡顿严重时进一步降档
这个决策过程被称为自适应码率(Adaptive Bitrate,ABR)——将在第 6 篇中讲解。本篇聚焦协议层。
HLS(HTTP Live Streaming)
创造者:Apple,2009 年,随 iOS 3.0 一起发布。
标准:IETF RFC 8216(在 draft-pantos-hls-rfc8216bis 中持续更新)。
两级 M3U8 播放列表
M3U8 是一种 UTF-8 文本文件(扩展的 M3U 格式)。HLS 使用两级结构:
Master Playlist Media Playlist
(top-level directory) (per-tier segment list)
master.m3u8
│
├─► 360p/index.m3u8 ──► 360p/seg1.m4s, seg2.m4s ...
│
├─► 720p/index.m3u8 ──► 720p/seg1.m4s, seg2.m4s ...
│
└─► 1080p/index.m3u8 ──► 1080p/seg1.m4s, seg2.m4s ...
主播放列表(Master Playlist)示例
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.640016,mp4a.40.2"
360p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
1080p/index.m3u8
关键字段:
BANDWIDTH:该档位的最大码率(必填)RESOLUTION:像素尺寸CODECS:RFC 6381 编解码器字符串——告诉浏览器该用哪个解码器
媒体播放列表(Media Playlist)示例(fMP4,VOD)
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="init.mp4"
#EXTINF:4.000,
seg_00001.m4s
#EXTINF:4.000,
seg_00002.m4s
#EXTINF:4.000,
seg_00003.m4s
...
#EXTINF:3.120,
seg_00150.m4s
#EXT-X-ENDLIST
EXT-X-TARGETDURATION:4:片段最大时长为 4 秒EXT-X-PLAYLIST-TYPE:VOD:VOD 模式(相对于 EVENT 或 LIVE)EXT-X-MAP:URI="init.mp4":fMP4 初始化片段的位置EXTINF:4.000,:下一个片段时长为 4 秒EXT-X-ENDLIST:播放列表已完整(VOD 必填;直播中不出现)
多音轨与字幕
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="en",NAME="English",DEFAULT=YES,URI="audio_en/index.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="zh",NAME="中文",URI="audio_zh/index.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",LANGUAGE="en",NAME="English",URI="subs_en/index.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",LANGUAGE="zh",NAME="中文",URI="subs_zh/index.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2",AUDIO="audio",SUBTITLES="subs"
720p/index.m3u8
HLS 的优势:在所有 iOS/Safari 上原生支持、基于 HTTPS(对防火墙友好)、人类可读的文本格式,以及全球最高的市场占有率。
DASH(Dynamic Adaptive Streaming over HTTP)
创造者:MPEG(ISO/IEC 23009-1),2012 年标准化。它被设计为 Apple 私有 HLS 之外的一个开放标准替代方案。
它的清单是一个 XML 文件:.mpd(Media Presentation Description,媒体呈现描述)。
简化的 MPD 示例
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
type="static"
mediaPresentationDuration="PT10M30S"
minBufferTime="PT2S">
<Period>
<AdaptationSet mimeType="video/mp4" codecs="avc1.64001f">
<Representation id="360p" bandwidth="800000" width="640" height="360">
<BaseURL>360p/</BaseURL>
<SegmentTemplate initialization="init.mp4"
media="seg_$Number$.m4s"
timescale="1000"
duration="4000"
startNumber="1"/>
</Representation>
<Representation id="720p" bandwidth="2500000" width="1280" height="720">
<BaseURL>720p/</BaseURL>
<SegmentTemplate initialization="init.mp4"
media="seg_$Number$.m4s"
timescale="1000"
duration="4000"
startNumber="1"/>
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" codecs="mp4a.40.2" lang="en">
<Representation id="audio_en" bandwidth="128000">
<BaseURL>audio_en/</BaseURL>
<SegmentTemplate initialization="init.mp4"
media="seg_$Number$.m4s"
duration="4000"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
关键概念:
- Period:整个呈现内容。电影通常只有一个;插入广告的内容会有多个。
- AdaptationSet:一种媒体类型(视频 / 音频 / 字幕 / 特定语言)。
- Representation:一个 AdaptationSet 内的具体版本(360p、720p、1080p)。
- SegmentTemplate:基于模板的片段 URL——比逐个列出每个片段更紧凑。
HLS vs DASH
| HLS | DASH | |
|---|---|---|
| 清单格式 | M3U8(文本) | MPD(XML) |
| 片段容器 | TS / fMP4(现代) | fMP4 / WebM |
| iOS/Safari | 原生支持 | 需要 MSE(JS) |
| Android | 支持 | 支持 |
| Web(Chrome/Firefox) | 通过 hls.js | 通过 dash.js / Shaka |
| 开放标准 | IETF 标准化中 | ISO/IEC 标准 |
CMAF + 双清单:行业最佳实践
正如第 4 篇所述,CMAF 让 HLS 和 DASH 能够共享同一套 fMP4 片段。
典型的目录结构:
/vod/episode-01/
init.mp4 ← CMAF init segment
seg_00001.m4s ← Shared video data
seg_00002.m4s
seg_00003.m4s
...
hls/
master.m3u8 ← HLS manifest (references shared segments)
360p/index.m3u8
720p/index.m3u8
dash/
manifest.mpd ← DASH manifest (references same segments)
- iPhone 用户 → 拿到
master.m3u8→ 下载seg_*.m4s - Android 用户 → 拿到
manifest.mpd→ 下载同一套seg_*.m4s
CDN 缓存命中率最大化。
延迟问题:为什么直播会滞后 30 秒
传统的 HLS/DASH 有 20~30 秒的启播延迟:
Encoder: [produce 6s segment]──────►
HLS spec: Client waits for 3 segments before playing = 3 × 6 = 18s
Add: Playback buffer + network = 25-30s total
对于体育直播、电商直播和互动直播来说,这个延迟太大了。
LL-HLS(Low-Latency HLS,低延迟 HLS)
Apple 在 2019 年推出的方案。目标:端到端小于 2 秒。
关键技术:
- 部分片段(Partial Segments):把 6 秒的片段切成 200~500 毫秒的”部分(parts)“,让播放器更早拿到数据。
- 阻塞式播放列表重载(Blocking Playlist Reload):播放器带着
?_HLS_msn=X&_HLS_part=Y发起请求,服务器在有新数据之前一直挂起响应(类似长轮询)。 - 预加载提示(Preload Hint):告诉播放器接下来会有什么。
LL-DASH / CMAF-LL
DASH 通过 HTTP 分块传输编码(Chunked Transfer Encoding) 实现同样的效果——编码器每产生一个 CMAF 块(约 300 毫秒)就立即推送,无需等待整个片段完成。
延迟对比
| 协议 | 典型延迟 | 复杂度 |
|---|---|---|
| 传统 HLS(TS,6s × 3) | 20~30 秒 | 低 |
| 现代 HLS(fMP4,4s × 3) | 8~15 秒 | 低 |
| LL-HLS / CMAF-LL | 2~5 秒 | 中 |
| WebRTC | <500ms | 高 |
WebRTC:另一条路
WebRTC 并不是 HLS/DASH 的升级版——它是一种从根本上不同的技术:
| HLS/DASH | WebRTC | |
|---|---|---|
| 传输 | HTTP/HTTPS | UDP + DTLS + SRTP |
| CDN 友好 | 是(HTTP 缓存) | 否(点对点或专用 SFU) |
| 延迟 | 2~30 秒 | <500ms |
| 规模 | 轻松支持数百万并发 | 受限于 SFU 容量 |
| 使用场景 | 电影、VOD、一对多直播 | 视频通话、互动、云游戏 |
VOD 不使用 WebRTC。 本系列聚焦于 HLS/DASH/CMAF。
协议选型指南
What are you building?
│
├── ① Pure VOD
│ → HLS + DASH dual manifest + CMAF fMP4
│
├── ② Standard live streaming (<10s latency OK)
│ → HLS + DASH + CMAF fMP4
│
├── ③ Low-latency live (sports, e-commerce, <3s)
│ → LL-HLS + LL-DASH + CMAF-LL
│
├── ④ Ultra-low latency (interactive, <500ms)
│ → WebRTC or RTMP-over-QUIC
│
└── ⑤ IPTV (carrier set-top boxes)
→ MPEG-TS over UDP/HTTP
动手实践:用 ffmpeg 和 Shaka Packager 生成 HLS + DASH
单档位 HLS(fMP4)
ffmpeg -i input.mp4 \
-c:v libx264 -preset slow -crf 22 -g 96 -keyint_min 96 -sc_threshold 0 \
-c:a aac -b:a 128k \
-f hls \
-hls_time 4 \
-hls_segment_type fmp4 \
-hls_playlist_type vod \
-hls_list_size 0 \
-hls_segment_filename "hls/seg_%04d.m4s" \
hls/index.m3u8
多码率 HLS(一条命令生成三个档位)
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3]; \
[v1]scale=640:360[v1out]; \
[v2]scale=1280:720[v2out]; \
[v3]scale=1920:1080[v3out]" \
-map "[v1out]" -c:v:0 libx264 -b:v:0 800k -maxrate:v:0 850k -bufsize:v:0 1600k \
-map "[v2out]" -c:v:1 libx264 -b:v:1 2500k -maxrate:v:1 2650k -bufsize:v:1 5000k \
-map "[v3out]" -c:v:2 libx264 -b:v:2 5000k -maxrate:v:2 5300k -bufsize:v:2 10000k \
-map 0:a -c:a aac -b:a 128k \
-g 96 -keyint_min 96 -sc_threshold 0 \
-f hls -hls_time 4 -hls_segment_type fmp4 -hls_playlist_type vod -hls_list_size 0 \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0" \
"hls/v%v/index.m3u8"
生产级方案:用 Shaka Packager 生成 CMAF + HLS + DASH
packager \
in=input_360p.mp4,stream=video,init_segment=cmaf/v0/init.mp4,segment_template=cmaf/v0/seg_\$Number\$.m4s \
in=input_720p.mp4,stream=video,init_segment=cmaf/v1/init.mp4,segment_template=cmaf/v1/seg_\$Number\$.m4s \
in=input_1080p.mp4,stream=video,init_segment=cmaf/v2/init.mp4,segment_template=cmaf/v2/seg_\$Number\$.m4s \
in=input_720p.mp4,stream=audio,init_segment=cmaf/a0/init.mp4,segment_template=cmaf/a0/seg_\$Number\$.m4s \
--segment_duration 4 \
--hls_master_playlist_output=cmaf/master.m3u8 \
--mpd_output=cmaf/manifest.mpd
输出:一套片段,同时生成 HLS 和 DASH 两份清单。
核心要点
- 流媒体 = 片段 + 清单 + 由客户端驱动的按需拉取。
- HLS(Apple)使用 M3U8 文本清单;DASH(MPEG)使用 MPD XML。
- iOS/Safari 只原生支持 HLS;其他平台两者都支持。
- CMAF 让 HLS 和 DASH 共享同一套 fMP4 文件——这是行业最佳实践。
- 传统 HLS 延迟为 20~30 秒;LL-HLS / CMAF-LL 可达到 2~5 秒。
- WebRTC 是另一条独立的路径(<500ms)——不适用于 VOD。
- 在生产环境中,使用 Shaka Packager 或云服务(MediaPackage)来完成打包。
上一篇: 第 4 篇:容器格式
下一篇: 第 6 篇:自适应码率流媒体
参考资料
- RFC 8216: HTTP Live Streaming — IETF
- DASH-IF guidelines — DASH Industry Forum
- HTTP Live Streaming — Apple Developer