前言
BOLA(Buffer Occupancy based Lyapunov Algorithm)是DASH视频流中一种经典的基于播放缓冲区的(Buffer-based)ABR(自适应码率)算法,并且其改进版本是如今dash.js开源播放器的默认ABR算法。本文基于dash.js v2.6.0介绍BOLA的代码实现。关于BOLA的原理部分,可参见:BOLA (INFOCOM ’16) 核心算法逻辑。
在dash.js中实现BOLA版本为BOLA-O,其文件路径为:src\streaming\rules\abr\BolaRule.js。其中,ABR算法逻辑对应getMaxIndex函数(详见:如何在dash.js中添加自定义ABR规则?)。
注:
- 选择v2.6.0版本,一是遵从BOLA论文的建议,二是因为后续版本中实现的是BOLA-E和DYNAMIC算法
- BOLA代码中引入了placeholder(虚拟buffer),其为后续改进版本BOLA-E的核心设计(详见:dash.js的ABR逻辑),本文不讨论这部分内容。
系列博文索引:
- DASH标准&ABR算法介绍
- dash.js的ABR逻辑
- ABR算法研究综述阅读笔记
- 经典ABR算法介绍:FESTIVE (CoNEXT '12) 论文阅读笔记
- 经典ABR算法介绍:BBA (SIGCOMM ’14) 设计与代码实现
- 经典ABR算法介绍:BOLA (INFOCOM '16) 核心算法逻辑
- 经典ABR算法介绍:BOLA (INFOCOM '16) dash.js代码实现
- 经典ABR算法介绍:Pensieve (SIGCOMM ‘17) 原理及训练指南
BOLA的三种状态
BOLA的代码实现中存在三种状态:
- BOLA_STATE_ONE_BITRATE:特殊case,如仅有一个码率或者初始化失败
- BOLA_STATE_STARTUP:默认初始状态,基于placeholder buffer进行码率决策
- BOLA_STATE_STEADY:buffer足够时,基于BOLA进行决策
主要关注第三种状态即可,对应BOLA的核心逻辑。
getMaxIndex主逻辑
(注:此部分忽略placeholder相关逻辑)
getMaxIndex为dash.js的ABR算法决策函数,其在BOLA中的主流程如下:
- 初始化
- 获取BOLA状态:getBolaState
- 第一次:getInitialBolaState
- 计算BOLA参数:calculateBolaParameters
- 置为BOLA_STATE_STARTUP
- 否则:checkBolaStateStableBufferTime
- 第一次:getInitialBolaState
- 根据BOLA的状态进行码率决策
- BOLA_STATE_STARTUP:
- 启动阶段基于RB选择码率:getQualityForBitrate
- 若buffer高于视频块时长,则切换为BOLA_STATE_STEADY状态
- BOLA_STATE_STEADY:
- 稳定阶段基于BOLA-BASIC选择码率:getQualityFromBufferLevel(BOLA的核心决策逻辑,与论文一致,不再赘述,可参见:BOLA (INFOCOM ’16) 核心算法逻辑)
- BOLA-O逻辑
- BOLA_STATE_STARTUP:
参数计算
BOLA代码中有Vp和gp两个参数,分别对应其论文中 V V V和 γ \gamma γ两个参数与视频块时长 p p p的乘积。参数计算一般仅在会话启动前初始化阶段进行(除非目标buffer发生了变化)。
注意代码实现的参数计算与论文中给出的参考值并不一致。看代码中的注释:
// If using Math.log utilities, we can choose Vp and gp to always prefer bitrates[0] at minimumBufferS and bitrates[max] at bufferTarget.
// (Vp * (utility + gp) - bufferLevel) / bitrate has the maxima described when:
// Vp * (utilities[0] + gp - 1) === minimumBufferS and Vp * (utilities[max] + gp - 1) === bufferTarget
// giving:
const gp = (utilities[highestUtilityIndex] - 1) / (bufferTime / MINIMUM_BUFFER_S - 1);
const Vp = MINIMUM_BUFFER_S / gp;
// note that expressions for gp and Vp assume utilities[0] === 1, which is true because of normalization
可以看出,代码中参数设置的逻辑是:使得BOLA可以在缓冲区水平为minimumBufferS时选择最低码率,在bufferTarget时选择最高码率,这个设定类似于BBA的双阈值。
结合原论文,假设 M M M表示最高质量的码率级别,1表示最低质量的码率级别。 B m a x B_{max} Bmax代表选择最高码率的buffer阈值,对应视频块数量为 Q m a x Q_{max} Qmax; B m i n B_{min} Bmin代表选择最低码率的buffer阈值,对应视频块数量为 Q m i n Q_{min} Qmin。令 v m v_m vm表示码率级别 m ∈ { 1 , 2 , … , M } m\in\{1,2,\dots,M\} m∈{1,2,…,M}对应的效用(utility), S m S_m Sm为对应码率级别的视频块大小。
选择最低码率的参数设定需要满足:
(
V
(
v
1
+
γ
p
)
−
Q
m
i
n
)
/
S
1
=
0
(V(v_1 + \gamma p) - Q_{min}) / S_1 = 0
(V(v1+γp)−Qmin)/S1=0
两边同时乘以视频块时长
p
p
p,则有下式:
V
p
(
v
1
+
γ
p
)
=
B
m
i
n
Vp(v_1 + \gamma p) = B_{min}
Vp(v1+γp)=Bmin
其中,效用
v
m
v_m
vm基于自然对数计算,有
v
1
=
0
v_1=0
v1=0。BOLA代码实现中,在计算参数时使用的效用比基于自然对数的计算值大了1(归一化),若将此值定义为
u
m
u_m
um,则有
u
m
=
v
m
+
1
u_m=v_m+1
um=vm+1且
u
1
=
1
u_1=1
u1=1,因此:
V
p
(
u
1
−
1
+
γ
p
)
=
B
m
i
n
(1)
\tag{1} Vp(u_1 - 1 + \gamma p) = B_{min}
Vp(u1−1+γp)=Bmin(1)
同理,在最大buffer处选择最高码率时需要满足下式:
V
p
(
u
M
−
1
+
γ
p
)
=
B
m
a
x
(2)
\tag{2} Vp(u_M - 1 + \gamma p) = B_{max}
Vp(uM−1+γp)=Bmax(2)
考虑到
u
1
−
1
=
0
u_1-1=0
u1−1=0,基于式(1)有:
V
p
=
B
m
i
n
γ
p
Vp = \frac{B_{min}}{\gamma p}
Vp=γpBmin
将式(1)与式(2)相除,,可得:
γ
p
=
u
M
−
1
B
m
a
x
/
B
m
i
n
−
1
\gamma p=\frac{u_M - 1}{B_{max} / B_{min}-1}
γp=Bmax/Bmin−1uM−1
这就是代码中参数设置的来源。
【但是效用归一化后引入了一个新问题:BOLA的实际决策逻辑和论文是一致的,这样可能会导致参数设置和决策逻辑不同,即无法在给定buffer处选到特定的码率。如果需要的保持一致的话,决策部分的效用应该也需要减一。该问题确认为dash.js v4.x的一个bug,将在v5.0.0修复,详见:BOLA’s Utility Issue · Issue #4301 · Dash-Industry-Forum/dash.js】
相关参数:
- B m i n B_{min} Bmin:对应MINIMUM_BUFFER_S,默认为10s
-
B
m
a
x
B_{max}
Bmax:对应bufferTime,大概意思是基于MINIMUM_BUFFER_S为每档码率级别留出2s的阈值间隔
- bufferTime = Math.max(stableBufferTime, MINIMUM_BUFFER_S + MINIMUM_BUFFER_PER_BITRATE_LEVEL_S * bitrates.length);
- stableBufferTime:DEFAULT_MIN_BUFFER_TIME,12s(不开Fast Switch的话)
- 这个值允许重新配置,但似乎没有被调用过
- MINIMUM_BUFFER_PER_BITRATE_LEVEL_S:2s,应该是对应每档码率的buffer间隔