1. 背景
视频转码是将视频文件经过解封装、解码、滤镜处理、编码、封装从而转换为另一个视频文件的过程,B站每天都有大量的视频原片上传后经过转码系统转换为多个不同分辨率。转换后的视频在画质接近原片的前提下会拥有更低的码率,因此会提高网络传输时的流畅性并节省带宽;同时,形形色色的视频原片经过转码后会生成为较为统一、规范的编码规格,也大幅提升了播放时的设备兼容性。
目前业界使用最多的服务端视频转码框架是FFmpeg,它可以处理几乎所有格式的多媒体文件。FFmpeg的转码核心组件是实现了封装/解封装、编解码、滤镜、算法原子能力的基础库,同时,FFmpeg也提供了可以直接运行的命令行工具ffmpeg,实现了简单的转码流水线逻辑。
ffmpeg7之前的串行流水线
随着转码业务的扩展,原生的ffmpeg命令行工具也暴露出了很多局限性:
在多清晰度转码时,因为流水线是串行的(*FFmpeg7.0前),在音频编码、复杂滤镜场景,对点播无法最大化利用多核cpu的优势,对直播可能会让各清晰度编码互相阻塞。
在直播这种长时间持续转码场景下,无法通过交互动态更新转码参数
所有转码流水线控制逻辑基本都塞在了5个.c文件里,模块划分不清晰,修改/维护成本很高
FFmpeg版本升级时,代码迁移难度高
因此,考虑转码业务的扩展,需要进行转码核心的自研,替换ffmpeg命令行工具。
2. 自研转码核心架构
如下图所示,自研转码核心采用FFmpeg基础库作为底层的多媒体处理原子能力,对转码流水线的各模块进行了抽象,对点直播不同的业务需求,衍生出不同的子类进行处理;Controller模块是转码的核心模块,负责所有流水线的帧调度逻辑,同样对点直播做了区分。点播业务的调度逻辑相对简单,主要包含了输入流-输出流的映射关系;而直播业务包含了导播台的定时取帧逻辑,以及消息交互逻辑(转码过程中动态更换输入、输出、滤镜等),逻辑比较复杂。
下图为一个两路清晰度转码对应的转码核心的模块架构图,流水线A和流水线B各视作一条Pipeline,一条Pipeline会对应一份转码产物;单个Pipeline内的单条音视频流对应的处理流程视作一条Flow;一条Flow内部的每个滤镜/编码/采样/封装操作视作一个Task。
转码核心运行过程中,上层业务可以随时下发动态指令,对转码的输入、输出及整条pipeline进行增删,这个功能在直播转码中,可以大幅提高转码流的启播速度:用户断流重推后,上行节点发生变更后,业务层可以在不重启容器的情况下,通过动态指令删除转码的输入与输出,再更新为新的上行、下行节点,可以节省100%的转码拉起时间,提高推流端断流重推后的转码清晰度的覆盖。
3. 模块级可控串/并行流水线
在点直播不同的场景,对转码流水线的流程控制有着不同要求,自研转码核心的每个Task类,继承了相同的基类PipelineWorker,可以自由选择以串行/并行的方式运行,并行模式的Worker会开启一个单独的线程进行帧的处理。
Task的串/并行调度策略依赖于业务需求和Task内部的并行度。
3.1 直播转码
直播转码因为对实时性要求很高,转码过程滤镜、编码、输出网络IO都是可能发生阻塞的模块,如果使用串行流水线,发生卡顿的风险很大,尤其是在一入多出的场景下,多个pipeline会互相阻塞;这也是为什么FFmpeg7.0前,不适合直接用于直播转码。开启并行模式后,Task会在内部队列达到阈值后触发丢帧,尽可能保证转码流的稳定。
3.2 点播转码
点播转码场景更看重转码性能,需要根据Task特性决定采用串行/并行模式:如果Task内部实现并行度低,使用并行模式可以增加cpu利用率,发挥线上容器多核优势;如果Task内部本身并行度较高或者逻辑过于简单,使用并行模式反而会让性能因为增加线程切换产生劣化。
4. 动态自适应转码
在直播转码场景,由于采用了flv封装格式+rtmp协议,直播源流可能在推流过程中随时改变分辨率、帧率等规格,这需要转码核心具备转码参数动态自适应的能力。
4.1 分辨率自适应
随着直播多人连麦业务的铺开,线上有越来越多的变分辨率直播流,flv格式可以通过刷新sequence header任意更新编码规格,而转码在感知到分辨率变化后,需要滤镜、编码、封装模块联动来保证转码流分辨率的正常刷新:
下图为不同宽高比的输入流,经过转码缩放(scale)滤镜后的宽高参数变化逻辑,采用了维持原始宽高比的zoom缩放:
滤镜组的参数自适应是一个难处理的点,因为滤镜种类繁多,参数和输入宽高的对应关系也不同,比如缩放滤镜scale只有目标宽高两个参数,只和输入帧的宽高比有关;而滤镜叠加有水印宽高、叠加坐标参数,计算起来就更复杂。因此我们复用了ffmpeg的表达式功能,业务层可以用占位符、已定义输入变量、表达式来使滤镜参数动态适应输入规格,而转码核心也只需要维护一套参数自适应规则即可覆盖几乎所有滤镜。
4.2 帧率自适应
大多数PC开播工具在推流时都会保持帧率固定,但是对移动端开播的用户,开播工具可能会根据网络情况在某个范围动态调整帧率,变化的帧率对帧采样逻辑提出了挑战。
目前视频帧采样算法有CFR(固定帧率)和VFR(变帧率)两种,针对直播变帧率场景,直播转码选择用VFR代替常用的CFR采样。主要是因为CFR算法在当前场景不够灵活,暴露出一些问题:
当使用固定的60/30fps规格进行CFR采样时,如果输入是50/25这种无法被60/30整除的帧率,会导致不均匀拷贝/丢帧,产生卡的感觉(见下图)
当使用输入流的初始帧率和目标帧率的最小值进行CFR采样时,如果源流中途帧率升高,会导致不应该丢弃的帧被丢弃,也会让播放相对于源流变卡了;如果源流中途帧率降低,会产生多余的拷贝帧,造成编码资源及码率的浪费。
使用VFR采样,当源流帧率大于设定帧率时,也会出现采样不均匀的情况(见上二图),为此,我们增加了VFR-HALF采样方式,会在源流fps满足一定条件时命中。如果当源流帧率为50而目标帧率是30的情况下,会将实际目标帧率调整为25(二采一),以保证采样画面的均匀,这也是youtube点播采用的方法。
5. 码流附加信息管理
码流附加信息主要是指码流中独立于帧压缩数据之外的数据,在avc编码中指sei信息。目前B站点直播业务均会依赖sei信息的处理能力。直播答题、跨年晚会的指尖游戏都是通过sei信息实现的,点播的HDR视频,也会用到sei中的调色信息。
ffmpeg命令行工具对sei信息的处理一直比较保守,在ffmpeg5以前,ffmpeg会在解码时直接丢弃sei信息;ffmpeg5以后,ffmpeg会将解码后的sei信息保存在frame结构里,虽然有sei信息写入的能力,但写入能力全部由对应的编码器来完成。
ffmpeg的sei处理逻辑因为强行绑定了编码器的内部实现,在当前自研+gpu+异构转码混合使用的场景下,无法覆盖所有的编码器且无法保证所有编码器写入方式的一致性。自研转码内核调整了sei的写入时机,在编码后增加了bsf滤镜统一处理所有avc/hevc/av1 sei的写入,在视频帧采样时也会根据业务配置,判断丢弃/合并弃帧的sei信息。
在直播转码中,我们也会使用sei记录转码流生产的整个生命周期。通过播端拿到的对应sei信息,可以准确分析出码流从推流端直播姬开始到用户链路上的各生产节点的延迟和其他核心指标,为指标优化提供数据支持。
6. 总结与展望
自研转码核心的开发初衷是覆盖ffmpeg命令行难以实现的业务场景,而随着业务需求越来越多元,自研转码核心的应用场景也越来越多。自2020年首次应用于直播导播台业务,自研转码核心已经覆盖了导播台业务、直播转码业务,目前正在逐步覆盖点播转码。
后续我们会继续聚焦在提升用户体验以及提升转码系统效率这两个目标上面:
之前我们已经和公司的AI、画质团队合作,完成了AI直播字幕、游戏赛事看板等功能的开发,后续我们也会接入AI相关更多的新功能、玩法到点直播转码中。
自研转码核心在点播流式转码的灰度过程中,已经展现出了比较明显的性能提升;后续我们会更细粒度地优化流水线串并行策略,将机器资源利用到极致。