3、HLS协议深度解析:m3u8索引文件、TS/CMAF分段与自适应码率

HLS 这个协议,说白了就是苹果搞的一套“切碎再拼”的玩法。我最早接触它是在 2012 年,那时候还在用 RTMP 做直播,结果苹果设备死活不支持 Flash。嗯,从那以后我就开始认真研究 HLS 了。

今天咱们把 HLS 拆开来看。核心就三样东西:m3u8 索引文件、媒体分段(TS 或 CMAF)、自适应码率逻辑。搞懂了这三块,你就能理解为什么 HLS 能成为目前互联网上最主流的直播协议之一。

3.1 m3u8 索引文件:播放列表的“地图”

m3u8 本质上就是一个文本文件,它告诉播放器“去哪里拿数据”。我习惯把它比作一张地图——地图本身不包含风景,但告诉你风景在哪。

3.1.1 单码率 vs 多码率

先看最简单的单码率 m3u8:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000,
segment_000.ts
#EXTINF:10.000,
segment_001.ts
#EXTINF:10.000,
segment_002.ts
#EXT-X-ENDLIST

这个文件告诉播放器:有 3 个分片,每个 10 秒,按顺序播。注意 #EXT-X-ENDLIST 这个标签——有它表示点播(VOD),没有它表示直播(Live)。

多码率就复杂一些了。我当年第一次配置多码率时踩过一个坑:子播放列表的路径写成了相对路径,结果 CDN 回源时找不到文件。来看标准的多码率结构:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=640x360
low.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=854x480
mid.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5120000,RESOLUTION=1280x720
high.m3u8

播放器拿到这个“主播放列表”后,会根据当前网络带宽自动选择最合适的子列表。这里 BANDWIDTH 的单位是 bps,注意别写错了。

我的习惯:多码率 m3u8 里,BANDWIDTH 值建议比实际码率高出 10%-20%。因为网络波动时,播放器需要一点“余量”来做切换决策。

3.1.2 关键标签详解

标签 作用 避坑点
#EXT-X-TARGETDURATION 单个分片的最大时长(秒) 所有分片时长不能超过这个值,否则播放器会报错
#EXT-X-MEDIA-SEQUENCE 直播场景下,当前第一个分片的序号 直播时这个值会递增,播放器用它来同步
#EXT-X-DISCONTINUITY 标记音视频编码参数发生变化 切广告或切换分辨率时一定要加,否则花屏
#EXT-X-KEY 加密信息(AES-128 等) 密钥文件路径必须是绝对 URL 或相对路径,不能混用

我曾经遇到过一个线上问题:直播流每隔几分钟就卡一下。查了半天,发现是 #EXT-X-DISCONTINUITY 标签没加——编码器在 GOP 边界调整了参数,但播放器不知道,结果解码器崩了。

3.2 TS 分段与 CMAF 分段

分片格式是 HLS 的“血肉”。早期清一色用 TS(MPEG-2 Transport Stream),后来苹果在 HLS 第 7 版引入了 CMAF(Common Media Application Format)。

3.2.1 TS 格式:老当益壮

TS 分片的特点:每个分片都是独立的 MPEG-2 TS 流,包含完整的 PAT/PMT 表。这意味着播放器拿到任何一个分片都能开始解码。

TS 分片的典型结构:

TS Packet (188 bytes)
├── 4 bytes: Sync byte + PID + flags
├── Adaptation Field (可选)
└── Payload (PES 数据)
    ├── Video (H.264/H.265)
    └── Audio (AAC/MP3)

我早期做 HLS 推流时,最头疼的就是 TS 分片的对齐问题。举个例子:如果分片边界没有对齐到关键帧(IDR),播放器切换码率时就会出马赛克。

重要:TS 分片必须从 IDR 帧开始,到 IDR 帧之前结束。否则播放器在切换码率时,解码器会处于“无参考帧”状态,导致画面异常。

3.2.2 CMAF 格式:现代选择

CMAF 是苹果和微软联手推的“统一格式”。它基于 ISO BMFF(MP4),核心思想是:同一个分片文件,HLS 和 DASH 都能用。

CMAF 分片的结构:

CMAF Fragment
├── ftyp (文件类型)
├── moov (元数据)
├── moof (分片元数据)
│   ├── mfhd (分片头部)
│   └── traf (轨道片段)
└── mdat (媒体数据)
    ├── 视频样本 (关键帧 + 非关键帧)
    └── 音频样本

