第 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 文件 .mpdMedia 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)做打包,不要手搓。
• • •
← 上一章:文件封装格式 目录 下一章:自适应码率 ABR →

© 2026 Zmead · VOD 流媒体技术全解