# VOD 深度剖析（六）：自适应码率——播放器如何自动切换清晰度

> 深入解析 ABR 的底层原理：基于吞吐量、基于缓冲区（BBA）、BOLA、MPC 以及 Pensieve 算法。并给出码率阶梯与短视频场景的实用工程建议。

- 作者: zhuermu
- 发布: 2026-05-10
- 网页版: https://zhuermu.com/blog/vod-deep-dive-part-6-adaptive-bitrate-streaming/

---
> *本文是 [VOD 流媒体深度剖析](/blog/vod-deep-dive-part-1-video-fundamentals/) 系列的第六篇。*

---

## 为什么 ABR 很重要

想象一下你在地铁里追剧，网络在 4G、弱 4G 和 5G 之间反复波动：

- 如果 App 锁定在 1080p → 每次信号变弱都会**卡顿、转圈缓冲**。
- 如果 App 锁定在 360p → 即便有 5G 可用，画面依然**模糊不清**。

理想状态是：App **根据网络状况自动调整清晰度**，让你观看时不被打断。这就是 **ABR（自适应码率，Adaptive Bitrate）**。

---

## ABR 决策循环

```
After downloading each segment, the player asks itself:

┌──────────────────────────┐
│  How fast did the last    │
│  segment download?        │
│  How much buffer remains? │
│  What's my bandwidth      │
│  estimate?                │
│  Can the device handle    │
│  the current tier?        │
└──────────────────────────┘
             │
             ▼
       ┌──────────┐
       │ Pick next │
       │ tier      │
       └──────────┘
             │
             ▼
     Download next segment
```

这个循环每 1–4 秒运行一次（在每个分片下载完成后触发）。

**关键信号**：近期吞吐量、缓冲区水位（已预下载内容的秒数）、当前码率，以及清单（manifest）中提供的可选码率阶梯。

---

## 策略 1：基于吞吐量（Throughput-Based）

最直观的做法：

```
Estimated bandwidth = average download speed of recent N segments
Next tier bitrate ≈ estimated bandwidth × 0.8   (20% safety margin)
```

**举例**：近期速度为 5 Mbps → 5 × 0.8 = 4 Mbps → 从 [360p(0.5) / 720p(2.5) / 1080p(5) / 4K(15)] 中挑选 ≤ 4 Mbps 的最高档 → **720p**。

**优点**：简单直观。

**缺点**：HTTP/TCP 的吞吐量测量本身噪声很大（慢启动、队头阻塞、TLS 握手），会导致清晰度频繁震荡——"一会儿清晰、一会儿模糊"。

应用场景：早期 hls.js 的 `bandwidthFraction` 策略。

---

## 策略 2：基于缓冲区（BBA）

另一种思路：**完全忽略吞吐量，只看缓冲区水位**。

```
Buffer < 10s  → download lowest tier (prevent stalling)
Buffer 10-30s → download mid tier
Buffer > 30s  → download highest tier (plenty of runway)
```

```
Bitrate tier
  ▲
Max │                    ┌────────
    │                   ╱
Mid │              ┌───╱
    │             ╱
Min │────────────┘
    └─────────────────────────► Buffer (seconds)
    0   10s      30s      60s
```

由 Huang 等人（斯坦福 / Netflix）在 2014 年 SIGCOMM 论文 *A Buffer-Based Approach to Rate Adaptation* 中提出。

**优点**：不受吞吐量噪声影响，清晰度稳定（切换更少）。

**缺点**：启动阶段过于激进地降低清晰度（此时缓冲区初始为空）；且无法充分利用可用带宽。

---

## 策略 3：BOLA（Lyapunov 优化）

**BOLA** = **B**uffer **O**ccupancy based **L**yapunov **A**lgorithm（基于缓冲区占用的 Lyapunov 算法）

由 Spiteri、Urgaonkar 和 Sitaraman 于 2016 年 INFOCOM 发表。**是 dash.js 的默认算法之一。**

### 核心思想

