直播架构 / 流媒体协议
直播系统整体架构
推流端(采集 -> 编码 -> 封包 -> 推流)
推流端是整个直播链路的起点,负责将现实世界的音视频信号转换为可在网络上传输的数据流。推流端包含以下核心环节:
采集(Capture)
采集阶段从硬件设备获取原始音视频数据。视频采集源包括摄像头、屏幕录制、摄像机采集卡等;音频采集源包括麦克风、混音器输出、系统音频等。采集后的数据为未经压缩的原始格式,视频为 YUV/RGB 帧序列,音频为 PCM 采样数据。
视频采集: 摄像头 -> YUV/RGB 原始帧 (分辨率 1080p/720p/480p 等)
音频采集: 麦克风 -> PCM 原始采样 (采样率 44100/48000 Hz)编码(Encode)
原始音视频数据体积巨大(例如 1080p@30fps 的 YUV 视频码率约 1.5 Gbps),必须经过压缩编码才能通过网络传输。编码阶段使用视频编码器和音频编码器对原始数据进行压缩。
- 视频编码:H.264(AVC)是目前最通用的直播编码格式,H.265(HEVC)和 AV1 提供更高压缩率但解码兼容性较低。编码器通过帧内预测、帧间预测、变换量化、熵编码等技术消除空间和时间冗余。
- 音频编码:AAC 是直播中最常用的音频编码格式,Opus 在低延迟场景表现出色,MP3 已较少用于直播场景。
编码器输出的码率、帧率、GOP(Group of Pictures)长度直接影响直播质量和延迟。
视频编码示例(FFmpeg):
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -b:v 2500k -g 60 -c:a aac -b:a 128k output.flv封包(Mux/Packetize)
编码后的 ES(Elementary Stream,基本流)需要按照特定协议格式进行封装,添加必要的元信息(时间戳、帧类型、序列头等),形成可传输的媒体容器格式。常见的直播封装格式包括 FLV、TS、fMP4 等。封装阶段会生成 PTS(显示时间戳)和 DTS(解码时间戳),用于播放端的同步渲染。
推流(Push/Publish)
封装后的数据通过推流协议发送到流媒体服务器。推流协议负责将媒体数据可靠或实时地传输到服务端,并支持流发布认证、元数据传递等功能。
采集 -> 编码 -> 封包 -> 推流
[摄像头] -> [H.264] -> [FLV/TS] -> [RTMP/SRT]分发端(源站 -> 转码 -> CDN -> 边缘节点)
分发端负责将推流端推送的直播流高效、可靠地分发到全球各地的观众。分发端架构通常采用多层分层设计。
源站(Origin Server)
源站是直播流的第一站,负责接收推流端的上行流。源站提供流媒体接入能力,对推流进行认证鉴权,并将流转换为多种格式供下游拉取。源站通常部署在核心数据中心,具备大带宽接入能力。
转码(Transcoding)
转码服务将源站接收的原始流实时转换为多种码率和分辨率的副本,用于自适应码率分发。转码包括:
- 多码率转码:将一路源流转为 1080p(高码率)、720p(中码率)、480p(低码率)、360p(流畅)等多路输出。
- 格式转换:将 RTMP 流转换为 HLS(TS/fMP4)、DASH 等拉流格式。
- 自定义处理:添加水印、裁剪画面、调整帧率等。
CDN(Content Delivery Network)
CDN 是直播分发的核心基础设施,通过遍布全球的边缘节点就近服务观众。直播 CDN 包括:
- 中心节点:汇聚源站数据,作为区域回源的总入口。
- 区域节点:覆盖大区域(如华东、华北),缓存热点内容,减少回源压力。
- 边缘节点:部署在用户附近的接入点,直接响应播放请求,提供低延迟拉流。
推流端 -> 源站 -> 转码集群 -> CDN 中心节点 -> CDN 区域节点 -> CDN 边缘节点 -> 播放端缓存策略
直播 CDN 采用分片级缓存策略。对于 HLS/DASH 等分片协议,CDN 按 .ts 或 .m4s 分片粒度进行缓存和回源;对于 HTTP-FLV,CDN 基于 GOP 边界进行缓存拼接,支持时间戳修正。
播放端(拉流 -> 解封装 -> 解码 -> 渲染)
播放端是直播链路的终点,负责从 CDN 或源站拉取媒体流并呈现给观众。
拉流(Pull)
播放器根据 URL 选择对应的拉流协议从 CDN 边缘节点获取媒体数据。播放器首先下载索引文件(如 M3U8、MPD),解析出媒体分片列表,然后按顺序请求分片数据。对于 HTTP-FLV 协议,播放器直接建立 HTTP 长连接获取持续的 FLV 流。
解封装(Demux)
播放器将获取到的媒体容器数据分解为独立的视频 ES、音频 ES 和元数据。解封装阶段根据容器格式(TS、fMP4、FLV)选择对应的解复用器,提取编码帧和时间戳信息。
解码(Decode)
使用硬件或软件解码器将压缩的视频 ES 和音频 ES 解码为原始 YUV/RGB 帧和 PCM 采样。现代播放器优先使用硬件解码(如 MediaCodec、VideoToolbox、VAAPI)以降低功耗和 CPU 占用。解码后得到无压缩的原始帧序列。
渲染(Render)
将解码后的视频帧按照 PTS 时间戳同步渲染到显示设备上,同时将音频数据发送到音频输出设备。渲染阶段需要处理音画同步(A/V Sync)、帧率适配、缓冲控制等逻辑。
拉流 -> 解封装 -> 解码 -> 渲染
[HLS/FLV] -> [FLV/TS Demux] -> [H.264 Decode] -> [OpenGL/Surface 渲染]直播链路端到端拓扑图
┌─────────────────┐
│ 播放端 (观众 A) │
│ HLS/FLV/DASH │
└────────┬────────┘
│
┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐ ┌──────┴──────┐
│ 推流端 │ │ │ │ │ │ CDN 边缘 │
│ ┌────┐ ┌───┐ │ │ 流媒体源站 │ │ 转码集群 │ │ 节点 1 │
│ │采集 │ │编码│ │ │ │ │ │ │ │
│ └────┘ └───┘ │ RTMP│ ┌────────────┐ │ │ ┌─────┐ ┌───────┐ │ └──────┬──────┘
│ ┌────┐ ┌───┐ │ ────┼─┤ 推流接入 │ │─────┼─┤1080p│ │HLS Pack│ │ │
│ │封包 │ │推流│ │ SRT │ │ 鉴权/分发 │ │ │ │720p │ │DASH │ │ │
│ └────┘ └───┘ │ │ └────────────┘ │ │ │480p │ │FLV │ │ ┌──────┴──────┐
└──────────────┘ │ ┌────────────┐ │ │ └─────┘ └───────┘ │ │ CDN 边缘 │
│ │ 录制/时移 │ │ │ │ │ 节点 2 │
│ └────────────┘ │ └─────────────────────┘ │ │
└──────────────────┘ └──────┬──────┘
│
┌────────┴────────┐
│ 播放端 (观众 B) │
│ HLS/FLV/DASH │
└─────────────────┘推流协议
RTMP(Real-Time Messaging Protocol)
RTMP 是 Adobe 开发的基于 TCP 的实时消息传输协议,自 2000 年代初起成为直播推流的事实标准。尽管 Adobe 在 2020 年停止支持 Flash,但 RTMP 在推流场景中仍被广泛使用。
协议握手
RTMP 握手是客户端与服务端建立连接后的第一个交互过程,包含三个往返阶段:
客户端 服务端
│ │
│──── C0 (版本 0x03) ────────────────────│
│──── C1 (1536 字节随机数据) ────────────│
│ │
│◄─── S0 (版本 0x03) ────────────────────│
│◄─── S1 (1536 字节随机数据) ────────────│
│ │
│──── C2 (回显 S1 数据) ─────────────────│
│ │
│◄─── S2 (回显 C1 数据) ─────────────────│
│ │
│──── 建立连接 (connect) ────────────────│
│◄─── 窗口确认/设置带宽 ─────────────────│
│◄─── 连接成功 (_result) ───────────────│
│──── 创建流 (createStream) ─────────────│
│◄─── 流ID (_result) ───────────────────│
│──── 发布 (publish) ───────────────────│
│◄─── 发布成功 (onStatus) ──────────────│
│ │
│──── 推送音视频数据 ────────────────────│握手完成后,客户端和服务端通过 RTMP Chunk Stream 进行消息交换。
AMF 编码
RTMP 使用 Action Message Format(AMF)进行协议级数据编码,包括 AMF0 和 AMF3 两种版本。
- AMF0:使用类型标记 + 数据的编码方式,支持 Number(0x00)、Boolean(0x01)、String(0x02)、Object(0x03)、Null(0x05)、Array(0x0A)等类型。
- AMF3:在 AMF0 基础上增加了引用机制和更紧凑的编码。
AMF0 String 编码示例:
0x02 // 类型标记: String
0x00 0x0A // 字符串长度: 10 字节
0x72 0x74 0x6D 0x70 0x3A 0x70 0x6C 0x61 0x79 // "rtmp:play"
AMF0 Object 编码示例:
0x03 // 类型标记: Object
0x00 0x04 0x6E 0x61 0x6D 0x65 // key: "name"
0x02 0x00 0x05 0x76 0x61 0x6C 0x75 0x65 // value: "value"
0x00 0x00 0x09 // 对象结束标记Chunk 分块
RTMP Chunk Stream 是 RTMP 的传输层,将大消息拆分为更小的 Chunk 进行传输,支持流复用和优先级控制。
Chunk Header 有以下四种类型:
| 类型 | 字段包含 | 头部大小 | 说明 |
|---|---|---|---|
| Type 0 | timestamp, msg_length, msg_type_id, msg_stream_id | 11 字节 | 完整头部,用于 Chunk 起始或时间戳变化 |
| Type 1 | timestamp_delta, msg_length, msg_type_id | 7 字节 | 省略 msg_stream_id,延续前一个 Chunk 的流 ID |
| Type 2 | timestamp_delta | 3 字节 | 仅携带时间戳增量 |
| Type 3 | 无 | 0 字节 | 完全复用前一个 Chunk 的头部信息 |
Chunk 基本格式:
┌────────┬──────────┬─────────────┬────────────┐
│基本头部 │ 消息头部 │ 扩展时间戳 │ Chunk 数据 │
│1-3 字节 │ 0/3/7/11 │ 0/4 字节 │ 变长 │
│ │ 字节 │ │ │
└────────┴──────────┴─────────────┴────────────┘
基本头部:
┌──────┬──────┬───────────────┐
│ fmt │ cs │ chunk body │
│ 2bit │ 6bit │ (可选扩展) │
└──────┴──────┴───────────────┘
fmt: Chunk 类型 (0-3)
cs: Chunk Stream ID (2-63 直接编码, 64-65599 扩展编码)
Type 0 头部:
┌─────────┬───────────┬───────────┬──────────────┐
│Timestamp│Msg Length │Msg Type ID│ Msg Stream ID│
│ 3 字节 │ 3 字节 │ 1 字节 │ 4 字节 │
└─────────┴───────────┴───────────┴──────────────┘Chunk 分块机制的优点:
- 大消息(如视频帧)被拆分为多个 Chunk,避免阻塞小消息(如音频帧、控制消息)。
- Type 3 Chunk 仅 1 字节头部,有效降低连续帧的传输开销。
- 支持 Chunk Stream ID 复用,同一流上的多个消息交错传输。
推送流程
1. TCP 三次握手建立连接 (1935 端口)
2. RTMP 握手 (C0/C1 -> S0/S1 -> C2/S2)
3. 协议层连接 (connect 命令)
- 传入应用名 (app) 和实例类型 (type)
4. 创建流 (createStream 命令)
- 服务端分配流 ID
5. 发布流 (publish 命令)
- 传入流名称和发布类型 (live/record/append)
6. 推送音视频数据
- 发送音频数据消息 (Type ID = 8)
- 发送视频数据消息 (Type ID = 9)
- 发送元数据消息 (Type ID = 18, 包含 onMetaData)
7. 断开连接 (FCUnpublish / deleteStream)RTMP 推送数据流示例 (十六进制):
// 视频关键帧 (Type 0 Chunk)
03 00 00 00 00 1E 00 09 01 00 00 00
// 以上: fmt=0, cs=3, timestamp=0, length=30, type=video, stream=1
// 后续帧 (Type 3 Chunk) - 仅基本头部
C3 [26 字节视频数据]
// 音频帧
02 00 00 2D 00 01 00 08 01 00 00 00
// fmt=0, cs=2, timestamp=45, length=1, type=audio, stream=1
[1 字节音频数据]元数据(onMetaData)
推流端在推送音视频数据之前,需要通过 @setDataFrame 发送元数据,包含音视频编码参数:
onMetaData:
{
width: 1920,
height: 1080,
videodatarate: 2500,
framerate: 30,
videocodecid: 7, // AVC/H.264
audiodatarate: 128,
audiosamplerate: 44100,
audiosamplesize: 16,
stereo: true,
audiocodecid: 10, // AAC
encoder: "Lavf58.76.100",
duration: 0 // 直播流时长为 0
}SRT(Secure Reliable Transport)
SRT 是 Haivision 开发的基于 UDP 的可靠传输协议,专为不稳定的网络环境下的实时视频传输设计。SRT 在 2017 年开源并被广泛应用于远程制作和直播推流。
协议基础
SRT 运行在 UDP 之上,通过内置的 ARQ(自动重传请求)机制实现可靠传输,同时保持低延迟。SRT 支持点对点传输,无需中间服务器中转。
OSI 模型对比:
TCP: 应用层 -> RTMP/HTTP -> TCP -> IP
SRT: 应用层 -> 音视频数据 -> SRT -> UDP -> IPARQ 丢包重传
SRT 的核心特性是基于接收端反馈的丢包重传机制:
- 发送端为每个数据包分配递增的序列号。
- 接收端定期发送 ACK(确认)和 NAK(丢包报告)反馈。
- 发送端收到 NAK 后立即重传丢失的数据包。
- 接收端对乱序到达的数据包进行重排序。
发送端 接收端
│ │
│──── Packet N (Seq=1001) ──────────────│ (正常接收,发送 ACK)
│──── Packet N+1 (Seq=1002) ────────────X (丢失)
│──── Packet N+2 (Seq=1003) ────────────│ (正常接收)
│ │
│◄─── ACK(1002) ────────────────────────│ (接收端确认已收到 1002 之前的数据)
│◄─── NAK(1002) ────────────────────────│ (接收端报告 1002 丢失)
│ │
│──── 重传 Packet N+1 (Seq=1002) ───────│
│ │SRT 可配置延迟参数(latency)控制重传的时间窗口。如果数据包超出延迟窗口仍未收到,则直接跳过,确保实时性。
时间戳
SRT 使用发送端时钟生成时间戳,接收端通过时间戳实现播放同步。SRT 时间戳基于微秒精度,与 RTP 时间戳不同,SRT 时间戳直接反映发送时刻。
SRT 支持两种时间戳模式:
- 发送端时间戳模式(默认):数据包携带发送端生成的绝对时间戳。
- 接收端时间戳模式:接收端在接收到数据包时追加本地时间戳。
加密
SRT 内置 AES 加密支持,提供三种安全模式:
- 无加密:明文传输。
- AES-128:128 位密钥加密。
- AES-256:256 位密钥加密。
加密密钥通过带外方式协商,SRT 握手阶段使用密钥推导函数生成会话密钥。
SRT 握手流程:
客户端 服务端
│ │
│──── 握手请求 (Handshake Request) ──────│
│ (SRT 版本、加密配置、延迟参数、流ID) │
│◄─── 握手响应 (Handshake Response) ─────│
│ (SRT 版本、加密配置、延迟参数) │
│──── 握手确认 (Handshake Confirm) ──────│
│ │
│──── 开始传输媒体数据 ──────────────────│
│ (TS: 接收端时间戳模式) │
│◄─── ACK / NAK ────────────────────────│SRT 使用 streamid 参数进行推流路径标识:
推流链接示例:
srt://live.example.com:10080?streamid=#!::r=live/test,u=username,p=password参数说明:r 为流名称路径,u 和 p 为认证凭据。
SRT 推流配置
FFmpeg SRT 推流示例:
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -b:v 2500k \
-c:a aac -b:a 128k -f mpegts \
"srt://origin.example.com:10080?streamid=#!::r=live/stream1,m=publish"
FFmpeg SRT 拉流示例:
ffmpeg -i "srt://origin.example.com:10080?streamid=#!::r=live/stream1,m=request" \
-c copy output.mp4
SRT 关键参数:
- lat=120: 延迟窗口 120ms(默认 120ms,不稳定的网络可调大至 500-1000ms)
- maxbw=5000000: 最大带宽 5Mbps(0 表示自适应估算)
- passphrase: AES 加密密码
- pbkeylen: 密钥长度(16-AES128, 24-AES192, 32-AES256)RTMPS(RTMP + TLS)
RTMPS 是 RTMP 在 TLS 加密传输通道上的安全变体。本质上是 RTMP over TLS,将 RTMP 协议内容的明文传输替换为 TLS 加密传输。
协议特性
- 推流端口:通常使用 443 端口(与 HTTPS 相同),可通过防火墙。
- 加密方式:TLS 1.2/1.3,与 HTTPS 采用相同的证书体系。
- 握手流程:TCP 建立连接后先进行 TLS 握手,再执行标准 RTMP 握手。
- 适用场景:对推流内容安全性要求高的场景,如付费直播、企业内部直播。
RTMPS 连接建立:
TCP 连接 (443 端口)
│
├── TLS 握手
│ ├── ClientHello
│ ├── ServerHello + Certificate
│ ├── 密钥交换
│ └── ChangeCipherSpec + Finished
│
└── RTMP 握手 (加密通道内)
├── C0/C1 -> S0/S1 -> C2/S2
├── connect
├── createStream
├── publish
└── 音视频数据 (加密传输)与 RTMP 的对比
| 特性 | RTMP | RTMPS |
|---|---|---|
| 传输层 | TCP | TCP + TLS |
| 默认端口 | 1935 | 443 |
| 加密 | 无 | AES/TLS 加密 |
| 防火墙友好 | 较差(非标准端口) | 好(HTTPS 端口) |
| 性能开销 | 低 | 较高(TLS 握手 + 加解密) |
| 推流工具支持 | OBS、FFmpeg 完整支持 | OBS、FFmpeg 支持 |
WHIP(WebRTC-HTTP Ingestion Protocol)
WHIP 是 IETF 制定的基于 WebRTC 的标准化推流协议,旨在替代 RTMP 提供低延迟、高兼容性的推流方案。WHIP 已被多个主流平台(如 Cloudflare、Twitch、YouTube)采纳。
协议基础
WHIP 使用 HTTP 作为信令传输通道,通过 WebRTC(SRTP/SCTP)传输媒体数据。WHIP 的关键设计目标:
- 简化 WebRTC 推流的信令交互,使用 HTTP POST 代替复杂的 WebSocket/SIP 信令。
- 利用 WebRTC 的 ICE(交互式连接建立)机制实现 NAT 穿越。
- 支持 STUN/TURN 中继,适应复杂网络环境。
WHIP 推流建立流程:
推流端 服务端 (WHIP Endpoint)
│ │
│──── POST /whip/endpoint ──────────────────│
│ Content-Type: application/sdp │
│ Body: [Offer SDP - 推流端支持的编解码] │
│ │
│◄─── 201 Created ──────────────────────────│
│ Content-Type: application/sdp │
│ Body: [Answer SDP - 服务端选择的参数] │
│ Location: /whip/session/abc123 │
│ ETag: "session-id" │
│ │
│──── ICE 连接建立 (STUN/TURN) ──────────────│
│──── DTLS 握手 ────────────────────────────│
│──── SRTP/SCTP 媒体通道 ───────────────────│
│ │
│──── DELETE /whip/session/abc123 ──────────│
│ (推流结束时删除会话) │Offer SDP 示例
v=0
o=- 1234567890 1234567890 IN IP4 0.0.0.0
s=WHIP Live Stream
t=0 0
a=group:BUNDLE 0 1
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
c=IN IP4 0.0.0.0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
a=rtpmap:97 VP8/90000
a=rtpmap:98 AV1/90000
a=mid:0
a=sendonly
m=audio 9 UDP/TLS/RTP/SAVPF 99 100
c=IN IP4 0.0.0.0
a=rtpmap:99 OPUS/48000/2
a=rtpmap:100 PCMU/8000
a=mid:1
a=sendonlyWHIP 与 WebRTC 的关系
WHIP 不是一个新的传输协议,而是对 WebRTC 推流信令过程的简化封装。WHIP 使用 WebRTC 的以下核心技术:
- ICE(Interactive Connectivity Establishment):NAT 穿越和连接建立。
- DTLS-SRTP:媒体加密传输(与 HTTPS 同等级安全)。
- RTP/RTCP:实时媒体传输和质量反馈。
- SDP(Session Description Protocol):媒体参数协商。
WHIP 优势
- 超低延迟:端到端延迟可控制在 500ms 以内(RTMP 通常 2-5 秒)。
- 浏览器原生支持:无需插件或额外软件,浏览器即可推流。
- 防火墙友好:使用 HTTPS 443 端口和 HTTP 信令。
- NAT 穿透:ICE 框架自动处理复杂网络环境。
推流协议对比
| 特性 | RTMP | SRT | RTMPS | WHIP |
|---|---|---|---|---|
| 传输层 | TCP | UDP | TCP + TLS | UDP (SRTP) |
| 默认端口 | 1935 | 10080 | 443 | 443 (HTTP) |
| 端到端延迟 | 2-5 秒 | 0.5-2 秒 | 2-5 秒 | 0.2-1 秒 |
| 加密 | 不支持 | AES 可选 | TLS 加密 | DTLS-SRTP |
| 抗丢包能力 | 差(TCP 队头阻塞) | 好(ARQ 重传) | 差(TCP 队头阻塞) | 好(FEC+NACK) |
| NAT 穿越 | 需反向代理 | 需中介 | 需反向代理 | 原生支持(ICE) |
| 浏览器支持 | 不支持 | 不支持 | 不支持 | 原生支持 |
| FFmpeg/OBS 支持 | 完整支持 | 完整支持 | 完整支持 | 逐步支持 |
| 协议复杂度 | 中 | 中 | 中 | 高(WebRTC 栈) |
| 生态成熟度 | 最成熟 | 成熟 | 成熟 | 快速发展中 |
| 适用场景 | 传统推流、CDN 上行 | 弱网环境、远程制作 | 安全推流、企业内部 | 低延迟直播、互动直播 |
| 编码器支持 | H.264/H.265 | 任意编码 | H.264/H.265 | H.264/VP8/AV1/Opus |
| 传输单位 | FLV Tag | TS 包 | FLV Tag | RTP 包 |
拉流协议
HLS(HTTP Live Streaming)
HLS 是 Apple 提出的基于 HTTP 的自适应码率流媒体传输协议,是目前最广泛使用的直播拉流协议。HLS 将直播流切分为一系列连续的 HTTP 文件片段,通过索引文件(M3U8)进行管理。
M3U8 索引文件
HLS 使用 M3U8 播放列表文件描述可用的媒体分片和码率信息。M3U8 基于 M3U 格式扩展,使用 UTF-8 编码。
主播放列表(Master Playlist):
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-INDEPENDENT-SEGMENTS
# 高码率流 - 1080p
#EXT-X-STREAM-INF:BANDWIDTH=3500000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
index_1080p.m3u8
# 中码率流 - 720p
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
index_720p.m3u8
# 低码率流 - 480p
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2"
index_480p.m3u8
# 音频流
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Chinese",DEFAULT=YES,URI="audio_ch.m3u8"媒体播放列表:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1682912345
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-PROGRAM-DATE-TIME:2026-07-12T10:00:00.000Z
#EXT-X-MAP:URI="init.mp4"
#EXTINF:6.000,
segment_1682912345.m4s
#EXTINF:6.000,
segment_1682912346.m4s
#EXTINF:4.000,
segment_1682912347.m4s
#EXT-X-KEY:METHOD=AES-128,URI="key.bin",IV=0x1234567890abcdef
#EXTINF:6.000,
segment_1682912348.m4s
#EXT-X-ENDLIST媒体播放列表关键标签说明:
| 标签 | 说明 |
|---|---|
#EXT-X-TARGETDURATION | 单个分片的最大时长(秒),播放器据此设置缓冲策略 |
#EXT-X-MEDIA-SEQUENCE | 当前列表第一个分片的序列号,实现分片淘汰和持续更新 |
#EXT-X-DISCONTINUITY | 标记音视频流参数发生不连续变化(如编码参数变更) |
#EXT-X-KEY | 指定分片解密方法和密钥获取地址 |
#EXT-X-MAP | 用于 fMP4 格式,指向初始化片段(包含 ftyp+moov) |
#EXTINF | 分片持续时间,格式为 #EXTINF:<duration>,<title> |
#EXT-X-ENDLIST | 标记列表结束,仅 VoD(点播)场景存在,直播场景不存在此标签 |
TS 分片与 CMAP 分片
HLS 最初使用 MPEG-2 Transport Stream(TS)作为分片容器格式,后引入 fMP4(Fragmented MP4)作为 CMAF(Common Media Application Format)标准容器。
TS 格式:
- 每个分片为独立的
.ts文件,包含完整的音视频数据。 - 分片起始位置包含 PAT(Program Association Table)和 PMT(Program Map Table)用于解复用。
- 每个 TS 包固定 188 字节,包含 4 字节头部和负载数据。
fMP4 格式:
- 分片由初始化片段(Init Segment,
.mp4)和媒体片段(Media Segment,.m4s)组成。 - 初始化片段包含 ftyp(文件类型)和 moov(元数据)box,描述编码参数。
- 媒体片段包含 moof(电影片段)和 mdat(媒体数据)box。
- 相比 TS,fMP4 解码开销更小,兼容性更广。
TS 分片格式:
┌─────────────────────────────────────┐
│ .ts 文件 (6 秒) │
├─────────────────────────────────────┤
│ PAT (PID=0x0000) │
│ └─ PMT PID = 0x1000 │
├─────────────────────────────────────┤
│ PMT (PID=0x1000) │
│ ├─ 视频 PID = 0x0100 (H.264) │
│ ├─ 音频 PID = 0x0101 (AAC) │
│ └─ PCR PID = 0x0100 │
├─────────────────────────────────────┤
│ PES 包 (PID=0x0100, H.264 视频帧) │
│ └─ 188 字节 TS 包序列 │
├─────────────────────────────────────┤
│ PES 包 (PID=0x0101, AAC 音频帧) │
│ └─ 188 字节 TS 包序列 │
└─────────────────────────────────────┘
fMP4 分片格式:
初始化片段 (init.mp4):
┌──────────┬──────────┐
│ ftyp │ moov │
│ 文件类型 │ 编解码参数 │
└──────────┴──────────┘
媒体片段 (segment_N.m4s):
┌──────────┬──────────┬──────────┬──────────┐
│ moof │ mdat │ moof │ mdat │
│ 片段元数据│ 视频帧 │ 片段元数据│ 音频帧 │
└──────────┴──────────┴──────────┴──────────┘HLS 与其他技术的对比
| 特性 | HLS + TS | HLS + fMP4 (CMAF) |
|---|---|---|
| 容器格式 | MPEG-TS | Fragmented MP4 |
| 文件扩展名 | .ts | .m4s + .mp4 (init) |
| 解码复杂度 | 较高(需要 PSI 解析) | 较低(box 结构清晰) |
| 兼容性 | 所有支持 HLS 的设备 | iOS 10+、现代浏览器 |
| 加密支持 | AES-128 / SAMPLE-AES | AES-128 / SAMPLE-AES |
| 分片 overhead | 较高(PES 头部 + TS 头部) | 较低(box 结构紧凑) |
| 多协议复用 | 无法复用 | 可同时用于 HLS 和 DASH |
直播 HLS 的分片更新机制
HLS 服务端持续生成新的媒体分片,并更新 M3U8 播放列表。播放器定期重新请求 M3U8 以获取新的分片列表。
时间轴:
分片: [1] [2] [3] [4] [5] [6] [7] [8] ...
└─── 当前列表 ───┘
(包含分片 1-4)
时间推移 -->
分片: [1] [2] [3] [4] [5] [6] [7] [8] ...
└─── 更新后的列表 ───┘
(包含分片 5-8)播放器通过将 #EXT-X-MEDIA-SEQUENCE 的值与本地已下载的分片序列号对比,判断哪些是新分片。
FLV(HTTP-FLV)
HTTP-FLV 是一种通过 HTTP 长连接持续传输 FLV 格式数据的拉流方案。它将传统的 RTMP 协议中的 FLV 数据封装在 HTTP 响应体中,利用 HTTP 的广泛兼容性实现准实时的低延迟直播。
协议原理
- 播放器向 CDN/源站发起标准的 HTTP GET 请求。
- 服务端返回 HTTP 200 OK,Content-Type 设置为
video/x-flv。 - 服务端持续以 Chunked Transfer Encoding 方式写入 FLV 数据。
- 播放器在 HTTP 连接上持续读取 FLV Tag,实时解码渲染。
- 连接保持开启,直到播放器主动断开或直播结束。
HTTP-FLV 响应:
HTTP/1.1 200 OK
Content-Type: video/x-flv
Transfer-Encoding: chunked
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Connection: keep-alive
[FLV 二进制数据流]
├── FLV Header (9 字节)
│ ├── Signature: "FLV" (0x46 0x4C 0x56)
│ ├── Version: 1 (0x01)
│ ├── Flags: 5 (0x05, 有音频 + 有视频)
│ └── HeaderOffset: 9 (0x00000009)
├── PreviousTagSize0: 0 (4 字节)
├── Script Tag (onMetaData)
├── PreviousTagSize1 (4 字节)
├── Audio Tag (AAC Sequence Header)
├── PreviousTagSize2 (4 字节)
├── Video Tag (AVC Sequence Header)
├── PreviousTagSize3 (4 字节)
├── Video Tag (关键帧数据)
├── PreviousTagSize4 (4 字节)
├── Audio Tag (AAC 音频帧)
├── PreviousTagSize5 (4 字节)
├── Video Tag (P 帧数据)
├── ...FLV Tag 格式
FLV Tag 结构:
┌────────────┬───────────┬───────────┬────────────┐
│ Tag Type │ Data Size │ Timestamp │ Stream ID │
│ 1 字节 │ 3 字节 │ 4 字节 │ 3 字节 │
└────────────┴───────────┴───────────┴────────────┘
┌─────────────────────────────────────┐
│ Tag Data (变长) │
├─────────────────────────────────────┤
│ Previous Tag Size (4 字节) │
└─────────────────────────────────────┘
Tag Type:
8 - 音频 (Audio)
9 - 视频 (Video)
18 - 脚本数据 (Script Data)
视频 Tag Data (Type=9):
┌──────────┬──────────────────────────┐
│ FrameType│ CodecID │
│ 4 bit │ 4 bit │
├──────────┴──────────────────────────┤
│ AVCPacketType (1 byte, H.264 专用) │
│ 0 = AVC Sequence Header │
│ 1 = AVC NALU │
│ 2 = AVC End of Sequence │
├────────────────────────────────────┤
│ CompositionTime (3 bytes, 仅 H.264) │
├────────────────────────────────────┤
│ Video Data (NALU / Sequence Header) │
└────────────────────────────────────┘
音频 Tag Data (Type=8):
┌──────────┬─────────┬─────────────┐
│ SoundFmt │ Rate │ Size/Type │
│ 4 bit │ 2 bit │ 2 bit │
├──────────┴─────────┴─────────────┤
│ AACPacketType (1 byte, AAC 专用) │
│ 0 = AAC Sequence Header │
│ 1 = AAC Raw │
├──────────────────────────────────┤
│ Audio Data (AAC Raw / Header) │
└──────────────────────────────────┘HTTP-FLV 延迟分析
HTTP-FLV 的延迟主要由以下因素决定:
- 传输延迟:HTTP 长连接传输,毫秒级。
- CDN 缓存延迟:CDN 节点基于 GOP 边界缓存,端到端延迟约 2-5 秒。
- 播放器缓冲:播放器需要缓冲 1-2 个 GOP 用于抗抖动。
- 整体延迟:典型配置下 2-5 秒,优化后可降低至 1-2 秒。
适用场景
- 实时监控:对延迟要求较高(1-3 秒)的监控场景。
- 互动直播:教育直播、电商直播等需要较低延迟的场景。
- 移动端直播:FLV 在移动端解码兼容性良好。
DASH(Dynamic Adaptive Streaming over HTTP)
DASH 是 MPEG(ISO/IEC 23009)制定的基于 HTTP 的自适应码率流媒体标准。DASH 与 HLS 功能类似,但采用 XML 格式的 MPD(Media Presentation Description)清单和 fMP4 分片。
MPD 清单
DASH 使用 MPD 文件描述媒体流的元数据、分片信息、码率、分辨率等。MPD 基于 XML 格式,支持多种描述模式。
<?xml version="1.0" encoding="UTF-8"?>
<MPD
xmlns="urn:mpeg:dash:schema:mpd:2011"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="dynamic"
availabilityStartTime="2026-07-12T10:00:00Z"
publishTime="2026-07-12T10:05:00Z"
minimumUpdatePeriod="PT2S"
timeShiftBufferDepth="PT1H"
minBufferTime="PT4S">
<Period id="1" start="PT0S">
<AdaptationSet mimeType="video/mp4" contentType="video"
width="1920" height="1080" frameRate="30"
segmentAlignment="true" startWithSAP="1">
<Representation id="v1" bandwidth="3500000"
codecs="avc1.640028">
<SegmentTemplate
timescale="90000"
duration="540000"
startNumber="0"
initialization="init-v1.mp4"
media="seg-v1-$Number$.m4s"/>
</Representation>
<Representation id="v2" bandwidth="2000000"
codecs="avc1.64001f">
<SegmentTemplate
timescale="90000"
duration="540000"
startNumber="0"
initialization="init-v2.mp4"
media="seg-v2-$Number$.m4s"/>
</Representation>
<Representation id="v3" bandwidth="1000000"
codecs="avc1.64001e">
<SegmentTemplate
timescale="90000"
duration="540000"
startNumber="0"
initialization="init-v3.mp4"
media="seg-v3-$Number$.m4s"/>
</Representation>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" contentType="audio">
<Representation id="a1" bandwidth="128000"
codecs="mp4a.40.2">
<SegmentTemplate
timescale="44100"
duration="264600"
startNumber="0"
initialization="init-a1.mp4"
media="seg-a1-$Number$.m4s"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>MPD 关键属性
| 属性 | 说明 |
|---|---|
type | static 表示点播,dynamic 表示直播 |
availabilityStartTime | 直播流的起始可用时间 |
publishTime | MPD 的发布时间,用于判断更新 |
minimumUpdatePeriod | MPD 更新周期(直播时不断更新) |
timeShiftBufferDepth | 时移回看的缓冲区深度,例如 PT1H 支持回看 1 小时 |
minBufferTime | 播放器最小缓冲时间 |
SegmentTemplate | 分片 URL 模板,支持 $Number$ 和 $Time$ 变量 |
Segment 分片
DASH 标准支持多种分片寻址方式:
- Segment Template:基于模板生成分片 URL,使用
$Number$(序列号)或$Time$(时间戳)变量。 - Segment Timeline:显式列出每个分片的时间和持续时间。
- Segment Base + Segment List:通过
SegmentURL元素直接列出所有分片 URL。
分片寻址模式示例:
1. Template (模板模式):
media="seg-v1-$Number$.m4s"
-> seg-v1-0.m4s, seg-v1-1.m4s, seg-v1-2.m4s
2. Timeline (时间线模式):
<SegmentTimeline>
<S t="0" d="540000" r="2"/> <!-- 从 0 开始,时长 540000 单位,重复 2 次 -->
<S t="1620000" d="540000"/> <!-- 从 1620000 开始,时长 540000 -->
</SegmentTimeline>
3. Base URL (直接列出):
<SegmentList>
<SegmentURL media="seg1.m4s"/>
<SegmentURL media="seg2.m4s"/>
</SegmentList>自适应码率
DASH 的 AdaptationSet 包含多个 Representation,每个 Representation 代表一个码率/分辨率版本。播放器根据网络条件动态选择不同的 Representation。
<AdaptationSet>
<!-- 高码率: 1080p, 3.5 Mbps -->
<Representation bandwidth="3500000" width="1920" height="1080" .../>
<!-- 中码率: 720p, 2.0 Mbps -->
<Representation bandwidth="2000000" width="1280" height="720" .../>
<!-- 低码率: 480p, 1.0 Mbps -->
<Representation bandwidth="1000000" width="854" height="480" .../>
<!-- 流畅: 360p, 0.5 Mbps -->
<Representation bandwidth="500000" width="640" height="360" .../>
</AdaptationSet>HLS vs FLV vs DASH 对比
| 特性 | HLS | HTTP-FLV | DASH |
|---|---|---|---|
| 标准制定者 | Apple | Adobe/社区实践 | MPEG |
| 清单格式 | M3U8 (文本) | 无清单 | MPD (XML) |
| 分片容器 | TS / fMP4 (CMAF) | FLV (流式) | fMP4 / MPEG-TS |
| 传输方式 | HTTP 分片请求 | HTTP 长连接流式 | HTTP 分片请求 |
| 默认延迟 | 6-30 秒 | 2-5 秒 | 6-30 秒 |
| 低延迟优化 | LL-HLS: 2-5 秒 | 不可优化(依赖 GOP) | LL-DASH: 2-5 秒 |
| 自适应码率 | 原生支持 | 不支持(需多路切换) | 原生支持 |
| 苹果生态 | 原生支持(iOS/macOS) | 不支持 | 有限支持 |
| Android 生态 | 支持 | 支持 | 支持 |
| Web 浏览器 | MSE 支持 | MSE + FLV 插件 | MSE 原生支持 |
| 防火墙穿透 | 好(HTTP 80/443) | 好(HTTP 80/443) | 好(HTTP 80/443) |
| 加密支持 | AES-128 / SAMPLE-AES | 依赖 HTTPS | AES-128 / SAMPLE-AES |
| 录制/时移 | 简单(分片持久化) | 困难(需转码) | 简单(分片持久化) |
| CDN 缓存友好 | 很好(静态分片缓存) | 较差(长连接维护成本高) | 很好(静态分片缓存) |
| 浏览器 MSE 支持 | 原生支持 | 需 flv.js 等 JS 解封装 | 原生支持(MediaSource) |
| 行业应用 | 点播/直播/电视 | 低延迟直播 | 多平台自适应分发 |
协议选择建议:
- 苹果生态全覆盖:HLS(TS 或 fMP4)。
- 低延迟互动直播:HTTP-FLV 或 LL-HLS。
- 跨平台自适应分发:HLS(CMAF)或 DASH。
- 需要统一交付:HLS + fMP4(CMAF)可同时服务 HLS 和 DASH 客户端。
- 监控/推流场景:HTTP-FLV,延迟低且实现简单。
CDN 分发
CDN 架构
CDN(Content Delivery Network)是直播分发的基础设施,通过在全球各地部署缓存节点,将直播流从源站就近分发给观众,有效降低回源压力和播放延迟。
分层架构
┌────────────────┐
│ 中心源站 │
│ (Origin) │
│ 直播服务器 │
└────────┬───────┘
│
┌─────────────┼─────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ 区域节点 A │ │ 区域节点 B │ │ 区域节点 C │
│ (华南) │ │ (华东) │ │ (华北) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
┌─────────┼───────┐ ┌───┼───────┐ ┌───┼─────────┐
│ │ │ │ │ │ │ │ │
┌─────┴──┐ ┌────┴──┐ ┌─┴─┐ │ ┌─┴──┐ ┌─┴┐ │ ┌──┴──┐ ┌──┴──┐
│边缘节点1│ │边缘节点2│ │...│ │ │... │ │ │ │ │ ... │ │ ... │
│ (广州) │ │ (深圳) │ │ │ │ │ │ │ │ │ │ │ │ │
└───┬────┘ └───┬───┘ └───┘ │ └────┘ └───┘ │ └─────┘ └─────┘
│ │ │ │
┌───┴───┐ ┌───┴───┐ │ │
│播放器 1│ │播放器 2│ │ │
└───────┘ └───────┘ │ │
┌────┴────┐ ┌────┴────┐
│播放器 3 │ │播放器 4 │
└─────────┘ └─────────┘各层职责
- 中心源站:接收推流端上行流,提供转码和格式封装能力,是分发的唯一数据来源。
- 区域节点:缓存区域内热门直播流,减少跨区域回源流量。区域节点之间通常不互通。
- 边缘节点:直接服务终端观众,从区域节点或中心源站回源。边缘节点是最接近用户的层级。
缓存策略
直播 CDN 的缓存策略与点播 CDN 有显著差异:
- 分片粒度缓存:对于 HLS/DASH,CDN 按
.ts、.m4s分片粒度缓存,分片生成后即可缓存。 - GOP 级缓存:对于 HTTP-FLV,CDN 基于 GOP(Group of Pictures)边界缓存和转发。CDN 需要将 FLV 流按照 GOP 边界切分为缓存单元。
- 时间戳修正:CDN 边缘节点在转发 FLV 流时,需要对时间戳进行修正,确保连续性和递增性。
- 预缓存:对于热门直播流,CDN 提前从源站拉取分片到边缘节点,减少首次播放等待时间。
直播 CDN 特殊调度
GOP 缓存
GOP(Group of Pictures)是视频编码中的帧组结构,以关键帧(I 帧)开始。直播 CDN 在缓存和回源时以 GOP 为基本单元:
- 播放器加入直播时,CDN 需从最近的 GOP 起始位置(I 帧)开始发送数据,确保播放器能正确解码。
- CDN 边缘节点缓存最近 1-2 个完整 GOP,新观众加入时可直接从缓存提供服务。
- 对于 HTTP-FLV,CDN 需要缓存 GOP 起始位置的时间戳映射,以便新接入客户端能快速开始播放。
GOP 缓存示意:
时间: |----- GOP 1 -------|----- GOP 2 -------|----- GOP 3 -------|
帧: I B B P B B P B B P I B B P B B P B B P I B B P B B P B B P
↑ ↑ ↑
| | |
└─ 缓存起始点 └─ 缓存起始点 └─ 缓存起始点
(播放器从此接入) (播放器从此接入) (播放器从此接入)回源策略
- 回源模式:当边缘节点未命中缓存时,向区域节点回源;区域节点未命中时,向中心源站回源。
- 回源保活:边缘节点与区域节点维护长连接,减少频繁回源建立的延迟。
- 多级回源:支持配置多个源站地址,当主源站不可用时自动切换备源。
- 回源鉴权:边缘节点在回源时携带鉴权令牌,确保只有授权的 CDN 节点能获取源流。
预热
直播预热是指在直播开始前,预先将推流分发的相关信息推送到 CDN 边缘节点,以降低首播延迟。
- URL 预热:在直播开始前,将 M3U8/MPD 等索引文件 URL 推送到 CDN 预热任务。
- 节点预热:将热门直播流的配置信息推送到所有边缘节点,提前建立回源通道。
- 带宽预热:预告大规模直播事件的带宽需求,提前调配资源。
带宽封顶
CDN 提供带宽封顶功能,防止突发流量造成成本失控:
- 域名级别封顶:为单个域名设置带宽上限。
- 区域级别封顶:为特定区域节点设置带宽上限。
- 封顶策略:达到上限时可选择拒绝新连接、切换到低码率流或者降级到备用源站。
- 监控告警:实时监控带宽使用情况,触发阈值时自动告警。
HTTPS 拉流
SSL/TLS 证书
HTTPS 拉流通过 SSL/TLS 证书加密 HTTP 传输内容,保护直播数据不被中间人窃听或篡改。
- 证书类型:DV(域名验证)、OV(组织验证)、EV(扩展验证)证书均可用于直播拉流。
- 通配符证书:
*.live.example.com可用于所有子域名直播拉流,简化证书管理。 - SNI(Server Name Indication):CDN 边缘节点根据客户端 SNI 扩展返回对应域名证书。
HTTPS 拉流握手过程:
播放器 CDN 边缘节点
│ │
│──── TCP 三次握手 ─────────────────────────│
│──── TLS ClientHello ──────────────────────│
│ (SNI: push.live.example.com) │
│◄─── TLS ServerHello + Certificate ────────│
│ (证书: *.live.example.com) │
│──── 密钥交换 ─────────────────────────────│
│──── ChangeCipherSpec ─────────────────────│
│──── Finished ─────────────────────────────│
│◄─── ChangeCipherSpec ─────────────────────│
│◄─── Finished ─────────────────────────────│
│ │
│──── HTTP GET /live/stream.m3u8 ──────────│ (加密通道)
│◄─── HTTP 200 OK (加密传输) ──────────────│CDN HTTPS 加速
CDN HTTPS 加速通过以下技术降低 TLS 开销:
- Session 复用:复用已建立的 TLS Session,减少握手次数。CDN 边缘节点维护 Session 缓存,同一客户端复用 Session ID 或 Session Ticket,跳过证书验证步骤。
- OCSP Stapling:CDN 节点主动缓存证书状态查询结果,代替客户端直接查询 OCSP 响应服务器,减少证书验证延迟。
- HSTS(HTTP Strict Transport Security):CDN 响应头部携带
Strict-Transport-Security,强制浏览器/播放器使用 HTTPS 连接。 - HTTP/2 Push:利用 HTTP/2 Server Push 预推送分片数据,减少客户端请求次数。
CDN HTTPS 响应头部示例:
HTTP/1.1 200 OK
Content-Type: video/x-flv
Content-Length: 12345678
Cache-Control: public, max-age=10
Access-Control-Allow-Origin: *
Strict-Transport-Security: max-age=31536000; includeSubDomains
Timing-Allow-Origin: *HTTPS 性能开销
| 阶段 | 非 HTTPS | HTTPS | 额外耗时 |
|---|---|---|---|
| TCP 连接 | 1 RTT | 1 RTT | 0 |
| TLS 握手 | 无 | 2 RTT(TLS 1.2)/ 1 RTT(TLS 1.3) | 1-2 RTT |
| 证书验证 | 无 | ~50-200ms(OCSP Stapling 可优化) | ~50-200ms |
| 数据传输 | 明文 | 加解密 | ~5-10% CPU 开销 |
| Session 复用 | 无 | 0 RTT(TLS 1.3 0-RTT) | 0 |
在现代 CDN 架构中,HTTPS 的额外延迟通过 Session 复用和边缘节点就近服务得到有效控制。对于 HLS/DASH 等分片协议,HTTPS 加密的开销集中在首片加载阶段。
自适应码率(ABR)
多码率转码
多码率转码(Multi-Bitrate Transcoding)是将源直播流实时转换为多个不同码率和分辨率的副本,供播放器根据网络条件自适应选择。
转码配置
多码率转码示例(一路源流转为四路输出):
源流: 1920x1080 @ 8 Mbps H.264 + 192k AAC
转码输出:
输出 1 (高码率): 1920x1080 @ 3.5 Mbps H.264 + 128k AAC
输出 2 (中码率): 1280x720 @ 2.0 Mbps H.264 + 96k AAC
输出 3 (低码率): 854x480 @ 1.0 Mbps H.264 + 64k AAC
输出 4 (流畅): 640x360 @ 0.5 Mbps H.264 + 48k AACGOP 对齐
多码率转码的关键要求是 GOP 对齐。所有码率副本的 I 帧必须出现在相同的时间位置,以保证 ABR 切换时播放器能无缝过渡到新码率流的 I 帧开始解码。
GOP 对齐:
时间: 0s 2s 4s 6s 8s
1080p: I B B P B B P B B I B B P B B P B B I B B P B B P B B I
720p: I B B P B B P B B I B B P B B P B B I B B P B B P B B I
480p: I B B P B B P B B I B B P B B P B B I B B P B B P B B I
↑ ↑ ↑
└── 所有流的 I 帧对齐在同一时间点 ──┘转码实现
FFmpeg 多码率转码示例:
ffmpeg -i rtmp://origin/live/stream \
-c:v libx264 -b:v:0 3500k -s:0 1920x1080 -g 60 -keyint_min 60 \
-c:v libx264 -b:v:1 2000k -s:1 1280x720 -g 60 -keyint_min 60 \
-c:v libx264 -b:v:2 1000k -s:2 854x480 -g 60 -keyint_min 60 \
-c:v libx264 -b:v:3 500k -s:3 640x360 -g 60 -keyint_min 60 \
-c:a aac -b:a:0 128k -b:a:1 96k -b:a:2 64k -b:a:3 48k \
-map v:0 -map a:0 -map v:0 -map a:0 -map v:0 -map a:0 -map v:0 -map a:0 \
-f flv rtmp://cdn/publish/live_streamABR 切换算法
ABR(Adaptive Bitrate,自适应码率)算法是播放器端根据网络带宽和播放状态动态选择码率的核心逻辑。不同的 ABR 算法在切换灵敏度、稳定性和用户体验上各有侧重。
基于吞吐量的算法(Throughput-based)
通过测量下载分片的实际吞吐量估算当前可用带宽,选择不高于估算带宽的最高码率。
算法伪代码:
function selectBitrate(throughputHistory, bitrateList):
// 使用滑动窗口的吞吐量平均值
avgThroughput = weightedMovingAverage(throughputHistory, windowSize=5)
// 安全系数,避免带宽波动导致频繁切换
safeThroughput = avgThroughput * SAFETY_FACTOR // 通常 0.75-0.85
// 选择不高于 safeThroughput 的最高可用码率
selectedBitrate = lowestBitrate
for bitrate in bitrateList (降序排列):
if bitrate <= safeThroughput:
selectedBitrate = bitrate
break
// 速率限制 - 避免码率剧烈抖动
if abs(selectedBitrate - currentBitrate) / currentBitrate > RATE_CHANGE_THRESHOLD:
selectedBitrate = clampRateChange(selectedBitrate, currentBitrate)
return selectedBitrate
function weightedMovingAverage(history, windowSize):
// 较近的数据权重更高
weights = [0.5, 0.25, 0.15, 0.07, 0.03] // 5 个采样点
sum = 0
weightSum = 0
for i = 0 to min(windowSize, len(history)) - 1:
sum += history[-i-1] * weights[i]
weightSum += weights[i]
return sum / weightSum优势:响应速度快,能及时上调码率。缺点:纯吞吐量估算不准确,可能与实际解码需要冲突。
基于缓冲的算法(Buffer-based)
通过监控播放器缓冲区的填充水平决定码率升降。缓冲区分为多个水位区间,不同区间对应不同的码率调整策略。
缓冲区水位分级:
缓冲区状态 码率决策
┌────────────────────┐
│ 紧急区 (0-2 秒) │ -> 降低到最低码率,避免卡顿
├────────────────────┤
│ 保守区 (2-6 秒) │ -> 保持或降低码率
├────────────────────┤
│ 稳定区 (6-15 秒) │ -> 保持当前码率
├────────────────────┤
│ 积极区 (15-30 秒) │ -> 上调码率(缓冲区充足)
├────────────────────┤
│ 充满区 (30+ 秒) │ -> 上调到最高码率
└────────────────────┘
算法伪代码:
function selectBitrate(bufferLevel, bitrateList, currentBitrate):
if bufferLevel < BUFFER_URGENT_THRESHOLD: // < 2 秒
return min(bitrateList) // 立即降级到最低码率
if bufferLevel < BUFFER_SAFE_THRESHOLD: // < 6 秒
if currentBitrate > median(bitrateList):
return stepDown(currentBitrate, bitrateList)
return currentBitrate
if bufferLevel < BUFFER_STABLE_THRESHOLD: // < 15 秒
return currentBitrate // 保持
if bufferLevel < BUFFER_AGGRESSIVE_THRESHOLD: // < 30 秒
if lastUpgradeAttemptFailed:
return currentBitrate
return stepUp(currentBitrate, bitrateList)
return max(bitrateList) // 缓冲区充足,使用最高码率优势:直接反映播放体验,卡顿风险低。缺点:缓冲区大时上调码率不够积极。
混合算法(Buffer + Throughput)
结合吞吐量估算和缓冲区状态的混合算法是目前主流方案。在缓冲区安全时使用吞吐量估算决定码率,在缓冲区危险时以缓冲区优先。
混合 ABR 算法:
1. 每下载完一个分片,记录:
- 实际下载吞吐量 (throughput = 分片大小 / 下载耗时)
- 当前缓冲区水位 (bufferLevel)
- 最近卡顿事件 (rebufferingEvents)
2. 码率决策:
if 最近发生卡顿:
强制降一级码率
提高缓冲区目标水位 (等待缓冲区填充后再考虑上调)
if bufferLevel < 紧急阈值:
降级到最低码率
重置吞吐量历史 (避免历史数据误导)
if bufferLevel < 安全阈值:
执行保守策略: 仅降级,不升级
使用当前缓冲区趋势判定是否需要降级
if bufferLevel >= 安全阈值:
使用吞吐量估算选择目标码率
应用安全系数 (0.8) 和延迟切换计数器
检查分片下载时间是否超过分片时长 (indicating congestion)
3. 输出稳定化:
- 码率切换频率限制 (每秒最多切换一次)
- 码率变化幅度限制 (一次最多升/降一级)
- 同码率稳定时间要求 (切到新码率后至少停留 10 秒)平滑切换策略
平滑切换策略关注的是切换过程中的用户体验:
- 无缝切换:利用 DASH/HLS 的分片对齐特性,在当前分片结束后使用新码率的下一分片继续播放,不存在画面中断。
- 渐进切换:逐级切换而非跳级切换。从 480p 直接升到 1080p 会导致视觉突兀,逐级(480p -> 720p -> 1080p)切换更平滑。
- 延迟切换:在检测到带宽变化后,等待 2-3 个分片确认趋势稳定后再执行切换,避免瞬时波动导致的乒乓切换。
- 降级优先:当网络恶化时优先降级保证流畅,当网络恢复时延迟升级确认稳定。
乒乓切换问题:
不稳定的 ABR 算法:
时间: 0s 10s 20s 30s 40s
码率: 720p -> 1080p -> 720p -> 1080p -> 720p
(反复切换,用户体验差)
稳定的 ABR 算法(延迟切换 + 滞回):
时间: 0s 10s 20s 30s 40s 50s 60s
码率: 720p -> 720p -> 720p -> 720p -> 720p -> 1080p -> 1080p
(带宽提升) (确认稳定) (切换) (保持)
(切换次数少,体验好)滞回机制(Hysteresis):上调码率需要更高的阈值,下调码率需要更低的阈值,在阈值之间维持当前码率。
滞回区间示例:
切换规则:
- 当前码率 720p (2000kbps)
上调至 1080p: 需估算带宽 > 3500kbps (1.75x 阈值)
- 当前码率 1080p (3500kbps)
下调至 720p: 需估算带宽 < 2500kbps (0.71x 阈值)
即上调更保守,下调相对激进。Player 端 ABR 实现逻辑
播放器端的 ABR 功能通常抽象为独立的 ABR 控制器,与播放核心解耦。以下是典型的 ABR 实现架构:
ABR 控制器架构
播放器组件关系:
┌─────────────────────────────────────────────────────┐
│ 播放器核心 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 下载引擎 │ │ 缓冲区 │ │ ABR 控制器 │ │
│ │ │ │ │ │ ┌────────────┐ │ │
│ │ HTTP请求 │ │ 数据缓冲 │ │ │ 吞吐量监控 │ │ │
│ │ 分片缓存 │ │ 帧缓冲 │ │ │ 缓冲区监控 │ │ │
│ │ │ │ │ │ │ ABR 决策引擎│ │ │
│ │ │ │ │ │ └────────────┘ │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ UI 层 (码率切换指示、画质选择菜单、统计信息) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘ABR 决策流程
播放器在一次分片请求周期内的 ABR 决策流程如下:
1. 下载完成回调
分片 X 下载完毕
├── 记录: 分片大小、下载耗时、TCP 连接时间
├── 计算: 本次吞吐量 = 分片大小 / 下载耗时
└── 推送: 吞吐量采样点进入历史队列
2. ABR 决策触发
├── 准备请求分片 X+1
├── 采集当前状态:
│ ├── 缓冲区水位 (bufferLevel)
│ ├── 播放帧率 (currentFPS)
│ ├── 最近卡顿记录 (rebufferingCount)
│ └── 当前码率 (currentBitrate)
└── 执行 ABR 算法 (混合模式)
3. 码率选择
├── 计算安全吞吐量估计值
├── 检查缓冲区水位约束
├── 应用滞回规则防止乒乓切换
├── 应用切换频率限制
└── 输出: 目标码率 (targetBitrate) 和分片 URL
4. 执行切换
├── if targetBitrate != currentBitrate:
│ ├── 更新分片请求 URL 到新码率流
│ ├── 触发 UI 层显示码率切换提示
│ └── 记录切换日志 (用于 QoE 统计)
└── 发起分片 X+1 下载请求QoE 监控与反馈
播放器端收集 QoE(Quality of Experience)指标,用于 ABR 算法调优和播放质量评估:
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 平均码率 | 总下载数据量 / 总播放时长 | 反映画质水平 |
| 卡顿率 | 总卡顿时长 / 总播放时长 | 反映流畅度 |
| 卡顿次数 | 每小时的 rebuffer 事件数 | 反映中断频率 |
| 码率切换次数 | 单位时间内码率变化次数 | 反映稳定性 |
| 首次缓冲时间 | 播放器启动到首帧渲染的时间 | 反映首开体验 |
| ABR 上调延迟 | 带宽恢复到切换码率的滞后时间 | 反映算法灵敏度 |
主流播放器 ABR 实现参考
- shaka-player(Google):默认使用带宽估算算法,支持通过配置切换为 buffer-based 算法。提供
abr.manager接口自定义 ABR 逻辑。 - hls.js:基于带宽估算 + 缓冲区的混合算法。使用 EWMA(指数加权移动平均)计算吞吐量,支持
abrController自定义扩展。 - ExoPlayer(Android):使用
DefaultTrackSelector,基于带宽估算和缓存持续时间决策,支持多音轨和自适应。 - AVPlayer(Apple):系统级 ABR 实现,使用苹果私有的 adaptive switching 算法,在 HLS 播放中自动管理多码率切换,不对外暴露算法细节。
播放器 ABR 配置示例 (hls.js):
const config = {
// ABR 算法配置
abrController: AbrController,
abrBandwidthFactor: 0.9, // 带宽安全系数
abrBandwidthUpFactor: 0.7, // 上调系数 (更保守)
abrMaxWithRealBitrate: true, // 使用实际码率而非声明码率
abrEwmaDefaultEstimate: 500000, // 初始带宽估算 (500kbps)
abrEwmaFastHalfLife: 3, // 快速 EMA 半衰期 (秒)
abrEwmaSlowHalfLife: 15, // 慢速 EMA 半衰期 (秒)
// 缓冲区策略
maxBufferLength: 30, // 最大缓冲长度 (秒)
maxMaxBufferLength: 60, // 绝对最大缓冲长度
backbufferLength: Infinity, // 后退缓冲区长度
// 码率切换限制
capLevelToPlayerSize: true, // 根据播放器尺寸限制最高码率
autoStartLoad: true
};