第 5 章 · 流媒体协议
HLS、DASH、CMAF 怎么工作
预计阅读时间:25 分钟
本章你会理解:为什么不能直接下载 MP4、HLS 和 DASH 是怎么工作的、m3u8 / mpd 文件里到底写了什么、LL-HLS 为什么存在。
1
5.1 为什么不能直接下载 MP4 就完事?
最朴素的做法:把 video.mp4 放到 HTTP 服务器,用户浏览器请求 → 下载 → 播放。
这叫渐进式下载(Progressive Download)。它有几个致命缺点:
1. 没做 faststart 时,要下完才能播(见第 4 章)。
2. 网速变差时还是要硬下,不能切低清晰度 → 卡顿。
3. 拖动进度条靠 HTTP Range 头,但一个大文件请求范围 CDN 缓存不友好。
4. 不能根据设备能力给不同版本:iPhone 收到 1080p 只能硬扛。
所以需要更聪明的做法——把视频切成小片、配合一个"目录文件"指挥播放器按需下载。这就是流媒体协议(Streaming Protocol)。
2
5.2 流媒体协议的核心思想
现代流媒体三件套:
1. 把视频切成很多小片(segment)
┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ...
│s1│ │s2│ │s3│ │s4│ │s5│
└──┘ └──┘ └──┘ └──┘ └──┘
每片 2-6 秒
2. 每种清晰度都做一套
360p: ┌──┐ ┌──┐ ┌──┐ ...
720p: ┌──┐ ┌──┐ ┌──┐ ...
1080p: ┌──┐ ┌──┐ ┌──┐ ...
3. 写一个"目录文件"告诉播放器怎么找
manifest:
"有 360p/720p/1080p 三档"
"每档从 s1.m4s 到 s100.m4s"
播放器看到 manifest 后:
• 第 1 秒:选一个保守档位开始下
• 第 2 秒:看实际网速,决定下一片用哪档
• 不停决策:网好升档、网差降档、卡顿就进一步降档
这个"看网况换档"的决策叫自适应码率(ABR),第 6 章专门讲。本章先讲协议本身。
3
5.3 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
(总目录) (每档的片段清单)
master.m3u8
│
├─► 360p/index.m3u8 ──► 360p/seg1.m4s
│ 360p/seg2.m4s ...
│
├─► 720p/index.m3u8 ──► 720p/seg1.m4s
│ 720p/seg2.m4s ...
│
└─► 1080p/index.m3u8 ──► 1080p/seg1.m4s
1080p/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
每行含义:
• #EXTM3U:文件头,必须是第一行
• #EXT-X-VERSION:7:HLS 版本(fMP4 需要 ≥ 7)
• #EXT-X-STREAM-INF:...:声明一个清晰度
- BANDWIDTH=2500000:这一档最大码率 2500 kbps(必填)
- RESOLUTION=1280x720:分辨率
- CODECS="avc1..,mp4a..":编码字符串(RFC 6381),告诉浏览器编码
• 下一行是对应的 Media Playlist URL
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 秒
• seg_00001.m4s:切片文件路径
• #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 传输,穿透防火墙能力强
• 简单文本格式,人类可读
• 全球市场占有率最高
4
5.4 DASH(Dynamic Adaptive Streaming over HTTP)
发明者:MPEG(ISO/IEC 23009-1),2012 年标准化。目的是做一个开放标准对抗 Apple 私有的 HLS。
核心:manifest 是 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"
profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
<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:整个节目。电影通常一个 Period;带广告插入的会分多个 Period。
• AdaptationSet:一类媒体(视频 / 音频 / 字幕 / 某种语言)。
• Representation:同一类媒体的一个具体版本(360p、720p、1080p 分别一个)。
• SegmentTemplate:用模板描述切片文件名,避免列出每个切片(比 HLS 省空间)。
DASH vs HLS 关键区别
|
HLS |
DASH |
| Manifest 格式 |
M3U8(文本) |
MPD(XML) |
| 切片容器 |
TS / fMP4(现代) |
fMP4 / WebM |
| iOS/Safari |
原生支持 |
需要 MSE |
| Android |
支持 |
支持 |
| Web(Chrome/Firefox) |
通过 hls.js |
通过 dash.js / Shaka |
| 智能电视 |
大多支持 |
大多支持 |
| 开放标准 |
IETF 标准化中 |
ISO/IEC 标准 |
| 特色 |
Apple 生态原生 |
更灵活(codec-agnostic) |
5
5.5 CMAF + 双 Manifest:工业界最佳实践
前面在第 4 章讲过 CMAF:一份 fMP4 文件被 HLS 和 DASH 共用。
典型目录结构:
/vod/episode-01/
init.mp4 ← CMAF init segment (fMP4 init)
seg_00001.m4s ← 真正的视频数据
seg_00002.m4s
seg_00003.m4s
...
hls/
master.m3u8 ← HLS manifest (引用上面的切片)
audio/index.m3u8
360p/index.m3u8
720p/index.m3u8
dash/
manifest.mpd ← DASH MPD (引用同样的切片)
播放过程:
• iPhone 用户请求 → 返回 master.m3u8 → 播放器下载 seg_*.m4s
• Android 用户请求 → 返回 manifest.mpd → 播放器下载同样的 seg_*.m4s
CDN 缓存就全命中了。
实际选择:CMAF fMP4 + 双 manifest(HLS + DASH)是当前最佳实践:同一套切片,同时生成 .m3u8 和 .mpd,覆盖所有设备。
6
5.6 延迟问题:为什么直播要 30 秒?
传统 HLS/DASH 起步延迟动辄 20-30 秒。算一下就知道:
编码器: [pack 6s 切片]──────►
HLS 规范: 客户端要看到 3 个切片才开始播 = 3 × 6 = 18s
再加上: 播放缓冲 + 网络传输 = 25-30s
对直播、互动、电商直播这个延迟太高了。
LL-HLS:Apple 的 2019 年方案
LL-HLS(Low-Latency HLS)在 2019 WWDC 发布。目标端到端 < 2 秒。
核心手段:
1. Partial Segment:把 6 秒切片再切成 200-500ms 的小段(partial),让播放器能比整片完成更早拿到数据。
#EXT-X-PART:DURATION=0.250,URI="seg_42.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.250,URI="seg_42.1.m4s"
#EXT-X-PART:DURATION=0.250,URI="seg_42.2.m4s"
#EXTINF:4.000,
seg_42.m4s
2. Blocking Playlist Reload:播放器用 ?_HLS_msn=X&_HLS_part=Y 请求,服务器阻塞直到有更新才响应(类似长轮询)。
3. Preload Hint:告诉播放器"下一个即将出现的是什么":
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg_43.0.m4s"
4. HTTP/2 Push(可选):服务端直接推送关联的 part 文件(多数 CDN 已弃用这一点)。
LL-DASH / CMAF-LL:对等方案
DASH 侧通过 HTTP Chunked Transfer Encoding:编码器每产出一个 CMAF chunk(~300ms)立刻通过分块传输发出,不等整片完成。
协议 vs 延迟对比
| 协议 |
典型延迟 |
复杂度 |
| 传统 HLS(TS, 6s x 3) |
20-30 秒 |
低 |
| 现代 HLS(fMP4, 4s x 3) |
8-15 秒 |
低 |
| LL-HLS / CMAF-LL |
2-5 秒 |
中 |
| WebRTC |
<500ms |
高 |
VOD 需要 LL-HLS 吗?
不需要。LL-HLS 是为直播设计的。VOD 的 manifest 类型是 VOD(完整列表),不涉及低延迟问题。但如果你的平台同时做直播+点播,LL-HLS 是必备能力。
7
5.7 WebRTC:超低延迟的"另一条路"
WebRTC 不是 HLS/DASH 的升级版,而是完全不同的技术:
|
HLS/DASH |
WebRTC |
| 传输层 |
HTTP/HTTPS |
UDP + DTLS + SRTP |
| 编码 |
任意 |
VP8/VP9/H.264/AV1 |
| CDN 友好 |
HTTP 缓存友好 |
点对点或专用 SFU |
| 延迟 |
2-30 秒 |
< 500ms |
| 规模 |
千万并发轻松 |
受 SFU 能力限制 |
| 典型场景 |
电影、点播、一对多直播 |
视频会议、互动连麦、云游戏 |
VOD 不用 WebRTC。本系列后面章节聚焦 HLS/DASH/CMAF。
8
5.8 协议选择实用指南
你做的是什么?
│
├── ① 纯 VOD(点播)
│ → HLS + DASH 双 manifest + CMAF fMP4
│
├── ② 普通直播(<10s 延迟即可)
│ → HLS + DASH + CMAF fMP4
│
├── ③ 低延迟直播(体育、电商,<3s)
│ → LL-HLS + LL-DASH + CMAF-LL
│
├── ④ 超低延迟(互动、连麦,<500ms)
│ → WebRTC 或 RTMP-over-QUIC
│
└── ⑤ 广电 IPTV(运营商盒子)
→ MPEG-TS over UDP/HTTP(不是本书重点)
9
5.9 动手:用 ffmpeg 生成一份 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"
输出:
hls/
master.m3u8
v0/index.m3u8 (360p)
v1/index.m3u8 (720p)
v2/index.m3u8 (1080p)
用 Shaka Packager 做 CMAF + HLS + DASH(生产级推荐)
# 先用 ffmpeg 转码成三档 mp4:input_360p.mp4 / 720p / 1080p
# 再用 shaka-packager 打包
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
输出:
cmaf/
master.m3u8 ← HLS
manifest.mpd ← DASH
v0/init.mp4 + v0/seg_*.m4s (360p)
v1/init.mp4 + v1/seg_*.m4s (720p)
v2/init.mp4 + v2/seg_*.m4s (1080p)
a0/init.mp4 + a0/seg_*.m4s (audio)
一份 segment,HLS + DASH 共用。
本章要点回顾
1. 流媒体 = 切片 + manifest + 客户端按需拉取。
2. HLS(Apple)用 M3U8 文本 manifest;DASH(MPEG)用 MPD XML。
3. iOS/Safari 原生只支持 HLS;其他平台都支持 DASH。
4. CMAF 让 HLS 和 DASH 共用一份 fMP4,是工业界最佳实践。
5. 传统 HLS 延迟 20-30 秒;LL-HLS / CMAF-LL 可做到 2-5 秒。
6. WebRTC 是另一条路(<500ms),但不是 VOD 用的。
7. 生产环境用 Shaka Packager 或云服务(MediaPackage)做打包,不要手搓。
© 2026 Zmead · VOD 流媒体技术全解