HLS / DASH 流媒体
HLS(Apple 主推)和 DASH(MPEG 开放标准)本质上都是 HTTP + 清单 + 分片 + ABR:播放器先拉清单文件,知道有哪些码率、哪些分片、每片多长;再一个个拉媒体分片,边下载边播放,并根据带宽和 buffer 状态切换码率。
本文速览
| 章节 |
读完要掌握什么 |
| 0. 核心模型 |
HLS/DASH 为什么都是"清单描述索引,分片承载媒体"。 |
| 1. 服务端工作原理 |
编码多档码率、按 GOP 切片、生成清单、放到 HTTP/CDN 分发。 |
| 2. HLS 工作原理 |
播放 HLS 时如何先拉 m3u8,再拉 ts/m4s 分片。 |
| 3. DASH 工作原理 |
播放 DASH 时如何先拉 MPD,再拉 init.mp4 + m4s 分片。 |
| 4. HLS vs DASH 对象映射 |
把 m3u8/MPD/segment/init 放到同一张表里对照。 |
| 5. ABR 工作流程 |
播放器如何根据吞吐、buffer、设备能力选择下一片码率。 |
| 6. CMAF 与低延迟 |
为什么现在常说"一套 fMP4 分片,两套清单"。 |
| 7. 加密 / DRM |
HLS AES-128/FairPlay 与 DASH CENC/Widevine/PlayReady。 |
| 8. 服务端切片工具 |
FFmpeg、Shaka Packager、Bento4 怎么落地。 |
| 9. 客户端集成 |
Android、iOS、Web 上怎么接 HLS/DASH。 |
| 10. 协议对比与选型 |
什么时候选 HLS、DASH、CMAF、LL-HLS、LL-DASH。 |
| 11. 直播架构 |
RTMP/SRT 输入如何转成 HLS/DASH 输出。 |
| 12. 工程坑与测试 |
GOP 对齐、时间戳、缓存、ABR 对齐、抓包验证。 |
| 13. 总结 |
用一条主线记住 HLS/DASH。 |
0. 核心模型:不是下载完整视频,而是下载清单和小分片
HLS 和 DASH 都把一个长视频或直播流拆成很多小文件,播放器按需下载。
| 问题 |
传统整文件下载 |
HLS / DASH 的做法 |
| 起播速度 |
要等较大文件下载一部分甚至全部。 |
先拉清单,再拉第一个小分片,buffer 足够就起播。 |
| 网络波动 |
单一清晰度,卡了只能等。 |
多码率分片并存,播放器可在分片边界切换码率。 |
| 直播 |
文件没有结束,普通下载模型不好表达。 |
清单持续更新,播放器定期刷新清单拿新分片。 |
| CDN 分发 |
大文件 range/cache 策略复杂。 |
清单和分片都是普通 HTTP 资源,CDN 缓存友好。 |
| 跨平台播放 |
容器/协议适配成本高。 |
HLS 适合 Apple 生态,DASH 适合 MPEG-DASH/MSE 生态。 |
一句话拆解:
| 层级 |
HLS |
DASH |
作用 |
| 清单层 |
master.m3u8 + media playlist.m3u8 |
manifest.mpd |
描述有哪些码率、音轨、字幕、分片 URL、时长、时间线。 |
| 媒体层 |
.ts 或 .m4s |
.m4s |
真正承载音视频数据。 |
| 传输层 |
HTTP/HTTPS |
HTTP/HTTPS |
播放器用普通 HTTP GET 拉清单和分片。 |
| 自适应层 |
ABR 选 variant |
ABR 选 representation |
根据带宽、buffer、设备能力切换码率。 |
1. 服务端工作原理:编码 ladder → 切片 → 生成清单 → CDN
HLS/DASH 播放端看起来只是"拉 URL",但服务端需要提前把媒体整理成多码率、多分片、多清单。
| 阶段 |
输入 |
输出 |
关键要求 |
| 采集 / 输入 |
文件、RTMP、SRT、WebRTC、SDI 等。 |
原始或已编码音视频流。 |
时间戳连续,音视频同步。 |
| 多码率编码 |
一路输入流。 |
480p、720p、1080p 等多档码率。 |
不同档位 GOP 边界对齐,便于 ABR 切换。 |
| 切片 |
每档编码流。 |
seg_001.ts / seg_001.m4s 等分片。 |
分片通常从关键帧开始,时长稳定。 |
| 生成清单 |
分片列表和媒体属性。 |
HLS m3u8 或 DASH MPD。 |
写清 URL、时长、码率、分辨率、codec、时间线。 |
| 源站发布 |
清单 + 分片文件。 |
HTTP 可访问资源。 |
设置合理 cache-control。 |
| CDN 分发 |
源站 HTTP 文件。 |
边缘节点缓存。 |
分片可长缓存,直播清单短缓存或不缓存。 |
| 播放器拉取 |
播放 URL。 |
解码播放。 |
根据清单逐片下载,并做 ABR。 |
多码率 ladder 可以这样理解:
| 档位 |
分辨率 |
码率示例 |
HLS 表达 |
DASH 表达 |
| 低清 |
854×480 |
800 kbps |
一个 variant playlist。 |
一个 Representation。 |
| 高清 |
1280×720 |
2 Mbps |
一个 variant playlist。 |
一个 Representation。 |
| 全高清 |
1920×1080 |
4 Mbps |
一个 variant playlist。 |
一个 Representation。 |
| 音频 |
AAC 2ch |
128 kbps |
可内嵌在 TS,也可独立 audio rendition。 |
常是独立 AdaptationSet。 |
关键点:ABR 不是把一个分片"实时转成不同清晰度",而是服务端提前准备好多档分片,播放器每次下载下一片前决定"下一片拉哪一档"。
2. HLS 工作原理:先拉 m3u8,再拉一个个 ts/m4s 分片
HLS 的清单是 m3u8 文本文件。最常见的播放链路是:
| 顺序 |
播放器动作 |
HTTP 请求示例 |
拿到什么 |
下一步 |
| 1 |
拉主清单 |
GET /master.m3u8 |
多档码率列表。 |
选择初始 variant。 |
| 2 |
拉子清单 |
GET /720p.m3u8 |
这一档的分片列表。 |
找到第一个要播的 segment。 |
| 3 |
拉第一个分片 |
GET /720p/seg_001.ts |
约几秒媒体数据。 |
解封装、解码,填充 buffer。 |
| 4 |
拉后续分片 |
GET /720p/seg_002.ts |
下一段媒体数据。 |
边播边下载。 |
| 5 |
估算网络 |
根据分片大小 / 下载耗时。 |
当前吞吐估计。 |
决定下一片是否升降码率。 |
| 6 |
切换码率 |
GET /480p/seg_003.ts 或 GET /1080p/seg_003.ts |
同一时间位置的另一档分片。 |
在分片边界无缝切换。 |
| 7 |
直播刷新清单 |
GET /720p.m3u8 |
最新滑动窗口。 |
继续拉新生成的 segment。 |
如果播放 URL 直接就是某个 media playlist,例如 https://example.com/live/720p.m3u8,播放器可以跳过主清单,直接从子清单开始。
2.1 HLS 主清单:告诉播放器有哪些档位
master.m3u8 用来列出多档码率、分辨率、codec 和对应的 media playlist。
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480,CODECS="avc1.4d4015,mp4a.40.2"
480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
1080p.m3u8
主清单重点字段:
| 标签 / 属性 |
示例 |
作用 |
#EXTM3U |
#EXTM3U |
表示这是 M3U/M3U8 清单。 |
#EXT-X-VERSION |
6 |
HLS 协议版本。 |
#EXT-X-STREAM-INF |
BANDWIDTH=2000000 |
描述一个 variant stream。 |
BANDWIDTH |
2000000 |
该档码率估计值,ABR 初选会参考。 |
RESOLUTION |
1280x720 |
该档分辨率。 |
CODECS |
avc1.4d401f,mp4a.40.2 |
音视频 codec 标识,播放器判断能否解码。 |
| URI 行 |
720p.m3u8 |
该档 media playlist 的 URL。 |
2.2 HLS 子清单:告诉播放器这一档有哪些分片
传统 HLS 常用 MPEG-TS 分片:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:9.984,
segment_001.ts
#EXTINF:10.000,
segment_002.ts
#EXTINF:10.000,
segment_003.ts
#EXT-X-ENDLIST
子清单重点字段:
| 标签 |
示例 |
作用 |
#EXT-X-TARGETDURATION |
10 |
最大分片时长的上限提示,播放器据此安排刷新和 buffer。 |
#EXT-X-MEDIA-SEQUENCE |
0 |
当前 playlist 第一个 segment 的序号。直播滑动窗口会增长。 |
#EXTINF |
9.984 |
下一个 segment 的实际时长。 |
| segment URI |
segment_001.ts |
真实媒体分片 URL,可以是相对路径或绝对路径。 |
#EXT-X-ENDLIST |
点播结尾出现 |
表示清单不会再追加新分片;直播一般没有。 |
2.3 HLS 分片到底是什么
| 分片类型 |
文件后缀 |
内部封装 |
初始化段 |
典型场景 |
| MPEG-TS HLS |
.ts |
MPEG-TS packet,常见 H.264/AAC。 |
通常不需要独立 init。 |
传统 HLS,兼容性好。 |
| fMP4 HLS |
.m4s |
fragmented MP4,moof + mdat。 |
需要 #EXT-X-MAP 指向 init segment。 |
CMAF、LL-HLS、HEVC、现代链路。 |
fMP4 HLS 的 media playlist 通常长这样:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:4
#EXT-X-MAP:URI="init.mp4"
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:4.000,
seg_001.m4s
#EXTINF:4.000,
seg_002.m4s
#EXTINF:4.000,
seg_003.m4s
#EXT-X-ENDLIST
| 字段 |
TS HLS |
fMP4 HLS |
| 媒体数据 |
.ts 文件里直接有 TS packet。 |
.m4s 文件里是 moof + mdat。 |
| 初始化信息 |
TS 流里周期性携带 PAT/PMT 等。 |
#EXT-X-MAP 指向 init.mp4,里面有 ftyp/moov。 |
| 和 DASH 复用 |
很难直接复用。 |
可与 DASH 共用 CMAF/fMP4 分片。 |
| 现代 codec |
支持受限或兼容性复杂。 |
更适合 HEVC、AV1、HDR 等现代能力。 |
2.4 HLS 直播:滑动窗口不是完整历史
直播 HLS 的 media playlist 通常只保留最近几个分片,随着时间向前滑动。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1234
#EXTINF:6.000,
seg_1234.ts
#EXTINF:6.000,
seg_1235.ts
#EXTINF:6.000,
seg_1236.ts
滑动窗口变化:
| 时间 |
Playlist 内容 |
MEDIA-SEQUENCE |
变化 |
| T=0 |
seg_1234, seg_1235, seg_1236 |
1234 |
初始窗口。 |
| T=6s |
seg_1235, seg_1236, seg_1237 |
1235 |
移除旧片,追加新片。 |
| T=12s |
seg_1236, seg_1237, seg_1238 |
1236 |
窗口继续向前。 |
| T=18s |
seg_1237, seg_1238, seg_1239 |
1237 |
播放器继续刷新清单。 |
播放器直播拉流要点:
| 要点 |
说明 |
| 定期刷新 m3u8 |
直播没有 #EXT-X-ENDLIST,播放器需要重复 GET media playlist。 |
| 从 live edge 附近起播 |
为避免追太近卡顿,通常落后最新分片几个 segment 起播。 |
| 旧分片可能被清理 |
如果播放器拿到旧 playlist,但切片已被源站清理,就会 404。 |
| 延迟来自多个 segment |
传统 HLS 往往要缓存 2-3 个完整 segment,segment 越长延迟越高。 |
3. DASH 工作原理:先拉 MPD,再拉 init 和 m4s 分片
DASH 的清单是 XML 格式的 MPD。播放 DASH 时,播放器通常按下面流程工作:
| 顺序 |
播放器动作 |
HTTP 请求示例 |
拿到什么 |
下一步 |
| 1 |
拉 MPD |
GET /manifest.mpd |
Period、AdaptationSet、Representation、SegmentTemplate。 |
解析可用音视频轨和码率。 |
| 2 |
选初始 Representation |
选 video_720p + audio_128k。 |
初始视频档和音频档。 |
计算 init 和 segment URL。 |
| 3 |
拉初始化段 |
GET /video/720p/init.mp4 |
ftyp/moov,解码配置和 track 信息。 |
初始化 demuxer/decoder/MSE SourceBuffer。 |
| 4 |
拉第一个媒体段 |
GET /video/720p/seg-1.m4s |
moof + mdat。 |
解封装、解码、填充 buffer。 |
| 5 |
拉音频段 |
GET /audio/init.mp4、GET /audio/seg-1.m4s |
音频初始化和媒体段。 |
和视频按时间戳同步。 |
| 6 |
继续拉后续段 |
seg-2.m4s、seg-3.m4s。 |
后续媒体数据。 |
边播边下。 |
| 7 |
ABR 切换 |
GET /video/1080p/seg-4.m4s 或 GET /video/480p/seg-4.m4s |
同一时间位置的另一档视频段。 |
在 segment 边界切换。 |
| 8 |
直播刷新 MPD |
GET /manifest.mpd |
最新时间线。 |
找到新可用分片。 |
3.1 DASH MPD 示例
DASH 的 MPD 表达力比 HLS 文本清单更强,常见结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<MPD type="static" mediaPresentationDuration="PT60S" minBufferTime="PT2S">
<Period id="p0" start="PT0S">
<AdaptationSet id="v1" mimeType="video/mp4" codecs="avc1.640028" segmentAlignment="true">
<Representation id="1080p" bandwidth="4000000" width="1920" height="1080">
<BaseURL>1080p/</BaseURL>
<SegmentTemplate timescale="1000" duration="4000"
initialization="init.mp4"
media="seg-$Number$.m4s"
startNumber="1" />
</Representation>
<Representation id="720p" bandwidth="2000000" width="1280" height="720">
<BaseURL>720p/</BaseURL>
<SegmentTemplate timescale="1000" duration="4000"
initialization="init.mp4"
media="seg-$Number$.m4s"
startNumber="1" />
</Representation>
</AdaptationSet>
<AdaptationSet id="a1" mimeType="audio/mp4" codecs="mp4a.40.2" lang="zh">
<Representation id="audio" bandwidth="128000">
<BaseURL>audio/</BaseURL>
<SegmentTemplate timescale="1000" duration="4000"
initialization="init.mp4"
media="seg-$Number$.m4s"
startNumber="1" />
</Representation>
</AdaptationSet>
</Period>
</MPD>
DASH 核心概念:
| DASH 概念 |
含义 |
HLS 类比 |
说明 |
| MPD |
Media Presentation Description。 |
master + media playlist 的综合体。 |
XML 根清单,描述整个媒体。 |
| Period |
一段连续时间区间。 |
一段节目 / 广告段。 |
插广告、切节目时常分多个 Period。 |
| AdaptationSet |
一组可替换的同类轨道。 |
video variants / audio renditions。 |
例如视频集合、音频集合、字幕集合。 |
| Representation |
某一档具体编码版本。 |
HLS variant。 |
例如 720p 2Mbps H.264。 |
| Initialization Segment |
初始化段。 |
HLS fMP4 的 #EXT-X-MAP。 |
常见 init.mp4,包含 ftyp/moov。 |
| Media Segment |
媒体分片。 |
.ts / .m4s segment。 |
常见 moof + mdat。 |
| SegmentTemplate |
分片 URL 模板。 |
media playlist 中的 segment URI 列表。 |
用 $Number$ 或 $Time$ 生成 URL。 |
| SegmentTimeline |
精确时间线。 |
#EXTINF 列表。 |
适合分片时长不完全一致的场景。 |
3.2 DASH 分片格式
| 文件 |
典型 box |
作用 |
init.mp4 |
ftyp + moov |
初始化解封装器和解码器,包含 track、codec、timescale 等信息。 |
seg-1.m4s |
moof + mdat |
第一个媒体分片,moof 描述样本,mdat 放样本数据。 |
seg-2.m4s |
moof + mdat |
后续媒体分片。 |
seg-N.m4s |
moof + mdat |
按时间线连续播放。 |
DASH 的 URL 生成方式:
| 方式 |
示例 |
特点 |
| SegmentList |
MPD 中列出每个 <SegmentURL media="seg-1.m4s"/> |
直观,但 MPD 可能很大。 |
| SegmentTemplate + Number |
media="seg-$Number$.m4s" |
按序号生成 URL,点播和直播都常见。 |
| SegmentTemplate + Time |
media="seg-$Time$.m4s" |
按时间戳生成 URL,配合 SegmentTimeline。 |
| SegmentBase |
单文件 + byte range |
点播或特殊封装中使用。 |
3.3 DASH 直播:动态 MPD 和可用时间线
DASH 直播通常使用 type="dynamic" 的 MPD,播放器要根据时间计算哪些 segment 已经可用。
| MPD 字段 |
作用 |
播放器怎么用 |
type="dynamic" |
表示直播或动态内容。 |
不能当成完整 VOD,一段时间后要刷新 MPD。 |
availabilityStartTime |
媒体时间线起点。 |
结合当前墙钟时间计算 live edge。 |
minimumUpdatePeriod |
MPD 建议刷新间隔。 |
到时间后重新 GET MPD。 |
timeShiftBufferDepth |
可回看的直播窗口长度。 |
超出窗口的旧 segment 可能不可用。 |
suggestedPresentationDelay |
建议播放落后 live edge 多久。 |
避免追得太近导致卡顿。 |
SegmentTimeline |
明确每个 segment 的时间和重复次数。 |
精准定位当前应该下载的 segment。 |
DASH 直播和 HLS 直播的共同点:
| 共同点 |
说明 |
| 清单会更新 |
HLS 刷新 media playlist;DASH 刷新 dynamic MPD。 |
| 有直播窗口 |
只保留最近一段时间的 segment。 |
| 播放器不追最新一帧 |
通常落后 live edge 一段时间以保证稳定。 |
| 分片 URL 可预测 |
HLS 通过 playlist 明列;DASH 常通过模板计算。 |
4. HLS vs DASH 对象映射
把两套协议放到一张表里,理解会更快:
| 目标 |
HLS |
DASH |
说明 |
| 总入口 |
master.m3u8 |
manifest.mpd |
播放 URL 通常指向这里。 |
| 某一档码率 |
#EXT-X-STREAM-INF + 子 m3u8 |
Representation |
ABR ladder 中的一档。 |
| 视频集合 |
多个 variant stream |
video AdaptationSet |
表示可互相切换的视频档位。 |
| 音频集合 |
#EXT-X-MEDIA:TYPE=AUDIO |
audio AdaptationSet |
多语言、多声道时常见。 |
| 分片列表 |
media playlist 中的 URI 列表 |
SegmentList / SegmentTemplate / SegmentTimeline |
告诉播放器去哪拉分片。 |
| 分片时长 |
#EXTINF、#EXT-X-TARGETDURATION |
duration、SegmentTimeline |
播放器安排 buffer 和下载节奏。 |
| 初始化段 |
#EXT-X-MAP |
Initialization / initialization |
fMP4/CMAF 场景需要。 |
| 媒体分片 |
.ts / .m4s |
.m4s |
真正音视频数据。 |
| 点播结束 |
#EXT-X-ENDLIST |
type="static" + duration |
表示内容完整且不会再追加。 |
| 直播更新 |
media playlist 滑动窗口 |
dynamic MPD |
播放器周期性刷新清单。 |
核心差异:
| 维度 |
HLS |
DASH |
| 清单语法 |
M3U8 文本,简单直接。 |
XML,表达能力强。 |
| 生态优势 |
Apple 原生支持,iOS/Safari 最稳。 |
开放标准,DASH-IF、MSE、Android/Web 生态常见。 |
| 传统分片 |
.ts 历史包袱较重,但兼容好。 |
以 fMP4 为主。 |
| CMAF 统一 |
可用 fMP4 HLS。 |
天然适合 fMP4。 |
| DRM 生态 |
FairPlay。 |
Widevine / PlayReady。 |
5. ABR 工作流程:下一片拉哪一档
ABR(Adaptive Bitrate)发生在客户端。服务端只提供多档资源,真正做选择的是播放器。
| 阶段 |
播放器做什么 |
关键输入 |
输出 |
| 1. 解析清单 |
读取所有 variant / representation。 |
码率、分辨率、codec、URL。 |
可选档位列表。 |
| 2. 选初始码率 |
通常保守起播。 |
网络类型、历史带宽、屏幕大小、默认策略。 |
初始档位。 |
| 3. 下载分片 |
拉当前档位的第一个 segment。 |
URL、range、缓存状态。 |
下载耗时和分片大小。 |
| 4. 估算吞吐 |
segment size / download time。 |
最近多个分片下载记录。 |
平滑后的带宽估计。 |
| 5. 观察 buffer |
看已缓冲时长、丢帧、解码压力。 |
播放队列和渲染状态。 |
是否需要降档或可升档。 |
| 6. 决策下一片 |
选择下一 segment 的码率。 |
吞吐、buffer、安全系数、设备能力。 |
下一片 URL。 |
| 7. 边界切换 |
在 segment 边界切到新码率。 |
时间对齐的分片。 |
尽量无缝播放。 |
常见 ABR 策略:
| 策略 |
思路 |
优点 |
风险 |
| 吞吐优先 |
根据最近下载速度选择低于估计带宽的码率。 |
简单直接。 |
网络抖动大时容易上下跳。 |
| Buffer 优先 |
buffer 低就降档,buffer 高才升档。 |
起播和抗卡顿更稳。 |
可能保守,画质提升慢。 |
| 混合策略 |
吞吐 + buffer + 设备能力 + 历史状态。 |
工程上最常见。 |
参数多,需要调优。 |
| 低延迟策略 |
更关注 live edge 距离和下载截止时间。 |
延迟更低。 |
容错空间小,弱网更容易卡。 |
ABR 切换的硬要求:
| 要求 |
说明 |
| 不同码率分片时间对齐 |
720p 的第 10 片和 1080p 的第 10 片应覆盖同一段播放时间。 |
| 关键帧边界对齐 |
切片最好从 IDR/关键帧开始,切换后能独立解码。 |
| codec 兼容 |
不能在播放器不支持的 codec 档位上升档。 |
| 音视频时间线一致 |
独立音频和视频 AdaptationSet/Rendition 要能同步。 |
| 初始化段匹配 |
切 representation 时可能需要对应的 init segment。 |
6. CMAF 与低延迟:同切片不同清单
CMAF(Common Media Application Format)把 HLS 和 DASH 的媒体分片统一到 fMP4/CMAF chunk 思路上。
| 项 |
传统 HLS |
DASH |
CMAF 统一后 |
| 清单 |
m3u8 |
MPD |
仍然各用各的清单。 |
| 分片 |
.ts 常见 |
.m4s 常见 |
共用 .m4s / CMAF fragment。 |
| 初始化段 |
TS 通常不需要;fMP4 用 #EXT-X-MAP。 |
init.mp4。 |
共用 init 和 media segment 的内容结构。 |
| 服务端成本 |
HLS 和 DASH 可能各切一套。 |
单独一套。 |
媒体分片切一次,生成两套 manifest。 |
| 现代 codec |
TS 支持复杂。 |
fMP4 友好。 |
更适合 HEVC、AV1、HDR、低延迟。 |
低延迟的核心不是"协议名字变了",而是减少等待完整大分片的时间。
| 方案 |
传统做法 |
低延迟做法 |
延迟降低点 |
| HLS |
等完整 segment 出来后写入 playlist。 |
LL-HLS 用 partial segment、preload hint、blocking playlist reload。 |
播放器更早拿到未完全结束的媒体片段。 |
| DASH |
等完整 segment 可用后下载。 |
LL-DASH/CMAF chunk 用更小 chunk 和 chunked transfer。 |
边生成边传输,减少 segment 等待。 |
| 播放器 buffer |
通常缓存多个 segment。 |
低延迟播放器减少目标 buffer。 |
更靠近 live edge。 |
| CDN 传输 |
缓存完整 segment。 |
支持 chunked transfer / range / 更短缓存策略。 |
新媒体数据更快到边缘。 |
LL-HLS 关键标签和机制:
| 机制 |
示例 / 名称 |
作用 |
| Partial Segment |
#EXT-X-PART |
一个 segment 内再拆更小 part。 |
| Part Target |
#EXT-X-PART-INF:PART-TARGET=0.333 |
告诉播放器 part 目标时长。 |
| Preload Hint |
#EXT-X-PRELOAD-HINT |
提示下一个即将生成的 part。 |
| Blocking Reload |
_HLS_msn、_HLS_part 查询参数 |
客户端请求可阻塞等待新 part 出现。 |
| Server Control |
#EXT-X-SERVER-CONTROL |
描述服务端支持的低延迟能力。 |
| Rendition Report |
#EXT-X-RENDITION-REPORT |
低延迟 ABR 切换时帮助定位其他码率的最新位置。 |
低延迟方案的代价:
| 代价 |
说明 |
| CDN 要配合 |
边生成边传输、阻塞刷新、短缓存都要求 CDN 行为正确。 |
| 播放器容错更小 |
buffer 越短,网络抖动越容易卡顿。 |
| 服务端状态更多 |
part、preload、blocking reload 对打包器和源站要求更高。 |
| 调试更复杂 |
不能只看完整 segment,还要看 part/chunk 到达时机。 |
7. 加密 / DRM 支持
7.1 HLS AES-128 简单加密
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="https://license.example.com/key.bin",IV=0x1234567890abcdef1234567890abcdef
#EXTINF:10.000,
seg_001.ts
#EXTINF:10.000,
seg_002.ts
| 项 |
说明 |
| 加密对象 |
整个 TS segment 或相关媒体数据。 |
| Key 获取 |
播放器按 URI 通过 HTTPS 获取 key。 |
| 安全强度 |
适合基础防盗链,不等价于完整商业 DRM。 |
| 常见配套 |
URL 鉴权、referer 校验、token 过期、HTTPS。 |
7.2 HLS FairPlay
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,KEYFORMAT="com.apple.streamingkeydelivery",URI="skd://asset-id"
| 项 |
说明 |
| 生态 |
Apple FairPlay Streaming。 |
| 加密粒度 |
SAMPLE-AES 更偏样本级加密。 |
| 播放端 |
iOS、tvOS、Safari 生态支持最好。 |
| 业务流程 |
播放器拿到 license challenge,业务服务返回 license。 |
7.3 DASH Widevine / PlayReady
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>BASE64_PSSH_DATA</cenc:pssh>
</ContentProtection>
| 项 |
说明 |
| 通用加密 |
DASH 常用 CENC(Common Encryption)。 |
| Widevine |
Android / Chrome 生态常见。 |
| PlayReady |
Windows / Xbox / 部分电视生态常见。 |
| PSSH |
Protection System Specific Header,告诉播放器如何请求 license。 |
8. 服务端切片工具
8.1 FFmpeg 切 HLS
ffmpeg -i input.mp4 \
-c:v libx264 -profile:v main -crf 20 \
-c:a aac -b:a 128k \
-f hls \
-hls_time 10 \
-hls_list_size 0 \
-hls_segment_type mpegts \
output.m3u8
输出结果:
| 文件 |
说明 |
output.m3u8 |
HLS media playlist。 |
output0.ts / output1.ts |
MPEG-TS 分片。 |
#EXT-X-ENDLIST |
点播输出通常会有结束标记。 |
关键参数:
| 参数 |
说明 |
-f hls |
使用 HLS muxer。 |
-hls_time 10 |
目标 segment 时长 10 秒。 |
-hls_list_size 0 |
点播保留全部 segment;直播通常设置滑动窗口大小。 |
-hls_segment_type mpegts |
输出 TS 分片;也可选择 fMP4。 |
8.2 FFmpeg 切 DASH
ffmpeg -i input.mp4 \
-map 0 \
-c:v libx264 -c:a aac \
-f dash \
output.mpd
输出结果:
| 文件 |
说明 |
output.mpd |
DASH manifest。 |
init-stream*.m4s |
初始化段。 |
chunk-stream*-00001.m4s |
媒体分片。 |
8.3 多码率切片
ffmpeg -i input.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3]; \
[v1]scale=854:480[480p]; \
[v2]scale=1280:720[720p]; \
[v3]scale=1920:1080[1080p]" \
-map "[480p]" -c:v libx264 -b:v 800k -g 60 -hls_time 2 -hls_segment_filename "480p_%03d.ts" 480p.m3u8 \
-map "[720p]" -c:v libx264 -b:v 2000k -g 60 -hls_time 2 -hls_segment_filename "720p_%03d.ts" 720p.m3u8 \
-map "[1080p]" -c:v libx264 -b:v 4000k -g 60 -hls_time 2 -hls_segment_filename "1080p_%03d.ts" 1080p.m3u8
| 注意点 |
说明 |
| 主清单 |
上面示例还需要生成或手写 master.m3u8。 |
| GOP 对齐 |
各档 -g、帧率、切片时长要一致。 |
| 音频处理 |
实战常把音频单独 map,避免三份重复音频浪费。 |
| 生产工具 |
大规模生产更常用专用 packager。 |
8.4 专业打包工具
| 工具 |
适合场景 |
特点 |
| Shaka Packager |
DASH、HLS、CMAF、DRM。 |
Google 生态,CMAF 友好。 |
| Bento4 |
MP4/fMP4/HLS/DASH 打包分析。 |
老牌工具,适合调试 MP4 box 和 DASH/HLS。 |
| Apple mediafilesegmenter |
Apple HLS 工作流。 |
HLS 官方工具链之一。 |
| 商业云转码 |
生产直播/点播。 |
集成转码、截图、DRM、CDN、监控。 |
9. 客户端集成
// HLS
val hlsSource = HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(m3u8Url))
// DASH
val dashSource = DashMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(mpdUrl))
player.setMediaSource(hlsSource)
player.prepare()
player.play()
| 能力 |
说明 |
| HLS |
Media3/ExoPlayer 可通过 HLS module 播放。 |
| DASH |
Media3/ExoPlayer 可通过 DASH module 播放。 |
| ABR |
播放器内部根据 TrackSelection 策略切换。 |
| DRM |
可集成 Widevine 等 DRM。 |
9.2 iOS AVFoundation
let url = URL(string: "https://example.com/master.m3u8")!
let player = AVPlayer(url: url)
player.play()
| 协议 |
iOS 支持情况 |
| HLS |
系统原生支持,是 iOS/tvOS/Safari 首选。 |
| DASH |
系统一般不原生支持,常用第三方播放器或转 HLS。 |
| FairPlay |
Apple DRM 生态集成最好。 |
9.3 Web
<video id="video" controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video');
const hls = new Hls();
hls.loadSource('https://example.com/master.m3u8');
hls.attachMedia(video);
</script>
Web 播放对比:
| 场景 |
常见方案 |
说明 |
| Safari 播 HLS |
<video src="master.m3u8"> |
Safari 原生 HLS 能力强。 |
| Chrome 播 HLS |
hls.js + MSE |
hls.js 解析 m3u8 和 TS/fMP4,喂给 MSE。 |
| Chrome 播 DASH |
dash.js / Shaka Player + MSE |
DASH Web 生态成熟。 |
| DRM Web 播放 |
EME + Widevine/PlayReady/FairPlay |
依赖浏览器和平台 DRM。 |
10. HLS vs DASH 对比与选型
| 维度 |
HLS |
DASH |
| 主推方 |
Apple。 |
MPEG 开放标准。 |
| 清单格式 |
m3u8 文本。 |
MPD XML。 |
| 传统切片 |
MPEG-TS。 |
fMP4。 |
| 现代切片 |
fMP4 / CMAF。 |
fMP4 / CMAF。 |
| iOS / Safari |
原生支持,首选。 |
通常需第三方或转封装。 |
| Android |
Media3/ExoPlayer 支持。 |
Media3/ExoPlayer 支持。 |
| Web |
Safari 原生;Chrome 常用 hls.js。 |
dash.js / Shaka Player 常见。 |
| DRM |
FairPlay。 |
Widevine / PlayReady。 |
| 传统延迟 |
常见 10-30 秒,取决于 segment 和 buffer。 |
常见 10-30 秒,取决于 segment 和 buffer。 |
| 低延迟 |
LL-HLS。 |
LL-DASH / CMAF low latency。 |
| 表达能力 |
简洁,易读。 |
XML 表达能力更强,时间线更灵活。 |
实战推荐:
| 场景 |
推荐方案 |
原因 |
| 面向 iOS/Safari |
HLS。 |
系统原生支持最好。 |
| 面向 Android/Web 国际生态 |
DASH + HLS 双出。 |
兼容不同浏览器、DRM 和设备。 |
| 大规模点播 |
HLS + DASH + CMAF。 |
同媒体分片,不同清单,节省存储和打包成本。 |
| 普通直播 |
HLS / DASH。 |
CDN 友好,稳定优先。 |
| 低延迟直播 |
LL-HLS / LL-DASH / WebRTC。 |
看延迟目标和互动需求。 |
| 强互动连麦 |
WebRTC。 |
HLS/DASH 即使低延迟也不是强互动首选。 |
11. 直播 HLS/DASH 架构
直播链路经常是 RTMP/SRT 输入,HLS/DASH 输出。
| 阶段 |
组件 |
输入 |
输出 |
作用 |
| 采集 |
摄像头 / OBS / App SDK |
音视频采集数据。 |
编码流。 |
采集和编码。 |
| 推流 |
RTMP / SRT / WebRTC |
编码流。 |
到源站的实时流。 |
把主播流送到服务端。 |
| 源站接入 |
SRS / Nginx-RTMP / 云直播源站 |
RTMP/SRT。 |
内部媒体流。 |
鉴权、接流、转发。 |
| 转码 |
FFmpeg / 云转码 / GPU 转码。 |
一路高码率流。 |
多码率 ladder。 |
生成 480p/720p/1080p 等。 |
| 切片打包 |
Packager。 |
多码率编码流。 |
TS/fMP4 分片 + m3u8/MPD。 |
按 GOP 切片并更新清单。 |
| 分发 |
HTTP 源站 + CDN。 |
清单和分片。 |
边缘缓存。 |
大规模分发。 |
| 播放 |
App / Web / TV 播放器。 |
播放 URL。 |
画面和声音。 |
拉清单、拉分片、ABR 播放。 |
直播延迟来源:
| 来源 |
说明 |
优化方向 |
| 编码器缓存 |
B 帧、lookahead、码率控制会引入延迟。 |
低延迟编码参数、减少 B 帧。 |
| GOP 等待 |
切片通常要等关键帧边界。 |
缩短 GOP,GOP 和切片对齐。 |
| Segment 时长 |
传统 6-10 秒 segment 会直接推高延迟。 |
缩短 segment 或使用 partial/chunk。 |
| 播放器 buffer |
为抗抖动会缓存多个 segment。 |
调低目标 buffer,但要接受更高卡顿风险。 |
| CDN 缓存策略 |
清单缓存过长会拿不到新分片。 |
m3u8/MPD 短缓存,分片长缓存。 |
| 网络抖动 |
下载慢时 buffer 被耗尽。 |
ABR 降档、CDN 调度、弱网优化。 |
12. 工程坑与测试
12.1 GOP 跟切片不对齐
| 问题 |
表现 |
正确做法 |
| 切片从非关键帧开始 |
播放器切到该片后不能独立解码,出现花屏/黑屏。 |
segment 边界对齐 IDR。 |
| 各码率 GOP 不一致 |
ABR 切换后时间线或解码失败。 |
所有 ladder 使用一致帧率、GOP、切片时长。 |
| GOP 太长 |
起播慢,seek/切换慢,直播延迟高。 |
按延迟目标设置 GOP,例如 2s 或更短。 |
示例:
ffmpeg -i input.mp4 -r 30 -g 60 -keyint_min 60 -sc_threshold 0 -hls_time 2 output.m3u8
| 参数 |
说明 |
-r 30 |
输出帧率 30fps。 |
-g 60 |
2 秒一个 GOP。 |
-keyint_min 60 |
最小关键帧间隔也为 60。 |
-sc_threshold 0 |
避免场景切换导致额外关键帧破坏对齐。 |
-hls_time 2 |
segment 目标时长 2 秒。 |
12.2 时间戳不连续
| 检查项 |
要求 |
异常表现 |
| TS 内部 PCR |
稳定连续增长。 |
播放时钟抖动。 |
| PTS/DTS |
音视频时间戳单调合理。 |
卡顿、跳跃、音画不同步。 |
| segment 间时间线 |
前后 segment 不能断裂或重叠异常。 |
播放器报 discontinuity 或 seek 错。 |
| DASH timescale |
MPD 时间线和 m4s 内部 timescale 对应。 |
播放位置计算错误。 |
12.3 m3u8 / MPD 与切片缓存不一致
| 缓存对象 |
推荐思路 |
风险 |
| 直播 m3u8 / MPD |
短缓存或不缓存。 |
缓存太久会拿不到最新分片。 |
| 直播 segment |
可缓存一段时间,至少覆盖回看窗口。 |
清单还指向旧片但源站已删,会 404。 |
| 点播 m3u8 / MPD |
可长缓存,版本化 URL 更好。 |
更新同名文件可能导致新旧不一致。 |
| 点播 segment |
长缓存。 |
需要内容 hash 或版本号避免污染。 |
12.4 ABR 切换破坏对齐
| ABR 切换要求 |
说明 |
| 只能在分片边界切换 |
不能在 segment 中间突然换码率文件。 |
| 不同码率 segment 数量/时间一致 |
同一序号应覆盖同一时间范围。 |
| 初始化段正确切换 |
DASH/fMP4 切 representation 时可能要加载对应 init。 |
| 音频和视频同步 |
分离音视频时,不能只切视频而破坏音频时间线。 |
12.5 抓 HLS
| 场景 |
命令 / 写法 |
看什么 |
| 看主清单 |
curl -L https://example.com/master.m3u8 |
variant 列表、码率、分辨率、codec。 |
| 看子清单 |
curl -L https://example.com/720p.m3u8 |
TARGETDURATION、MEDIA-SEQUENCE、segment URL。 |
| 下载切片 |
wget https://example.com/seg_001.ts |
文件是否存在、大小是否合理。 |
| 看响应头 |
curl -I https://example.com/720p.m3u8 |
cache-control、content-type、CDN 命中。 |
12.6 抓 DASH
| 场景 |
命令 / 写法 |
看什么 |
| 看 MPD |
curl -L https://example.com/manifest.mpd |
Period、AdaptationSet、Representation、SegmentTemplate。 |
| 下载 init |
wget https://example.com/720p/init.mp4 |
初始化段是否可访问。 |
| 下载 m4s |
wget https://example.com/720p/seg-1.m4s |
分片是否存在、大小是否合理。 |
| 看 box |
mp4dump seg-1.m4s |
是否有 moof/mdat。 |
12.7 ffprobe 看切片
ffprobe -hide_banner seg_001.ts
ffprobe -hide_banner init.mp4
ffprobe -hide_banner seg-1.m4s
| 输出 |
重点关注 |
| codec |
H.264/H.265/AAC 等是否符合清单声明。 |
| duration |
segment 时长是否接近清单描述。 |
| start_time |
segment 起始时间是否连续。 |
| bitrate |
是否接近 ladder 配置。 |
12.8 hls.js / dash.js 调试
const hls = new Hls({ debug: true });
hls.loadSource('https://example.com/master.m3u8');
hls.attachMedia(videoElement);
| 日志 |
说明 |
| manifest parsed |
主清单解析完成。 |
| level switch |
ABR 档位切换。 |
| frag loading / loaded |
分片下载开始和完成。 |
| buffer appended |
媒体数据写入 MSE buffer。 |
| error |
网络、解复用、解码、媒体错误。 |
13. 总结
HLS / DASH 核心模型:
按"播放一个 HLS"来记最直观:
| 顺序 |
HLS 播放动作 |
DASH 对应动作 |
记忆点 |
| 1 |
拉 master.m3u8。 |
拉 manifest.mpd。 |
先拿"地图"。 |
| 2 |
解析 variant 列表。 |
解析 AdaptationSet/Representation。 |
知道有哪些码率和轨道。 |
| 3 |
拉某档 720p.m3u8。 |
计算某 Representation 的 init/segment URL。 |
定位具体媒体资源。 |
| 4 |
拉 segment_001.ts 或 seg_001.m4s。 |
拉 init.mp4 + seg-1.m4s。 |
真正下载媒体数据。 |
| 5 |
解封装、解码、填 buffer。 |
解封装、解码、填 buffer。 |
buffer 足够就起播。 |
| 6 |
边播边拉后续分片。 |
边播边拉后续分片。 |
播放不是一次性下载完整文件。 |
| 7 |
根据网络切到 480p/1080p 下一片。 |
根据网络切 Representation 下一片。 |
ABR 发生在分片边界。 |
| 8 |
直播定期刷新 m3u8。 |
直播定期刷新 dynamic MPD。 |
清单持续更新,窗口向前滑。 |
最终金句:HLS 和 DASH 都是"HTTP 文件系统上的流媒体协议"------清单负责索引,分片负责媒体,ABR 负责选择下一片。HLS 更偏 Apple 生态,DASH 更偏开放标准;CMAF 让两者可以共用 fMP4 分片,但清单仍然各写一套。