如何设计高效的视频数据库,Netflix的NMDB给出了答案。本文是系列文章的第二篇,感谢Hulu的小伙伴们的技术审校。
文 / Subbu Venkatrav, Arsen Kostenko, Shinjan Tiwary, Sreeram Chakrovorthy, Cyril Concolato, Rohit Puri and Yi Guo
译 / 元宝
技术审校 / Hulu的小伙伴们
原文 https://medium.com/netflix-techblog/netflix-mediadatabase-media-timeline-data-model-4e657e6ffe93?source=collection_home---4------0---------------------
在本系列的
前一篇文章中
,我们描述了Netflix的一些重要业务需求以及被称为“Netflix媒体数据库(NMDB)”的媒体数据系统的特点。好奇的读者可能已经注意到,这些特性中的大部分与NMDB管理的数据的属性有关。具体地说,结构化数据是围绕媒体时间轴的概念建模的,具有额外的空间属性。这篇博客文章详细介绍了NMDB使用的媒体时间线数据模型的结构,称为“媒体文档”。
媒体文档模型
媒体文档模型旨在成为一种灵活的框架,可用于表示各种媒体模态的静态和动态(随时间和空间变化)元数据。例如,我们希望能够表示(1)具有29.97 fps NTSC帧速率的视频文件的每一帧的颜色和亮度信息,(2)基于“媒体时间基线”单位来描述的时序文本文件中的字幕样式和布局信息,以及(3)由VFX艺术家生成的时变3D模型的空间属性,所有这些都要求时间和空间维度的完全精确性。
媒体文档模型包罗万象,它可以用来描述大量的文档类型,包括描述视频流编码分析结果和VMAF分数的文档、描述在多个时序文本流中同时发生的事件的信息的文档、以及描述形成电影剪辑的一系列DPX图像的结构化信息的文档。为了满足所有这些用例,媒体文档围绕以下详述的一些核心原则构建。
时间模型
我们使用媒体文档模型来描述媒体文件 中的时序元数据。因此,我们主要围绕时序事件的概念进行设计。时序事件可以描述本质上属于“周期性”以及“基于事件”的时间线。图1显示了连续视频帧的周期序列。在这种情况下,感兴趣的事件是在第三帧之后发生了镜头更改事件。
图1:跨越镜头变化的一系列视频帧
对应于图1的媒体文档实例片段可以如下。
{
…
“events”: [
{
“startTime”: T0,
“endTime”: T1,
“metadata”: {
“shotEnvironment”: “outdoors”
}
},
{
“startTime”: T1,
“endTime”: T2,
“metadata”: {
“shotEnvironment”: “indoors”
}
},
…
]
…
}
时序事件类似于TTML(定时文本标记语言)字幕事件,但主要区别在于,在媒体文档中,这些事件并不意味着向最终用户显示。更确切地说,这些事件是描述媒体文件中特定时间间隔内的元数据。在我们的模型中,我们选择将给定的媒体文档实例中的所有事件对应一个时间线,匹配媒体文件的时间线(我们想要指出,媒体文档时间模型相当于SMIL规范中与par相关元素的时间线)。这个选择背后的一个目标是促进时序查询,既可以从一个文档实例中查询(获取电影中从56秒到80秒之间发生的所有事件),也可以从跨文档实例中查询(电影中从132秒到149秒之间的所有语言中是否有活动的字幕信息?)。
图2:与字幕事件对应的媒体时间线
在我们的模型中,每个事件在时间线上占用一个时间间隔。我们不会对事件的相关性做出任何假设。例如,在ISO基本媒体文件格式(BMFF)文件中,样本可能不重叠并且在轨道内是连续的。但是,在媒体文档模型中,事件可能会重叠。时间线中也可能存在间隙,即没有事件的间隔。图2显示了基于事件的字幕时间线,其中一些间隔没有事件。对应于图2的媒体文档实例片段可以如下。
{
…
“events”: [
{
“startTime”: T0,
“endTime”: T1,
“metadata”: {
“subtitle”: “Hi there! How are you?”
}
},
{
“startTime”: T2,
“endTime”: T3,
“metadata”: {
“subtitle”: “Thanksforasking — i am good. How are you?”
}
},
{
“startTime”: T4,
“endTime”: T5,
“metadata”: {
“subtitle”: “Very well — thanks a lot!”
}
}
]
…
}
空间模型
与时序模型一样,媒体文档与单个空间坐标空间相关联,并且事件可以通过空间属性进一步限定,提供事件在此坐标空间中发生的位置的详细信息。这使我们能够提供空间查询(“获取贯穿整个电影的媒体文件的这个区域中出现的所有事件”)或时空查询(“获取给定区域中在给定时间间隔内发生的所有事件“)。
图3:一系列视频帧,其中感兴趣的空间区域随时间变化
图3显示了由两个时间事件组成的视频时间轴的可视化,这两个时间事件由镜头变化分开。在每个时间事件内,不同的空间区域(对应于人脸并用彩色矩形示出)形成感兴趣的区域。在本节的末尾描述了与此媒体时间线对应的完整媒体文档实例。
嵌套结构
受行业领先的媒体容器格式(例如SMPTE可互操作主格式(IMF)或ISO BMFF)的启发,媒体文档模型将具有类似属性的事件分组。可以使用两种嵌套级别的分组:轨道和组件。我们的模型是灵活的:在时间线上同属于某个公共间隔的两个事件可以放置在同一轨道的同一组件中,也可以放置在同一轨道的两个不同组件中,还可以放置在不同轨道的各自组件中。
组件和轨道的语义可以由媒体文档实例的作者自由定义。对于一个典型的多媒体文件的实例,媒体文档实例会对媒体文件中的每个媒体模态都会创建一个轨道元素,比如说,对于一个同时包含了音频和视频的文件,媒体文档实例就会创建两个轨道来描述。在图4中展示了如何描述一个包含了音频、视频和文本模态的文件。
图4:包括多个轨道的媒体时间线
如上所述,对应于图4的媒体文档实例片段可以如下。
{
...
"tracks": [
{
"id":"1",
"metadata": {"type":"video"},
...
},
{
"id":"2",
"metadata": {"type":"audio"},
...
},
{
"id":"3",
"metadata": {"type":"text"},
...
}
]
}
或者,对于多声道音频文件,媒体文档实例可以描述成一个轨道,但在轨道内,单独的组件元素将为每个通道提供元数据和事件来描述它,如图5所示。
图5:显示属于单个轨道的多个组件的媒体时间轴
对应于图5的媒体文档片段可以如下。
{
...
"tracks": [
{
"id":"1",
"metadata": {"type":"stereo audio"},
"components": [
{
"id":"0",
"metadata": {"channel":"left"},
...
},
{
"id":"1",
"metadata": {"channel":"right"},
...
},
]
}
]
}
媒体文档的整体嵌套结构如图6所示。每个级别都要求作者指定所有媒体文档实例的共同(必需)信息(每个级别的id、组件级别的时间和空间解析单元、事件级别的时间间隔信息、区域级别的空间信息)。此外,每个级别允许作者提供特定于每个级别的每个媒体文档类型的元数据(例如,事件级别的每个帧的VMAF分数或文档级别的平均值,或者组件或轨道级别的音频的响度信息)。
图6:媒体文档的数据结构层次结构
虽然媒体文档实例可以用任何流行的序列化格式表示,例如JSON,Google Protocol Buffers或XML,但我们使用JSON作为首选格式。这在一定程度上源于不同web系统之间通常使用JSON作为有效负载格式。更重要的是,许多流行的分布式文档索引数据库,如Elasticsearch和MongoDB使用JSON文档。选择JSON作为我们的序列化格式,可以使用任何这些可伸缩文档数据库来索引媒体文档实例。值得一提的是,对事件级时间间隔信息以及区域级空间信息的索引提供了开箱即用的时空查询能力。
以下示例显示了一个完整的媒体文档实例,该实例通过图3所示的视频序列的时间轴表示人脸检测元数据。所讨论的视频序列是高清视频序列(1920x1080空间分辨率),帧率为23.976帧每秒。它包括两个不同的时间事件。每一个事件都包含单个感兴趣的空间区域,对应于检测到的人脸矩形边界框。
{
"metadata":{
"algorithm":"video_face_detection"
},
"tracks": [
{
"id":,
"components": [
{
"id":,
"eventRateNumerator":24000,
"eventRateDenominator":1001,
"xSize":1920,
"ySize":1080,
"events": [
{
"startTime":,
"endTime":2,
"regions": [
{
"xmin":1152,
"xmax":1536,
"ymin":108,
"ymax":648
}
]
},
{
"startTime":3,
"endTime":4,
"regions": [
{
"xmin":576,
"xmax":960,
"ymin":108,
"ymax":648
}
]
}
]
}
]
}
]
}
媒体文档架构
前面的段落介绍了媒体文档模型的基本原理。媒体文档对象广泛用于各种Netflix媒体处理工作流程中。以下是一个典型的生命周期:
运行在如Archer的平台上的媒体处理算法产生出特定类型的媒体文档实例,其中元数据部分包含特定域的元数据(例如,视频帧中文本的边界框);
媒体文档实例被摄取,持久化并索引到NMDB中;
NMDB用户查询具有类似特征的一组特定媒体文档实例。通常,这些是具有额外特定域特征的时空查询(例如“在屏幕中间查找所有出现的文本”)
特定域的API用于向下游用户公开特定的媒体文档实例。
if(property1 instanceOf String) {
…
}elseif(property1 instanceOf Integer) {
…
}elseif(property1 instanceOf Boolean) {
…
if(property2 instanceOf Double) {
…
}elseif(property2 instanceOf List) {
…
}
…
}
为了在Netflix规模上维持这个生命周期,我们意识到有必要采用一种“写入模式”的方法。在此方法中,每个媒体文档类型都会与对应模式相关联。提交给NMDB的特定类型的所有媒体文档实例都会被为该类型定义的模式进行验证。如果媒体文档实例不符合验证规则,则会被拒绝。更具体地说,我们决定使用JSON Schema语法的子集来表达我们的验证规则。因此,首先会要求媒体文档实例的生产者提供描述相关媒体文档类型结构的JSON Schema。这种方法带来了几个好处:
我们可以确保与域关联的所有媒体文档实例的结构类似。这允许我们编写特定域的查询并获得一致的结果。例如,如果表示字幕内容的所有媒体文档实例遵循相同的结构(例如,TTML body元素包含一个div元素,这个div元素包含p元素,p元素潜在包含几个span元素),它可以使用一个请求查询所有使用ruby注释的TTML事件 ,这个查询可以运行在一个媒体文档实例里或对应域里的全部集合里。
我们可以确保对于相同的媒体文档类型,文档树中给定位置的给定名称的属性是精确类型而不是通用字符串。例如,这使得能够将本质上为数字的属性的类型强制为数字类型。然后,可以对该属性进行范围查询(具体来说,我们已经仔细选择了JSON模式的子集,以确保没有元素可以具有不明确的定义或允许不兼容的解释,即,每个对象都被指定为其原始类型,包括字符串,布尔值,数字和整数)。在没有模式的情况下,读取媒体文档实例可能会降级为类似下面的伪代码。从软件角度来看,这样的实现难以维护,并且导致较低的读取性能。
if(property1 instanceOf String) {
…
}elseif(property1 instanceOf Integer) {
…
}elseif(property1 instanceOf Boolean) {
…
if(property2 instanceOf Double) {
…
}elseif(property2 instanceOf List) {
…
}
…
}
我们可以自动提供强类型API,以支持使用特定类型的媒体文档实例。NMDB的用户不必编写代码来解析媒体文档实例,并且提供了强类型代码来处理它们并提出特定于域的API。
此外,由于开发人员需要在他们的媒体文档定义中保持灵活性,并且随着时间发展,常常需要逐步演化其特定域的元数据,或更广泛地演化特定域的媒体文档类型,因此我们允许更新媒体文档模式。但是,为了保留上述优点,我们对模式的更新进行了限制,只允许增加或更新可选字段。这可确保媒体文档实例与媒体文档读取器之间的前向和后向兼容性,同时保持媒体文档实例索引和查询的稳定性。简而言之,这种设计选择既让NMDB系统易于推广,也让我们在运营NMBD时保持了可扩展性。最后,当必要的更新无法和现有模式相兼容时,也可以创建新的媒体文档类型。
下一步计划
在下一篇博文中,我们将深入探讨NMDB系统的实现。我们将讨论我们的设计选择,以实现Netflix业务需求产生的服务可用性和服务规模要求。
精品文章推荐
领取专属 10元无门槛券
私享最新 技术干货