CMAF 相比 TS 的优势:

  • 文件体积更小:没有 TS 的 188 字节固定包头,节省约 5%-8% 带宽
  • 支持加密更灵活:原生支持 Common Encryption (CENC)
  • 兼容性更好:同一份文件同时服务 HLS 和 DASH

但 CMAF 也有坑。我记得有一次升级 CMAF 后,某些老旧机顶盒播放不了——因为它们只支持 TS 格式。所以我的建议是:如果终端设备可控,优先用 CMAF;如果面向海量未知设备,保留 TS 回退方案。

3.3 自适应码率(ABR)逻辑

自适应码率是 HLS 最吸引人的特性。播放器会根据网络状况,在多个码率版本之间无缝切换。

3.3.1 切换策略

常见的 ABR 算法有几种:

算法 原理 优缺点
基于吞吐量 统计最近 N 个分片的下载速度 简单直接,但对突发网络抖动反应慢
基于缓冲区 根据播放器缓冲区水位决策 缓冲区快空了就降码率,满了就升码率
混合策略 结合吞吐量和缓冲区 最稳定,但实现复杂

我个人比较推荐混合策略。举个例子:当缓冲区低于 5 秒时,立即降一档码率;当缓冲区高于 15 秒且吞吐量足够时,升一档码率。这样既避免了频繁切换,又能快速响应网络恶化。

3.3.2 码率阶梯设计

码率阶梯不是随便设几个值就行的。我见过很多团队把码率设成 500k、1000k、2000k、4000k——结果播放器在 1000k 和 2000k 之间反复横跳。

合理的码率阶梯应该满足:相邻两档的码率差不超过 1.5 倍。比如:

档位 1: 500 kbps (360p)
档位 2: 800 kbps (480p)
档位 3: 1300 kbps (720p)
档位 4: 2000 kbps (1080p)

这样播放器在切换时,视觉差异不会太明显。另外,最低码率要保证画面可看——我见过有人设 200kbps,结果画面全是马赛克,用户直接关掉了。

核心原则:ABR 的目标不是“永远最高码率”,而是“在可用带宽下提供最稳定的观看体验”。宁可降码率,不要卡顿。

3.4 HLS 协议架构总览

下面这张图把 HLS 的整体流程串起来了。从编码器到 CDN,再到播放器,每一步都有讲究。

HLS 协议架构与数据流 HLS 协议架构与数据流 编码器 分片器 分片存储 m3u8 生成器 CDN 分发 播放器 ABR 决策模块 关键流程说明: 1. 编码器输出连续的音视频流(H.264/H.265 + AAC) 2. 分片器按固定时长(通常 2-10 秒)切割为 TS 或 CMAF 分片 3. m3u8 生成器维护索引文件,记录分片 URL 和时长 4. 播放器下载 m3u8,根据 ABR 策略选择合适码率的分片

从这张图能看出来,HLS 的每个环节都环环相扣。编码器输出要稳定,分片器要保证对齐,m3u8 要实时更新,CDN 要低延迟分发,播放器要智能决策——任何一个环节出问题,用户体验都会打折扣。

3.5 实战中的几个关键点

最后分享几个我在项目中积累的经验:

  • 分片时长不要设太短:2 秒分片虽然延迟低,但 CDN 请求量暴增,容易把源站打垮。我一般用 4-6 秒。
  • 直播场景下,m3u8 要保留至少 3 个分片:这样播放器在启动时能快速填满缓冲区,减少卡顿。
  • 加密时注意密钥轮换:#EXT-X-KEY 的 KEYFORMAT 要写对,否则某些播放器会解密失败。
  • 测试 ABR 切换时,用 Chrome 的 DevTools 模拟弱网:我习惯把网络限速到 200kbps,然后看播放器能不能平滑降码率。
我曾经踩过的坑:有一次直播活动,用户反馈频繁卡顿。查日志发现 CDN 回源超时——因为 m3u8 里分片 URL 用了绝对路径,但 CDN 配置了防盗链,回源时签名校验失败。后来我统一改成相对路径,问题解决。

嗯,HLS 协议的内容就讲到这里。记住:m3u8 是地图,分片是砖块,ABR 是智能导航。把这三样东西搞明白,HLS 对你来说就没有秘密了。