如何在dash.js中添加自定义ABR规则?


参考资料:


引言

dash.js作为DASH协议下的标准播放器实现,出于ABR的研究需求,往往需要在其中实现自己的ABR算法。

主要步骤如下:

  1. 实现自定义ABR
  2. main.js中添加自定义ABR
  3. 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"前面的框点掉,因为这也是个坑- -)。
Show Options
至于如何使用dash.js的播放器网页搭建自己的视频播放系统,可以参见DSAH视频系统(服务器&播放器)搭建


  1. src/streaming/rules/abr/ABRRulesCollection.js的 initialize()中。 ↩︎

  2. src/streaming/rules/abr/ABRRulesCollection.js的 getMaxQuality()调用。 ↩︎

  3. 该定义见Exploring the interplay between CDN caching and video streaming performance
    ↩︎

猜你喜欢

转载自blog.csdn.net/LvGreat/article/details/114790362