BOLA 将 ABR 建模为一个**多目标优化问题**：

```
maximize:  average_quality  -  V × (stall_penalty + switching_penalty)
```

`V` 是一个权衡参数：V 越大越保守（优先避免卡顿）；V 越小越激进（优先追求清晰度）。

作者借助 **Lyapunov 优化理论**证明：**仅依据当前缓冲区水位就能做出接近最优的决策**。

### 简化伪代码

```python
def bola_select_bitrate(buffer_level, bitrates, V):
    best_bitrate = None
    best_score = float('-inf')

    for r in bitrates:
        utility = log(r)  # Diminishing returns on quality
        score = buffer_level * utility - V * r
        if score > best_score:
            best_score = score
            best_bitrate = r

    return best_bitrate
```

### 工程实践

dash.js 会动态地将 BOLA 与吞吐量规则结合：启动阶段（缓冲区较低）使用基于吞吐量的策略，一旦缓冲区健康后切换到 BOLA。

---

## 策略 4：MPC（模型预测控制）

由 Yin、Jindal、Sekar 和 Sinopoli 于 2015 年 SIGCOMM 发表。

### 思想

不再逐个分片做决策，而是**预测未来带宽**并**联合优化接下来的 K 个分片**：

```
Currently at segment n.
Predict bandwidth for the next 5 segments: T1, T2, T3, T4, T5.
Try all bitrate combinations and pick the one that maximizes:
  (total quality - stall penalty - switching penalty)
```

带宽预测通常采用**调和平均数**（相比算术平均数对慢速样本更敏感）或 **EWMA**（指数加权移动平均）。

**优点**：比 BOLA 更聪明（会向前看）。**缺点**：计算量略大；一旦预测出错就是**垃圾进、垃圾出**（garbage in, garbage out）。

---

## 策略 5：Pensieve（AI 驱动的 ABR）

由 Mao、Netravali 和 Alizadeh（MIT）于 2017 年 SIGCOMM 发表。

### 思想

**用强化学习（A3C）训练一个神经网络来做 ABR 决策。**

**输入特征**：过去 K 秒的吞吐量、当前缓冲区、上一次的码率、剩余分片数、可选码率阶梯。

**输出**：下一个分片选用哪个码率档位。

**训练方式**：在真实 / 合成的网络轨迹上模拟数百万次会话，直到网络学到最优策略。

在特定测试集上表现优于 BOLA 和 MPC，但也有局限：对未见过的网络模式泛化能力不确定、可解释性低，且需要持续的在线微调。

头部公司（Netflix、YouTube）已在内部构建了类似的基于学习的 ABR。对绝大多数平台而言，BOLA 或 MPC 已经绰绰有余。

---

## 业界实际怎么做

- **Netflix**：客户端采用类 BBA 策略 + 服务端提示（"CDN 负载较高，请降级"）+ 基于 VMAF 优化的码率阶梯。
- **YouTube**：自研算法，纳入观看时长预测；辅以大量 A/B 测试。
- **其他所有人**：使用开源播放器（hls.js、Shaka Player、ExoPlayer）的默认 ABR，再做参数调优。

---

## 短视频：ABR 的特殊考量

竖屏短视频与长视频 VOD 在几个关键点上存在差异：

- **首帧时间（TTFF）至关重要**——如果用户在划动后 300ms 内看不到画面，就会划走。
- **单集时长短**（60–90 秒）：ABR 可能还没来得及提升清晰度，这一集就已经播完了。
- **多集预加载**：App 可能在后台提前下载 N+1、N+2 集的分片。

### 常见调整

- **降低启动档位**（加快首播速度）
- **放慢向上切换的速度**（避免刚启动就卡顿）
- **以低清晰度预加载**（为后台下载节省带宽）
- **当前正在播放的这一集给高清晰度**（前台优先）

---

## 常见的 ABR 问题与工程建议

### 为什么播放中途清晰度会突然下降？

通常是：带宽测量值骤降（蜂窝信号变化、路由器信道切换）、CDN 边缘节点异常，或设备因过热而降频。

