文章目录
参考资料:
- Dash-Industry-Forum/dash.js: A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
- ABR Logic · Dash-Industry-Forum/dash.js Wiki
- Dash JavaScript Player v3.0.1
- dash.js的ABR逻辑
经典ABR算法介绍系列博文:
- DASH标准&ABR算法介绍
- 经典ABR算法介绍:FESTIVE (CoNEXT '12) 论文阅读笔记
- 经典ABR算法介绍:BBA (SIGCOMM ’14) 设计与代码实现
- 经典ABR算法介绍:BOLA (INFOCOM '16) 核心算法逻辑
- 经典ABR算法介绍:BOLA (INFOCOM '16) dash.js代码实现
- 经典ABR算法介绍:Pensieve (SIGCOMM ‘17) 原理及训练指南
引言
dash.js作为DASH协议下的标准播放器实现,出于ABR的研究需求,往往需要在其中实现自己的ABR算法。
主要步骤如下:
- 实现自定义ABR
- 在
main.js
中添加自定义ABR - 在
index.html
中引用ABR脚本
(*注:本文基于dash.js v3.0.1)
1 实现自定义ABR
假设我们要实现的ABR规则叫CustomRule,那么我们要解决如下几个问题:
- 在哪里实现CustomRule?
- 如何实现CustomRule?
- 有哪些可用的输入?
接下来,我们来一一解决这些问题。
1.1 代码位置&示例
在dash.js的参考播放器(dash-if-reference-player)实现代码中,自定义ABR算法(JavaScript)应被放置在:dash-if-reference-player/app/rules/
。该路径下已有两个ABR示例:
- ThroughputRule.js:什么都没做,仅仅输出了官方接口提供的metrics;
- DownloadRatioRule.js:计算之前视频块的下载速率,取3个块的平均值预测未来可用带宽,以此选择下一个视频块的码率。这个代码很有用,后文详述。
另外需要注意的是,dash.js有内置的ABR规则,路径为:src/streaming/rules/abr/
,包括BOLA-E等ABR,详见:dash.js的ABR逻辑。这些代码属于dash.js内部代码,若是对JS不够了解,自定义ABR不建议直接按照内部ABR实现,因为里面的一些变量是无法在dash.js之外直接获取的。
1.2 基本代码框架
其实通过观察ThroughputRule.js,我们就能看出一个自定义ABR的基本代码框架,如下所示:
var CustomRule;
function CustomRuleClass() {
let factory = dashjs.FactoryMaker;
let SwitchRequest = factory.getClassFactoryByName('SwitchRequest');
let MetricsModel = factory.getSingletonFactoryByName('MetricsModel');
let Debug = factory.getSingletonFactoryByName('Debug');
let context = this.context;
let instance,
logger;
function setup() {
logger = Debug(context).getInstance().getLogger(instance);
}
function getMaxIndex(rulesContext) {
// here you can get some informations aboit metrics for example, to implement the rule
return SwitchRequest(context).create();
}
instance = {
getMaxIndex: getMaxIndex
};
setup();
return instance;
}
CustomRuleClass.__dashjs_factory_name = 'CustomRule';
CustomRule = dashjs.FactoryMaker.getClassFactory(CustomRuleClass);
需要注意的是:
- 代码的最前和最后:声明自定义ABR的名字;
- 代码中部:定义自定义ABR的类,类名和名字保持一致。
而ABR的核心代码,就在类的定义代码中实现。其中主要有两个函数:
- setup():初始化代码。当自定义ABR类的对象被创建时调用1,可以在其中执行一些需要提前执行的代码;
- getMaxIndex():ABR决策逻辑的核心代码2。将dash.js中可获取的metric作为输入,返回SwitchRequest对象,其中的quality即为目标码率级别。
可以看出,我们需要重点关注的就是getMaxIndex()
这个函数。具体而言,我们需要创建SwitchRequest对象,修改其中的quality,即可实现ABR的功能。示意代码如下(不能直接运行):
function getMaxIndex(rulesContext) {
const switchRequest = SwitchRequest(context).create();
switchRequest.quality = targetQuality;
switchRequest.reason = {
throughput: throughput
};
switchRequest.priority = SwitchRequest.PRIORITY.STRONG;
return switchRequest;
}
但是这个函数没有任何输入参数,那么ABR决策所需要的各种metric从何而来呢?
1.3 各种metric的获取
在dash.js中,ABR在请求下一个视频块之前被调用,常用的输入有:
- Buffer水平;
- 视频块大小、视频块传输时间(=请求时间+下载时间3)、视频块吞吐量;
- 视频块码率级别、视频块时长等。
接下来我们来看看这些metric如何在getMaxIndex()
中获取。这里主要用到了DashMetrics和rulesContext这俩东西。
1.3.1 准备
DashMetrics是dash.js官方提供的获取metric的类,其中包含了很多接口。首先,我们要先获取DashMetrics对象:
function getMaxIndex(rulesContext) {
let mediaType = rulesContext.getMediaInfo().type;
let dashMetrics = DashMetrics(context).getInstance();
...
}
*注:ABR算法不止是对视频进行决策,同样也对音频等其他媒体内容有效,因此可以使用mediaType来判断媒体类型。下文默认媒体内容只包含视频。
1.3.2 Buffer水平
获取Buffer水平直接调用API即可,只需要一行代码:
let bufferLevel = dashMetrics.getCurrentBufferLevel(mediaType, true);
1.3.3 视频块大小&传输时间&吞吐量
对于视频块大小、视频块传输时间、视频块吞吐量这些metric,没有现成的API可以调用。dash.js给出的方式是,提供上一个HTTP请求的相关参数,ABR使用这些参数来计算metric。
首先,获取上一个有效的请求,上一个视频块的请求保存至lastRequest(这里直接照抄DownloadRatioRule.js里的代码,这里面有很多用于判断的语句):
let requests = dashMetrics.getHttpRequests(mediaType);
let lastRequest = null;
let currentRequest = null;
if (!requests) {
return SwitchRequest(context).create();
}
// Get last valid request
i = requests.length - 1;
while (i >= 0 && lastRequest === null) {
currentRequest = requests[i];
if (currentRequest._tfinish && currentRequest.trequest && currentRequest.tresponse && currentRequest.trace && currentRequest.trace.length > 0) {
lastRequest = requests[i];
}
i--;
}
if (lastRequest === null) {
return SwitchRequest(context).create();
}
if(lastRequest.type !== 'MediaSegment' ) {
return SwitchRequest(context).create();
}
获取到的请求中保留了多个trace,可以从中计算出上个视频块的大小(单位为Byte,若需bit,还需要乘以8):
function getBytesLength(request) {
return request.trace.reduce((a, b) => a + b.b[0], 0);
}
...
let chunkSzie = getBytesLength(lastRequest);
获取到的请求里包含三个时间戳:
- trequest:客户端发送HTTP请求的时间点;
- tresponse:客户端接收到HTTP响应的第一个字节的时间点;
- _tfinish:客户端接收完HTTP响应的最后一个字节的时间点,即请求完成时间。
根据这三个时间点,我们可以计算出:
- 请求时间:tresponse - trequest;
- 下载时间:_tfinish - tresponse;
- 传输时间:请求时间+下载时间,即_tfinish - trequest。
在此我们只关心视频块的传输时间(单位为s),其计算的代码为:
let transmissionTime = (lastRequest._tfinish.getTime() - lastRequest.trequest.getTime()) / 1000;
有了传输数据量(视频块大小)和传输时间,视频块吞吐量(单位为bps)则由视频块大小/视频块传输时间计算得到:
let throughput = chunkSzie * 8 / transmissionTime;
1.3.4 视频块码率级别&时长
除了DashMetrics之外,getMaxIndex()
的输入参数rulesContext也是个很有用的东西。这些官方的范例好像没有给出(也可能是我没看到),是我自己摸索出来的。
比如,如果我们要获取上一个视频块的码率级别的话,可以这么做:
let lastQuality = rulesContext.getRepresentationInfo().quality;
多说一点,有人可能会有疑惑:上一个视频块的码率不是ABR选的吗?我自己的选的码率我当然知道,为啥还要从系统中获取呢?
这个涉及到dash.js里面的一些坑:) 简单来说就是,你以为ABR选择的码率和实际请求的码率在某些情况下是不一致的,等我日后有空展开讲讲。
此外,我们还可以获取到上一个视频块的时长:
let chunkDuration = rulesContext.getRepresentationInfo().fragmentDuration;
对于自己编码完成的视频,视频块时长一般是固定的。这个代码的作用在于,当实现MPC、Pensieve类算法的时候,可以免于手动设置块时长。
最后总结一下,其实想要实现自定义ABR很简单,从抄DownloadRatioRule.js开始就可以了:)
2 在main.js中添加自定义ABR
在写完了自定义ABR的代码之后,你需要做的事情是让系统知道你有一个自定义ABR算法。这里需要做两件事,一是修改main.js,二是修改index.html。
main.js是播放器的核心逻辑代码,其路径为dash-if-reference-player/app/main.js
。简单来说,你看到的HTML网页(index.html)只是一层皮肤,而你在网页上的所有操作,都需要一个大脑来处理,这个大脑也就是main.js。
具体需要修改的地方是Line 427左右的toggleUseCustomABRRules
中,在下面的if分支里,分别在addABRCustomRule()
和removeABRCustomRule()
中注册自己的ABR规则(注意名字不能错):
$scope.toggleUseCustomABRRules = function () {
$scope.player.updateSettings({
'streaming': {
'abr': {
'useDefaultABRRules': !$scope.customABRRulesSelected
}
}
});
if ($scope.customABRRulesSelected) {
// $scope.player.addABRCustomRule('qualitySwitchRules', 'DownloadRatioRule', DownloadRatioRule); /* jshint ignore:line */
// $scope.player.addABRCustomRule('qualitySwitchRules', 'ThroughputRule', CustomThroughputRule); /* jshint ignore:line */
$scope.player.addABRCustomRule('qualitySwitchRules', 'CustomRule', CustomRule); /* jshint ignore:line */
} else {
// $scope.player.removeABRCustomRule('DownloadRatioRule');
// $scope.player.removeABRCustomRule('ThroughputRule');
$scope.player.removeABRCustomRule('CustomRule');
}
};
*注:addABRCustomRule()
的第一个参数有两个取值:“qualitySwitchRules"和"abandonFragmentRules”,我们需要选第一个参数。
3 在index.html中引用ABR脚本
写好的ABR规则,也需要在HTML中引用,index.html的路径为dash-if-reference-player/index.html
。
需要改动的地方只有一行,大概在Line 34,加一句就好了:
<script src="app/rules/CustomRule.js"></script>
至此,在dash.js中添加自定义ABR的工作就全部完成了。之后只需打开网页播放器,在其中的"Show Options"中,把"Use Custom ABR Rules"前面那个框点上,就能运行自己的ABR规则了(注:如果没什么特殊的需求,建议把"Fast Switching ABR"前面的框点掉,因为这也是个坑- -)。
至于如何使用dash.js的播放器网页搭建自己的视频播放系统,可以参见DSAH视频系统(服务器&播放器)搭建
在
src/streaming/rules/abr/ABRRulesCollection.js
的 initialize()中。 ↩︎由
src/streaming/rules/abr/ABRRulesCollection.js
的 getMaxQuality()调用。 ↩︎该定义见Exploring the interplay between CDN caching and video streaming performance
↩︎