在媒体内容传播行业中,视频作为信息传播的载体,其重要性占比越来越高。通常,为了应对不同的播放场景,视频需要修改封装容器格式、编码格式以及分辨率、码率等媒体流属性,这些处理的过程我们统称其为视频转码。
网易云信集网易 21 年 IM 以及音视频技术,对外提供行业一流的融合通信云服务。其中,云信的点播服务,基于分布式处理集群和大规模分发系统资源,可满足全终端设备的播放需求,为企业用户提供极速稳定的视频上传、存储、转码、播放和下载等云服务功能。云信的分布式任务处理系统,则承载了其中的媒体处理这一能力,主要功能有音视频的转封装、转码、合并、截图、加密、加减水印等,以及图像伸缩、图像增强、音量归一化等一系列前处理功能。
视频转码作为媒体处理的核心功能,在对大视频文件转码时,通常需要花费较长时间,为了提升服务质量,我们将重点提升视频转码的速率。本次文章将以分片转码为主,介绍网易云信在转码提速方面的努力与效果提升。
转码性能的影响因子与优化
常见的视频转码流程,一般如下图所示:
在转码过程中,瓶颈主要在视频流,因此我们对速度提升的讨论主要针对视频流处理,音频流处理暂不在考虑范围内。针对视频流处理的环节,从以下几个方面展开讨论:
-
源视频:一般源视频越长,那么编解码所需时间则越长。
-
封装与编解码:对于视频转封装、关键帧片段裁剪等不需要解码编码的处理,其所需计算量很小,一般耗时在1~2s。若需要重新编解码,则根据源视频、编码输出参数的不同,所耗时间也不同。常见的有编码格式以及码率、分辨率、帧率,比如不同编码算法的压缩率与计算复杂度不一样,导致所需耗时也有所不同,举例来说 AV1 编码时间就大于 H.264 的编码时间。目标码率、分辨率、帧率等参数越大,通常计算耗时更大。
-
计算资源的水平与垂直伸缩:通用处理器的单核计算能力越强,转码耗时肯定越小。利用 GPU 这类更适合图像计算处理的专用处理器,也有利于降低转码耗时。提升转码执行流的并发计算度,也有利于降低转码耗时。这里的并发路数可以是多线程和多进程,多线程指单进程内以多线程的方式提升,多进程则为通过对文件切片后,多进程对多个切片文件计算。
-
集群任务调度:多租户的云服务系统,通常都是基于租户间资源分配优先级和租户内转码任务优先级综合而设计的调度算法,调度效率主要在以下几个方面体现:如何用更少的时间调度多的任务,如何用更少的集群资源实现高吞吐量,如何做好优先级和防饥饿的权衡设计。
针对上述的影响因素,我们提出以下几个优化方向:提升硬件能力、优化编码、分片转码以及提升集群调度效率。
专用硬件加速
多媒体处理是典型的计算密集型场景,优化多媒体应用程序的整体计算性能至关重要。CPU 是一种通用计算资源,将视频图像类运算 offload 到专用硬件上是一种常见的方案。目前,业内诸如 Intel、NVIDIA、AMD、ARM、TI 等芯片厂商都有相应的多媒体硬件加速方案,提升高码率、高分辨率等视频场景的编码效率。
我们的转码系统主要基于 FFmpeg 多媒体处理框架,在 Linux 平台上支持的厂商方案有诸如 Intel 的 VA-API (Video Acceleration API)和 Nvidia 的 VDPAU (Video Decode and Presentation API for Unix ),同时两家厂商也支持了相对更私有的 Intel Quick Sync Video 和 NVENC/NVDEC 加速方案。目前我们主要采用了 Intel 核芯显卡的视频加速能力,结合 FFmpeg 社区的 QSV Plugin 和 VAPPI Plugin 两种方式,针对 AVDecoder、AVFilter、AVEncoder 三个模块做了硬件加速。硬件加速相关技术,相关厂商和社区都在持续优化,在我们的后续系列文章中,我们也会详细介绍这块的进一步实践。
AMD 大核服务器
这里主要指搭载了 AMD EPYC 系列处理器的服务器,相对于我们此前线上的服务器,其单核计算力更强,并行计算能力更优异。单核计算力的提升让我们在解码、前处理、编码能有整体性的通用提升,而搭载超大核则是让我们的分片转码方案中的单机多进程场景更加如虎添翼,大大避免了媒体数据的跨机器IO通信。
自研 Codec
NE264/NE265 是网易云信自主研发的视频编码器,在云信的 NERTC、直播点播中都有所应用。除了编码性能的提升之外,NE264 更重要的技术优势是低带宽高画质,其适用于高码率高清晰度直播场景(如:游戏直播、线上演唱会、产品发布会等),可以确保人眼主观画面质量不变的情况下,平均节省 20%~30% 的码率。这里不再展开介绍,有兴趣的可以关注网易智企技术+微信公众号。
分片转码
如果说上面的几种性能优化手段是垂直的,那么本节所说的分片转码则是水平的。视频流本质就是一连串的图像组成,并以 IDR 帧为边界划分成一串 GOP,每个 GOP 就是独立的一组图像集。视频文件的这一内容特点,决定了我们可以参考 MapReduce 的算法思路,将视频文件切割为多个分片,然后并行地对分片进行转码,最后再合并成一个完整的文件。
任务调度
除了对单个转码计算执行流优化之外,我们也需要提升集群资源的整体调度效率。在调度算法这块,调度节点既要接收任务提交,又要完成任务下发的关键流程,这个任务下发的算法设计需要做好多租户分配、任务优先级抢占和尽量提升集群资源利用率的多方平衡。
我们设计了两种任务下发的机制:
-
Master 节点 push 任务到计算节点
-
计算节点主动来 Master 节点 pull 任务
前者的优点是实时性更高,缺点则是 Master 对计算节点的资源视角是一种 snapshot 快照,有些情况下该快照信息的滞后性会导致部分节点的过载现象。后者的优点则是节点按需取任务来执行,不会出现部分节点过载的现象,同时在任务选择性这块的可编程性更加便利,而缺点则是 Master 对全局资源分配的实时力度掌控性不足。
分片转码方案实践
媒体流程
媒体处理的简易流程如下图所示,主要分四个步骤:分片前转封装(按需)、视频分片、并行转码、视频合并。
分片流程
在集群资源充足的情况下,即任务的调度与分发一般不会有积压和资源抢占现象,这种情况下视频流本身的处理计算,一般会消耗整个任务周期 80%-90% 的时间,所以针对这一阶段进行优化,可以有更高性价比的收益。
提升硬件能力、优化编码这两个维度是针对提升单个转码进程的计算效率,但是单进程能调用的资源有限,对大视频文件的速率提升也是很有限。因此,在这里我们探讨如何采用分布式 MapReduce 的思想,缩短一个转码任务所消耗的时间。接下来的章节将详细讲述实现分片转码技术方案。
分片转码流程基础架构如上图,我们首先介绍以下几个概念:
- 父任务:类似于 Hadoop 中的 Job,客户端提交的转码 Job,需将其待转码的视频拆分成多个小分片;
- 子任务:类似于 Hadoop 中的 Task,将多个小分片包装成可以独立调度和执行的 Task 子任务;
- 父 Worker:执行父任务的计算节点;
- 子 Worker:执行子任务的计算节点。
分片转码的主要流程:
- Dispatch center 给 worker0 派发一个转码 job,worker0 根据总开关、job 配置、视频文件大小等策略判断是否进行分片转码;
- 如果确定进行分片转码,则进行分成 n 片;
- 包装成 n 个转码Task提交给 Dispatch center;
- Dispatch center 将这 n 个转码子任务调度给符合要求的 n 个 worker;
- worker1~n 转码完成后,给 worker0 发送回调;
- worker0 分别从 n 个 worker 上下载转码后的分片视频,待所有分片转码完成,将转码后的分片合并在一起;
- 发送回调给 client。
子任务调度
在调度系统中,每个用户的任务队列是独立的,并分别设置任务限额。当 Dispatch center接收到计算节点的 fetch job 请求时,调度线程先从多个用户队列中,选出已使用限额比例(比较简单的算法模型可以是,已调度任务数量/用户总限额)最小的用户队列,然后从队列头部取出一个符合该计算节点条件的子任务返回。子任务调度和普通任务调度在调度优先级、节点选择上有所不同,需要单独设计,这里我们简单介绍一下。
-
子任务优先级
子任务不需要在各自用户队列里重新排队,子任务调度的目标是希望可以第一时间被调度到。该父任务其实已经被调度到了,而系统处于加快该任务执行的目的,在系统设计上将其再次派发,如果还要和其他未被调度的任务一起竞争,那对这个任务来说是不公平的,也减弱了加速的作用。所以针对分片子任务,会将其放置到一个全局高优先级队列,优先被选择调度。
-
子任务调度节点选择
影响到子任务调度节点主要有以下因素:
- 机器类型
机器类型分为硬件转码机器与普通转码机器,由于两个环境中使用的编码器不一样,所以可能导致合并分片后的视频会有瑕疵,因此我们选择将子任务调度到与父任务相同的机器类型。
- 代码版本
不同版本的代码可能导致转出的分片无法很好的合并在一起,所以当出现这样的版本迭代后,可以通过计算节点 worker 上的代码版本,来确定子任务能调度到哪些其他计算节点上。
- 数据存储
当父 worker 上的任务并发大时,就会同时进行多个上传下载的网络传输,这样会导致分片文件 IO 阶段耗时增加,因此优先选择将子任务放在父 worker 上执行,就会节省网络 IO 与上传下载耗时。
掉队者问题
在分片转码场景中,掉队者问题(straggler problems) 是指在多个子任务中,如果大部分子任务已执行完成,但还剩少数子任务迟迟未完成,父 worker 久久无法进入到下一个流程,从而导致该任务被阻塞住。这在分布式系统中是一种较为常见的现象,针对该问题的系统领域研究论文也是屡见不鲜。
对这个问题的解决方案很大程度会影响系统的效率。如果父 worker 选择一直等待子任务,就可能出现任务过长时间的等待,这违背了我们提速的初衷。因此,基于保证该任务在有限时间内能被完成的原则,有以下几个优化方向:
1.冗余调度
这个方案参考了 Hadoop 中 MapReduce 对掉队者问题的解决方案:当达到超时标准时,子任务还未完成,则父 worker 会针对同一个分片文件再次发送一个新 Tsak 子任务给 Dispatch center,让其重新调度,并重新执行。当有其中一个子任务完成则取消另一个。
这种做法是用空间换时间,不把希望只放在一个节点上,而是采取赛马机制。但是,当这样的情况发生较多时,则会产生大量的任务冗余,而且也不能保证新起的子任务不阻塞。
2.父 worker 接替完成
为了解决冗余调度中的不足之处,我们对其进行了优化。当达到超时标准,而子任务还未完成时,则父 worker 会挑选完成进度最少的分片进行转码。同样,其中一个任务完成则取消另一个冗余的任务。若还有子任务未完成,则继续挑选,自己完成转码,直到所有子任务完成为止。
上述第二种方案较第一种方案的区别在于冗余任务不会重新调度到其他 worker 上执行,而是优先让父 worker 来冗余执行。这样,父 worker 上会持续对分片进行转码,直到整个 job 完全完成。最大的优点是:在不会无限制的消耗资源下,保证父 worker 不会处于无限等待状态中,只有在少数情况下,当父 worker 负载较高时,会考虑使用其他拥有空闲资源的 worker。
子任务进度跟踪
在父 worker 挑选子任务执行时,需要收集子任务的进度后选择进度最慢的子任务进行冗余执行。在计算任务进度时,我们将一次转码分为这四个阶段:等待调度、下载与准备、转码计算执行、上传与收尾。
不同阶段的开始,表示到达不同的进度:
等待调度0% → 下载与准备20% → 转码计算执行30% → 上传与收尾90% → 结束100%
其中,转码计算执行占70%,也是执行速度无法保证的一个阶段,所以需要详细计算进度,转码执行流会定期输出 metric 日志,并进行监控计算,当前转码进度 = 已转码时长(time 字段)/ 需转码时长 。
HLS/DASH 封装
HLS 格式与其他封装格式不同之处在于其会有多个 ts 文件与 m3u8 文件,对转出 HLS 视频的任务进行分片转码,会增加分片视频传输与管理的复杂性。因此我们对此问题的解决方案是先将源视频转成 mp4 视频,然后在父 Worker 上合并后,再对整个视频转换 HLS 封装。
测试数据
通过记录并对比同一个视频转为不同分辨率视频的速率,我们可以发现各个单个的优化措施对转码速度都有不同程度的提升。在实际线上场景,我们通常会根据用户设定、视频属性决定综合使用某几项优化方式。
测试视频1属性:
Duration: 00:47:19.21, bitrate: 6087 kb/s
Stream #0:0: Video: h264 (High), yuv420p, 2160x1216, 5951 kb/s, 30 fps
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 127 kb/s
测试视频2属性:
Duration: 02:00:00.86,bitrate: 4388 kb/s
Stream #0:0: Video: h264 (High), yuvj420p, 1920x1080, 4257 kb/s, 25 fps
Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 125 kb/s
结语
以上就是本文的全部内容,网易云信转码团队主要通过调度优化、硬件能力、自研编码、分片转码等维度切入,提升视频转码速度,测试结果显示转码提速效果显著。另外着重介绍了云信转码系统中对分片转码模块的主要设计。我们也会持续进行技术探索,实现提速以及更多的场景覆盖。后续的系列文章中,我们还会对集群资源调度算法、硬件转码实践等其他方面的内容进行分享,也欢迎持续关注我们。
作者介绍
罗微恒,网易云信高级服务端开发工程师,硕士毕业于武汉大学计算机学院,网易云信转码团队核心成员,目前负责云信媒体任务处理系统设计与开发工作,致力于提升云信视频转码服务质量。