### 为什么在良好的 Wi-Fi 下还卡在低清晰度？

启动策略过于保守、吞吐量历史被旧的慢速样本污染，或者清单里根本没有高码率档位（转码时没有生成 1080p）。

### 为什么清晰度一直来回震荡？

码率阶梯的相邻档位太接近，算法对吞吐量噪声过于敏感。解决办法：**让相邻档位间隔约 1.5×**、增加切换惩罚项，或从基于吞吐量的策略切换到 BOLA。

### 最重要的 ABR 配置不是算法，而是码率阶梯。

糟糕的阶梯会让任何算法都失效。设计原则：

- 相邻档位在码率上应**相差 1.5–2 倍**（太接近 = 切换毫无意义；太远 = 跳变突兀）
- 最低档位必须足够低，以覆盖 2G / 弱 3G 用户
- 最高档位不应浪费带宽（在手机上，8 Mbps 的 1080p 与 5 Mbps 看起来几乎没有区别）

[逐标题编码（Per-title encoding）](/blog/vod-deep-dive-part-2-video-codecs-h264-h265-av1/) 会为每个片源定制最优的码率阶梯。

---

## 关键要点

1. ABR = 自适应码率。每个分片下载后，播放器决定下一个分片下载哪个档位。
2. 五种经典策略：
   - **基于吞吐量**：简单但噪声大
   - **基于缓冲区（BBA）**：稳定但保守
   - **BOLA**：接近最优、经过生产验证（dash.js 默认）
   - **MPC**：会向前看，更聪明但依赖预测
   - **Pensieve**：AI 驱动，被头部公司采用
3. **码率阶梯比算法更重要。**
4. 短视频的 ABR 必须针对 TTFF 和预加载做调优。
5. 档位间隔约 1.5–2 倍，最低档足够低，最高档合理。

---

*上一篇：* [第五篇：流媒体协议](/blog/vod-deep-dive-part-5-streaming-protocols-hls-dash/)

*下一篇：* **[第七篇：CDN 分发](/blog/vod-deep-dive-part-7-cdn-distribution/)**

---

## 常见问题

### 视频播放器的自适应码率（ABR）是怎么工作的？

播放器每下载完一个分片就跑一次决策循环（约每 1～4 秒一次）：看最近的下载速度、缓冲区还剩多少秒、当前码率，以及清单里有哪些档位可选，然后决定下一个分片下载哪个档位。带宽好就升档，带宽差就降档，缓冲区快见底就进一步降档防止卡顿。正是这个循环让你在网络波动时也能流畅观看。

### 为什么看视频时清晰度一会儿高清一会儿模糊，来回跳？

这种震荡通常有两个原因：码率阶梯相邻档位间隔太小，或者算法对吞吐量噪声过于敏感——HTTP/TCP 的测速本身就受慢启动、队头阻塞、TLS 握手等因素干扰。解决办法：让相邻档位的码率相差约 1.5 倍、给算法加上切换惩罚项，或者从纯吞吐量策略换成 BOLA 这类基于缓冲区的算法。

### BOLA 算法是什么？为什么 dash.js 默认采用它？

BOLA（基于缓冲区占用的 Lyapunov 算法）发表于 2016 年 INFOCOM，它把 ABR 建模成优化问题：最大化平均画质，同时对卡顿和频繁切换施加惩罚。作者用 Lyapunov 优化理论证明，只看当前缓冲区水位就能做出接近最优的决策，完全不依赖噪声很大的带宽预测。dash.js 将其作为默认算法之一，并在启动阶段（缓冲区还低时）先用吞吐量策略、缓冲健康后再切到 BOLA。


---

## 参考资料

- [RFC 8216: HTTP Live Streaming](https://datatracker.ietf.org/doc/html/rfc8216) — IETF
- [hls.js](https://github.com/video-dev/hls.js) — GitHub
- [Shaka Player](https://github.com/shaka-project/shaka-player) — Google / GitHub
