Matroska解封装原理与实践

Matroska解封装原理与实践

首页休闲益智代号轨驶神拆更新时间:2024-09-22

Matroska是一种开放标准、功能强大的多媒体封装格式,可容纳多种不同类型的视频、音频及字幕流,其常见的文件扩展名为.mkv、.mka等。与应用广泛的MP4相比,Matroska更加灵活开放,可以同时容纳多个字幕,甚至可以包含章节、标签等信息,成为了许多用户的偏爱。B站Web投稿页上传的所有视频中,封装格式为Matroska的视频占比超过2%,是除MP4以外占比最高的格式。

Web投稿页作为稿件生产的第一个环节,在获取到用户上传的视频后会根据视频内容为用户投稿提供辅助,其中包含:

而这些内容的实现,都基于对视频文件的解析,其中又包含了解封装和解码两个部分。

通用方案

此前,Web投稿页的实现方案为使用Emscripten将FFmpeg编译为WebAssembly后在Web平台运行,借助FFmpeg的音视频处理能力来进行解封装和解码。这种方案的优点是支持的视频格式齐全,可以解析几乎所有视频格式,但其缺点也很明确——解析速度慢,无法使用硬件加速,性能不佳,内存消耗大,甚至可能导致页面崩溃。

升级方案

针对MP4格式视频的解析,Web投稿页已经实现了方案升级,改为使用mp4box(https://www.npmjs.com/package/mp4box)进行解封装,然后结合WebCodecs API进行解码,方案升级后获取视频信息和视频画面效率提高了70%。Matroska格式是除MP4以外占比最高的格式,在Web投稿始终占据着一席之地,对其解析方案进行改造升级势在必行。使用WebCodecs API进行解码实现起来并不复杂,已有诸多成熟的实践,需要着重解决的问题就是如何对Matroska视频进行高效解封装。

技术调研

Matroska简介

Matroska是一种多媒体封装格式,是EBML在多媒体领域的应用。EBML是一种八位位组对齐的二进制格式,由一系列Elements(元素)组成,各个Element可顺序排列,可层层嵌套,能够灵活地表示和存储各种数据。而EBML中的所存储的具体数据内容,随着EBML文件类型的不同而不同,由文件类型所定义的EBML Schema来确定(详见附录1)。Matroska继承了EBML的层级化结构,在EBML基础上定义了一套独特的Schema模式,以实现其强大的功能和特性(详见附录2)。

从宏观结构上看,Matroska文件仅由两个主要的Element组成:EBML Header和 Segment,而Segment中又存储着8个可能的Element,称为Top Element。其中, SeekHead对Matroska解析非常重要,其内部包含了其他Top Element的位置索引,可用于方便快速搜索需要的信息:比如从Info、Tracks中获取视频的元信息,从Cues、Cluster获取具体的视频数据(详见附录3)。

从微观组成上看,无论是EBML Header、Segment还是SeekHead等等,Matroska中的每个Element都是标准的EBML Element,有着确定的解析方式。

由此,遵循Matroska特定的EBML Schema结构,按照EBML Element的组成和编码方式进行逐个解析,即可对整个Matroska文件的内容进行读取。

相关开源项目

在MDN文档的WebCodecs API部分可以看到,文档中提及了一些对视频进行解封装的开源项目,对于MP4格式的文件,可以使用mp4box来进行解封装,对于WebM文件,则可以使用jswebm(https://www.npmjs.com/package/jswebm)。

WebM是一种基于Matroska的多媒体封装格式,其规范与Matroska规范基本一致,只是对视频编码和音频编码作出了一些限制(见附录4)。jswebm在解析过程中对文件的音视频编码进行了判定,如果不符合则抛出错误。如果将这部分判定功能进行忽略,jswebm是可以同时解析Matroska、WebM文件的。

jswebm使用起来非常简单,相关代码如下:

const demuxer = new JsWebm(); demuxer.queueData(buffer); while (!demuxer.eof) { demuxer.demux(); } console.log(demuxer); console.log(`total video packets : ${demuxer.videoPackets.length}`); console.log(`total audio packets : ${demuxer.audioPackets.length}`);

而细读源码,可以发现其工作方式非常简单明了,就是顺序暴力读取:

1.文件读取层面:

2.内容解析层面:

初步使用下来,从功能上来讲,我们想要的信息jswebm最终的确都能获取到,但想要在生产环境进行使用,有几个关键的问题不可忽略:

因此,直接使用jswebm来对视频进行解析并不可行,无法满足Web投稿的实际需要。

方案实现

方案设计

Web投稿页对视频的解析需要在较短的时间内完成,这样才能确保计算结果能够及时曝光给用户,能够给用户投稿提供有效的帮助。这个过程中,内存的占用是非常重要的指标,如果内存占用过大,引发页面崩溃,则会打断投稿流程。实际生产环境使用时,有以下诉求:

因此,需要设计更加灵活、更加高效的工作流,打造更高性能、更实用的Matroska解封装SDK。

针对Web投稿页的实际诉求,主要进行以下设计:

1.数据读取层面:

2.内容解析层面:

根据Web投稿的实际应用需求,实现三个功能模块,分别进行文件预处理、视频基础信息获取、视频帧数据获取。各个模块之间存在一定的依赖关系,会对前置模块的运行结果进行校验。例如,获取视频基础信息时会使用到文件的索引信息,因此会预先检查文件是否完成了预处理工作。

每个功能模块内部又包含着一些子功能,来对视频文件进行不同的处理和分析:

1、文件预处理

2、视频基础信息获取

3、视频帧数据获取

新的方案中,SDK的工作方式和代码逻辑改变很大,因此需要另起炉灶,创建新的SDK——mkv-demuxer(https://www.npmjs.com/package/mkv-demuxer)。

mkv-demuxer重点解决了内存问题,优化了解析过程中的异常处理,并提供了几个有用的API,基本满足了Web投稿页的应用需求。

使用方式

mkv-demuxer的使用非常简单,首先创建一个解封装器实例,将文件进行传入,配置解析时的分片大小,然后根据实际需要调取相应的API即可。

import MkvDemuxer from 'mkv-demuxer' const demuxer = new MkvDemuxer() const filePieceSize = 1 * 1024 * 1024 await demuxer.initFile(file, filePieceSize) const meta = await demuxer.getMeta() const data = await demuxer.getData() const frame = await demuxer.seekFrame(10)

其中,getMeta可以获取视频文件基本的容器信息以及视频轨道、音频轨道的编码信息,返回的内容的格式为:

{ info: { duration: 10000, muxingApp: "Lavf58.47.100", ... }, video: { codecID: "V_VP9", width: 1280, height: 720, ... }, audio: { codecID: "A_OPUS", channels: 2, rate: 48000, ... }, }

getData可以获取到视频轨、音频轨的所有数据packet信息,包含每个packet在文件中的位置、所在时间戳。同时,cues中存储了视频所有关键帧的时间戳和相对位置信息,可与其他信息结合,得到关键帧在文件中具体位置。getData所返回内容的格式为:

{ cues: [ { cueTime: 50, cueTrackPositions: { cueClusterPosition: 660, cueRelativePosition: 2539, cueTrack: 1, }, }, ... ], videoPackets: [ { end: 3311, isKeyframe: true, keyframeTimestamp: 0.05, size: 51, start: 3260, timestamp: 0.05, }, ... ], audioPackets: [ { end: 1998, size: 1273, start: 725, timestamp: 0.001, }, ... ], };

seekFrame可以根据给定的时间戳返回最接近该时间戳的关键帧数据信息,所返回的内容为:

{ end: 3311, isKeyframe: true, keyframeTimestamp: 0.05, size: 51, start: 3260, timestamp: 0.05, };

性能表现

对于改造前后两个SDK的性能表现,可以抽取不同的视频进行测试,验证改造效果。经过多次测试和对比,发现改造后内存占用大大减小,不再受到视频本身体积的影响,能够平稳保持在较低水平;解析视频速度大大提高,基本都能在一秒内完成。尤其对于高分辨率、大体积的视频文件,使用两个SDK进行解析时性能表现差异极大,优化效果最为明显。

以一个4K视频为例,其基本信息如下:

属性名

视频大小

1.61G

视频编码

VP9

分辨率

3840x2160

码率

10023 kb/s

对改造前后的内存占用变化及解析视频时间进行记录,可以得到如下结果:

改造后内存占用减少了98.34%,解析视频时间减少97.21%。改造后的mkv-demuxer性能表现良好,可以在生产环境进行使用。

Web投稿实际应用

Web投稿页在获取到用户上传的视频后会分别获取视频元信息和视频抽帧画面,用以计算推荐封面和推荐分区,推荐结果获取的速度对用户体验有着直接的影响。

将Matroska视频的截帧方案进行升级,使用mkv-demuxer完成解封装,调用WebCodecs API对视频帧数据进行高性能解码,再结合WebGL以更快的速度绘制截帧画面,当用户投递Matroska视频时,获取到推荐封面和推荐分区的速度会变快。当视频无法解析或浏览器不支持WebCodecs API时,则会自动降级为旧方案,因此成功率不会受到影响。

数据结果

将升级方案与通用的FFmpeg WebAssembly方案进行对比,可以得到如下结果:

具体数据如下:

推荐封面获取总耗时:

推荐分区获取总耗时:

由于推荐封面总流程和推荐分区获取总流程中除了画面截帧还包含了许多其他步骤,且受限于WebCodecs API的兼容性,实验组用户中很大比例的用户最终使用的还是旧版降级方案,因此与本地进行的截帧性能测试数据相比,耗时减少的效果得到了一定的稀释。但整体来说,新的方案对用户投稿体验的改善是比较可观的,且随着未来WebCodecs在Web平台的逐渐兼容,优化效果会越加明显。

后续规划

基于Web投稿页的实际运作需求以及mkv-demuxer现有的解析能力,可以规划如下优化策略:

一、针对Web投稿页上的Matroska视频,可以利用mkv-demuxer的解封装能力,优化视频边传边转流程。在这一过程中,mkv-demuxer能够更加快速地获取视频帧数据,并进行逐帧遍历,精确计算视频的比特率、大小等关键数据。将这些信息更早地同步至视频云,可以加速视频转码的整体流程,提升用户体验。

二、进一步提升mkv-demuxer的解析能力,完善对Matroska格式的全面支持。通过增加对Tag、Attachments等内容的解析能力,兼容对EBML Stream的解析,可以更全面地解析Matroska文件,提取更多有用的信息。

相关资料

EBML相关:

Matroska相关:https://www.matroska.org/index.html

WebM相关:

工具推荐:

项目地址及npm包:

附录

1. EBML的结构:

EBML文件仅由两个部分组成:EBML header和 EBML body。EBML必须以EBML header开头,用以声明如文件类型、编写/读取EBML所需版本等重要信息,提供对EBML body处理方式的声明。而EBML body中的内容,随着EBML文件类型的不同而不同,又包含很多其他的EBML Element。

2. Matroska基于EBML的限制:

Matroska本身是基于EBML的,只是在通用的EBML基础上制定了Matroska独特的EBML Schema,同时做出了一些规定和限制:

3. Matroska的Top Element:

Matroska不同的Top Element,存储了文件的不同内容:

Tags:包含了对文件的描述数据和额外信息,如歌手、流派、评论、导演等。

4. WebM与Matroska的区别:

WebM由Google推出,目标是构建一个开放的、免费的视频格式。WebM在互联网中使用非常方便,因为它通常压缩率高,体积较小,使得共享和传输媒体内容变得更加容易,也节省了存储空间。WebM是基于Matroska多媒体容器格式进行开发的,所不同的是,WebM对视频编码和音频编码作出了一定限制,并且定义了新的DocType的值:

作者:王妍君

来源-微信公众号:哔哩哔哩技术

出处:https://mp.weixin.qq.com/s/3rATwUHgV0-kM10M_dJGsA

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved