VOD 深度剖析(五):流媒体协议——HLS 和 DASH 到底是怎么工作的

为什么渐进式下载会失败、HLS 两级清单和 DASH MPD 如何工作、CMAF 双清单最佳实践、面向低延迟的 LL-HLS,以及何时该考虑 WebRTC。

zhuermu··25 分钟
vodstreaminghlsdashcmafll-hls

这是 VOD 流媒体深度剖析 系列的第 5 篇。


为什么不能直接下载一个 MP4?

最简单的做法:把 video.mp4 放到 HTTP 服务器上,让用户直接下载。这就是渐进式下载(progressive download)。它有几个致命缺陷:

  1. 如果没有做 faststart,必须等整个文件下载完才能开始播放(参见第 4 篇)。
  2. 当带宽下降时,播放器无法切换到更低的清晰度——只能一直卡顿缓冲。
  3. 拖动进度条要在一个大文件上使用 HTTP Range 请求——对 CDN 缓存不友好
  4. 无法为不同设备提供不同的版本(一台老手机拿到的和 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 文件:.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">

  <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

HLSDASH
清单格式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 秒

关键技术:

  1. 部分片段(Partial Segments):把 6 秒的片段切成 200~500 毫秒的”部分(parts)“,让播放器更早拿到数据。
  2. 阻塞式播放列表重载(Blocking Playlist Reload):播放器带着 ?_HLS_msn=X&_HLS_part=Y 发起请求,服务器在有新数据之前一直挂起响应(类似长轮询)。
  3. 预加载提示(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-LL2~5 秒
WebRTC<500ms

WebRTC:另一条路

WebRTC 并不是 HLS/DASH 的升级版——它是一种从根本上不同的技术:

HLS/DASHWebRTC
传输HTTP/HTTPSUDP + 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 两份清单。


核心要点

  1. 流媒体 = 片段 + 清单 + 由客户端驱动的按需拉取。
  2. HLS(Apple)使用 M3U8 文本清单;DASH(MPEG)使用 MPD XML。
  3. iOS/Safari 只原生支持 HLS;其他平台两者都支持。
  4. CMAF 让 HLS 和 DASH 共享同一套 fMP4 文件——这是行业最佳实践。
  5. 传统 HLS 延迟为 20~30 秒;LL-HLS / CMAF-LL 可达到 2~5 秒。
  6. WebRTC 是另一条独立的路径(<500ms)——不适用于 VOD。
  7. 在生产环境中,使用 Shaka Packager 或云服务(MediaPackage)来完成打包。

上一篇: 第 4 篇:容器格式

下一篇: 第 6 篇:自适应码率流媒体

参考资料

  1. RFC 8216: HTTP Live Streaming — IETF
  2. DASH-IF guidelines — DASH Industry Forum
  3. HTTP Live Streaming — Apple Developer