来源:Global Video Tech Meetup:Berlin 主讲人:Daniel Silhavy 内容整理:尹文沛 在这篇文章中,主讲人以讲故事的方式讲述了 dash.js 从诞生到现在所走过的发展历程。
目录
- 引言
- 第一阶段 一个英雄的诞生
- 第二阶段 熟悉环境
- 媒体源扩展(MSE)
- 加密媒体扩展(EME)
- 第三阶段 好的(坏的)旧时光
- 第四阶段 我们年轻,我们很快
- 第五阶段 年轻奔放的日子结束:优化我们的环境
- 第六阶段 打补丁
- 致谢
引言
如果想要阐述一些科学知识,把这件事情当作是讲故事不失为一个好方法,所以我选择在这次的演讲中做同样的事情,将这次的演讲题目取名为“一个流媒体发展的故事”。
当我们谈论 dash 时总是绕不开 MPD,通过 MPD 的 periods 字段,我们可以获取任何关于视频的信息,所以今天我的演讲也是以 period 字段进行划分的。
- 第一阶段我们需要定义故事的主角,同时定义背景,就像每本书刚开头做的那样;
- 第二阶段我们得去熟悉环境;
- 第三阶段则是去回顾那些值得回忆的旧时光,就像平常和爷爷奶奶聊天一样;
- 第四阶段,我们仍然还很年轻,需要去探索新事物;
- 但是随着时间流逝,我们老了,所以第五阶段,年轻奔放的日子结束了,我们想要去改善优化我们的生活条件和环境;
- 第六阶段,我们已经真的很老了,需要休养生息,养好身体;
- 第七阶段,也像每本书的结尾一样,我们需要去致谢一些特定的人和机构。
图 1 概览
第一阶段 一个英雄的诞生
在这里,英雄就是 Big Buck Bunny (dash.js),可以把 DASH-IF 的相关实现看作是英雄的父亲。dash.js 完全是由 javascript 语言编写的,这样英雄就有了一个稳定的成长环境。因此,我们利用媒体源扩展(Media Source Extensions, MSE)和加密媒体扩展(Encrypted Media Extensions, EME)来播放内容。同样,这样做也使得 dash.js 非常灵活,支持全 ABR 播放,而且无论在什么平台上都能工作,比如说 chrome,win 8.1 下的 IE 浏览器,win10 系统的 edge, 苹果系统下的 Safari,以及火狐浏览器等。最重要的是 dash.js 是一个开源项目,所有人都可以免费地使用以及改进 dash.js。
图 2 一个英雄的诞生
第二阶段 熟悉环境
媒体源扩展(MSE)
接下来讨论 MSE。首先需要注意环境(MSE)中的漏洞或某些威胁。所以当谈论 MSE 时,一个关于 MSE 播放器的关键事实是,只要源缓冲区之一出现间隙,播放就会停止,每个人可能都遇到过这样的情况。这些间隙到底是什么呢?如下图所示:
图 3 MSE 间隙
理想情况下,在一个视频流中,视频块之间应该没有间隙,就像 Segment 1 和 Segment 2 一样,它们中间没有间隙,但是根据我们的经验,会有很多内容的细节导致视频块之间产生微小的间隙。这样 Segment 2 和 Segment 3 之间就存在间隙,这会导致播放立即中断,因为无法处理这些间隙。所以我们需要一个播放器实现来人为地跳过这些间隙,这也是之后给 dash.js 添加的一个关键特征。
什么会导致间隙的产生呢?
- 没有对齐的视频块时间戳;
- 采样持续时间和视频块持续时间不匹配,这会导致视频块的重叠以及音频和视频的不匹配;
- 没有对齐的 DASH periods,所以无论何时做多 period 的 dash 时,你都要保证你的 period 是完全对齐的。
保证 dash period 对齐,并且如果有一个视频块与 dash periods 不匹配则不能切换视频块,这样就完全克服了所有的 MSE 间隙问题。
加密媒体扩展(EME)
EME 是第二重要的 API,如果想要播放器在浏览器上能使用的话,EME必不可少。因为一些传统设备也支持 MSE,所以这些设备也需要 EME,但是这些设备可能需要已经过时了的 EME 版本。
为了同时支持传统设备和新设备,dash.js 支持三种不同的 EME 版本,他们分别是:
- ProtectionModel_01b.js:EME 的初始实现,由 Google Chrome 在版本 36 之前实现。此 EME 版本不是基于承诺的,并使用过时或带前缀的事件,如“needkey”或“webkitneedkey”。
- ProtectionModel_3Feb2014.js:截至 2014 年 2 月 3 日规范的 EME API 的实现。由 Internet Explorer 11 实现(windows 8.1)。
- ProtectionModel_21Jan2015.js:最新的 EME 实现。此模型中添加了 EME 规范的最新更改,并支持基于承诺的 EME 函数调用。
第三阶段 好的(坏的)旧时光
当谈论 dash 的旧时光时,我们不可避免地谈论到 DVR 窗口。DVR 窗口与动态传输流密切相关,遭遇的主要问题是:可用媒体片段的呈现时间在时移缓冲区之外。
我们所说的开始是什么意思?一般的 dash 客户端将始终使用由于信号时间造成的 noatime 时间作为锚定时间并从那里开始,然后开始定义缓冲区时间,开始传输流的时间 ... 然后你可以找到一个有效的 DVR 窗口。用户或应用程序本身只允许在有效的 DVR 窗口内寻找和移动。所以这非常简单,我们确切地知道 DVR 窗口有多大以及可以操作的时间范围。不幸的是,在某些情况下会遇到问题,即在有效 DVR 窗口中不包含任何片段,因此在检查 MPD 并专门检查第二个时间轴属性时,可能在 DVR 窗口中没有找到任何片段,那现在怎么办?根据 MPD 可知,段不可用,因此不会开始播放。
这就是我们在 dash.js 中实现回退机制的原因。所以当遇到上述问题时,在有效的 DVR 窗口内找不到任何可以下载的视频块,可以使用视频块时间线中最后一个视频块的时间作为时间锚点。所以我们将时间对应到最后一个视频块,然后做前面提到的标准计算,由此定义了缓冲区的时移、直播延时以及有效的 DVR 窗口。
图 4 DVR 窗口
播放器使用 MPD@timeShiftBufferDepth 属性来确定 DVR 窗口。此外,从“UTC now”时间中减去实时延迟以缩短 DVR 窗口,从而产生“有效 DVR 窗口”。在播放期间,允许底层应用程序在有效的 DVR 窗口内搜索。
在图 4 中,最后一个可用段(段 5)的演示结束时间在 DVR 窗口之外。在这种情况下,播放器没有要下载和播放的片段。dash.js 能够通过调整锚定时间来处理这种情况。“now UTC”时间被最后一段的“presentation time”代替。这会导致计算“有效 DVR 窗口”的调整值。通过以下方式调整设置参数启用此功能:
代码语言:javascript复制player.updateSettings({
streaming:
{
calcSegmentAvailabilityRangeFromTimeline: true
}
})
MPEG-DASH 中的时序模型并不是很容易理解。错误的 DVR 窗口会导致播放停止和失败。重要的是使用 MPD 特定属性对齐所有时段以避免媒体缓冲区中的不一致。此外,应避免媒体时间线中的间隙,因为 MSE 实现无法处理媒体缓冲区中的间隙。
第四阶段 我们年轻,我们很快
既然年轻,就要多经历一些事情,我们接下来讲到 dash.js 的低延时传输——CMAF 低延时传输。
通用媒体应用格式(Common Media Application)引入了“块”(chunk)的概念。一个 CMAF 块有多个“moof”和“mdat”框,允许客户端在段完全完成之前访问媒体数据。在查看具体示例时,分块模式的好处变得更加明显:
图 6 CMAF 低延时传输示例
所以假设有 8 秒的片段,目前进入第 4 片段的时间为 3 秒。对于经典媒体片段,这里有两个选择:
- 由于第四段没有完成,从第三段开始。这样,最终比实时边缘落后 11 秒——来自第三段的 8 秒,来自第四段的 3 秒。
- 等待第 4 段结束并立即开始下载和播放。最终得到 8 秒的延迟和 5 秒的等待时间。
另一方面,使用 CMAF 块,我们可以在第 4 段完全可用之前播放它。在上面的示例中,有持续时间为 1 秒的 CMAF 块,这导致每个段有 8 个块。假设只有第一个块包含 IDR 帧,因此总是需要从片段的开头开始播放。进入第 4 段 3 秒让我们有 3 秒的延迟。这比经典分块方案要好得多。我们还可以快速解码第一个块,并在更接近实时边缘的地方播放。
dash.js 从 2.6.8 版本开始支持 CMAF 低延迟流媒体。https://github.com/Dash-Industry-Forum/dash.js/wiki/Low-Latency-streaming 提供了多个示例。
可以通过以下 API 调用打开低延时功能:
代码语言:javascript复制player.updateSettings({
streaming: {
lowLatencyEnabled: true,
delay: {
liveDelay: 4
},
liveCatchup: {
minDrift: 0.02,
maxDrift: 0,
playbackRate: 0.5,
latencyThreshold: 60
}
}
});
第五阶段 年轻奔放的日子结束:优化我们的环境
当我们变老了,想要翻修自己的家,优化居住环境。在这一节我们介绍公共媒体客户端数据 (Common Media Client Date, CMCD),CMCD 与每个媒体对象请求通信数据,并在每个 CDN 上一致地接收和处理数据。
让我们用一个例子来看看为什么需要使用 CMCD:假设您每天有 100 万订阅者登录您的服务并观看各种电影、短片内容、预告片和歌曲。您的受众分布在不同的地理区域,并由多个不同的 PoP 提供服务。他们使用各种设备观看来自多种不同连接类型(宽带、3G/4G/5G)的内容;有些接收 MPEG-DASH 内容,有些接收 HLS 等。
当运行如此多样化的操作时,从交付管道的不同阶段收集尽可能多的信息非常重要,然后以某种方式将这些不相交的信息组合在一起并使其有意义。
假设一位客户抱怨他在美国东部时间晚上 11 点在纽约观看视频经历了很多重缓冲,
- 可以从 CDN 日志中获得什么?
- 是否能够将日志过滤到特定订阅者?
- 如何将范围缩小到特定会话?
- 特定的电影?
可以使用 Conviva、NPAW、MediaMelon、Mux 等 QoE/QoS 分析服务从媒体客户端收集非常详细的统计数据。这些产品可以非常准确地反应谁观看了什么内容、从哪里观看以及如何观看。当使用商业分析解决方案时,通常可以获得从媒体客户端看到的每个订阅者和每个会话级别的信息。
大多数商业分析解决方案每 N 秒将数据从播放器发送回他们的服务器,其中包含大量分段级别的信息(测量的带宽、请求的分段的数据速率、缓冲、播放事件等等)。从本质上讲,我们可以获得整个流媒体会话的信息——从头到尾。
同样,也可以从 CDN 提供商处获取日志,这些日志会描绘出不同的画面——如从 CDN 中看到的那样。
现在,困难在于将客户端数据和 CDN 数据结合在一起并从中理解。问题是有没有办法做到:
- 筛选 CDN 日志并查看特定订阅者观看某部电影的所有日志?
- 单独“加入/合并”每个视频流会话的 CDN 和客户端日志?
这是一个具有挑战性的问题,因为
- 传输管道两端的数据量都是巨大的。
- 需要存在一个协议,以便信息定义、交换和日志记录可以标准化。
但是,如果存在这样的标准,那么您手上就有更多的信息——捕获媒体对象从 CDN 到播放器再到屏幕的过程的信息,就可以:
- 获取数千行服务器日志并将它们标记为相同的会话和订阅者;
- 确定导致缓冲问题的 CDN(或 PoP);
- 根据 Live 和 VOD 对数据进行分段,然后监控 CDN 的性能。
为了解决加入/关联客户端数据与 CDN 日志的问题,CTA-WAVE 开发了 CMCD 规范。
它清楚地表明“每个”媒体播放器都可以与“每个” CDN 通信,并“一致地”接收和处理数据。这很重要,因为我们不希望针对不同的播放器和 CDN 组合有多种不同的规范。拥有统一且有用的规范(例如 CMCD 规范)可以使管道两端的工程工作更轻松!
- CMCD 使用“sid”将 QoE 日志与 CDN 日志联系起来,并围绕会话来衡量 CDN 数据;
- CMCD 计算每个会话服务器的平均字节数,从而计算交付每个 asset 的成本;
- 指示 CDN 边缘预取下一个媒体片段;
- 当缓冲区为空时优先发送段(例如:在启动时,在频道更改、搜索或快速音频切换时),或者当缓冲区已经很短的情况下优先发送段(比如说在低延时直播的场景下)。
要深入了解 CMCD,可以阅读规范。它用简单易懂的语言编写,只有十几页左右。(https://docs.google.com/document/d/1S_Dj_aHVnbWnjeJYMfLU1D6tZoqYdgHT1stfznBvxig/edit#heading=h.kimtynlg2eqk)
第六阶段 打补丁
我们已经很老了,所以是时候关心自己的身体了,怎样照顾我们自己呢——给 MPD 修补不足之处。
MPD 也是慢慢发展起来的,接下来简要介绍 MPD 的更新历程:
- 因为直播的原因,所以 Periods 和 segments 被加入了 MPD。
- 解析 MPG 实际上可能需要大量时间,尤其是在低端设备上,如图 7 所示。
- 低的 @minimumUpdatePeriod 值会导致客户端高负载
- 通常两个连续的 MPD 之间只有很小的差异
图 7 不同设备上解析 MPD 的时间
给 dash.js 客户端的 MPD 打补丁是 MPEG dash 版本 5 的机制,它允许你直接指定与上次的 MPG 相比哪些属性可以发生改变。因此,在这种情况下,我们使用的语法允许替换当前 MPD 中的某些元素,添加新元素并删除旧元素。所以你可以直接通知客户端,例如删除一个时间线元素或向段添加一个元素,不需要其他任何额外的信息。
这是更新 MPG 的好方法,因此你就可以仅获得你所需的内容,而不会增加任何开销。同时这样也节省了发送整个 MPD 的开销,因为只需指出改变与最近一次 MPD 更新有哪些不同即可。
致谢
现在这个故事已经成为了一本好书,最后致谢,感谢每一个为 dash.js 客户端做出贡献的人,因为他们慷慨的分享使得 dash.js 成为了一个真正有用的社区项目。