“零耗时”首帧视频体验的优化实践

直播

点播端到端音视频解决方案

image.png

上图是火山引擎点播端到端的音视频解决方案架构图。点播端到端一般指视频从上传到播放所经历的全链路技术解决方案,涉及的主要技术模块包括上传 SDK视频处理与管理CDN 分发以及最终播放端的点播 SDK。在每一个环节里火山引擎点播中台都做了很多相关的技术优化和重点功能的迭代。随着我们服务的用户和业务越来越多,我们也经常收到实现极致体验的需求。于是,在近几年的主要工作中,我们面向用户体验做了一些相关的点播端到端解决方案。但是体验与成本存在一定的矛盾关系,如何在有限的资源条件下将用户体验最大化,取得两者间的最佳平衡,是我们点播解决方案所面临的挑战。

播放质量指标

在介绍我们的技术优化实现之前,先来看一下如何衡量用户体验。我们将用户体验拆解为播放源质量、交互体验和观看体验三个方向,而用户体验的质量指标一般会分成三个层次:

  • QoS (Quality of Service):即播放器本身能够量化的技术指标,主要包括 4 个方面:
    • 播放失败率:基于起播和未起播两个环节,涉及大盘级的播放失败率和起播率。
    • 起播时间:和用户播控时间点相关,涉及首帧时间和 seek 后的起播时间。
    • 卡顿指标:卡顿是影响用户观看体验的一个重要因素,卡顿指标包括卡顿渗透率、百秒卡顿时长、卡顿次数等。
    • 画质指标:画质是播放效果的最终呈现,涉及的指标包括码率、转码档位、分辨率、转码质量等。
  • QoE (Quality of Experience):在播放器可以监控到的 QoS 指标之上,我们加入了真实场景中用户行为侧跟业务相关的数据,包括播放次数、播放时长、完播率、投稿量以及投稿率。
  • 业务数据:再往上延伸,每一个业务最终关注的是 DAU、留存、广告、收入和成本等指标。

以上三个层次的数据指标可以让我们实现真正对业务增长有收益的播放体验优化。

认识首帧时间

在介绍首帧这个概念之前,我们先来了解下播放事件的生命周期。一次播放,也就是 VV(VideoView),是指这次播放任务从建立到结束的整个过程。

image.png

我们把一个完整的播放任务分为起播前、播放过程中和播放完成三个状态。在起播过程中,由于用户等不及而退出,或者因为播放器原因导致用户被迫退出,这些都属于未起播率。在播放过程中,我们需要关注所有网络相关的问题如卡顿等,以及 seek、pause 等播控行为。播放完成这个状态也包含三个类型:

  • 播放失败:由于网络、设备等播放器相关原因或者系统 bug 导致用户被迫退出。
  • 在业务侧符合预期的情况下,播放器正常完成了播放任务。
  • 在不符合用户预期的情况下,APP 退出。这时播放器本身没有发生错误,而是由于 APP 进入后台整个进程被 kill 所导致。

以上播放事件生命周期几个环节的拆解也是我们播放埋点建设的依据。我们通过一次播放的 sessionID 或 traceID,将整个播放过程中所有事件进行关联,然后进行细致的播放体验分析。

image.png

首先我们定义一下什么是首帧。一般来讲,首帧时间的定义是从用户操作播放相关动作(点击播放、滑动卡片等)到首帧渲染出来的耗时,即用户从 App 上感知到的耗时。首帧时间除了业务侧关于用户点击、页面创建和渲染的耗时之外,还包括播放器层面的视频 prepare、视频 play、数据下载以及数据下载完之后的解码和渲染这些环节的耗时。 而再往下进行拆分,首帧的时间会区分为两个层面:

  • 播放器内核层面的复用、解码渲染、起播水位判断等策略和逻辑层的相关耗时。
  • 和播放器相关联的网络 IO 模块层面:包括 CDN 和 P2P 之间的 PC 切换、DNS 解析、网络连接以及下载等耗时。

下图是我们线上一次真实的播放首帧埋点耗时示例。

image.png

示例的首帧耗时分为 4 个阶段:

  • 业务耗时:在业务层的埋点包括页面的创建、渲染等相关业务自定义的埋点数据。在播放器层面主要基于从 set url 到最后触发播放的时间。
  • 播放器内核初始化耗时:包括播放内核各模块初始化耗时,可以看到在我们的线上应用中这个耗时很小,下文会介绍在内核层面我们进行了哪些优化。
  • 网络耗时:包括从 DNS 解析到播放器收到用于解码的视频首包的时间。
  • 解码渲染耗时:解码渲染是播放器内核核心的功能模块。

上图所示的从设置 URL 一直到收到首帧消息整个过程的时间,就是最后呈现在大盘上的首帧时间。

“零耗时”首帧优化实践

什么是“零耗时”首帧?耗时本身想描述的是用户侧是否感受到了耗时这件事。所谓“零耗时”,并不是真的 0 毫秒起播,而是指用户在起播时平滑播放,没有首屏的顿感。从我们现在的大盘来看,核心业务方 50% 的播放首帧都已经小于 100ms 了。从交互设计体验角度来看,小于 200ms 的时间人体感知就已经不明显了。所以我们认为,100ms 对于用户来说就是零耗时的首帧

image.png 我们做了哪些优化来达到小于 100ms 的零首帧呢?前面提到对于每一个首帧,我们都细拆了很多环节,不同的环节都进行了有针对性的技术优化。我们梳理了一下,将首帧时长的构成拆解为了 4 个模块:

  • 业务相关的页面创建、交互和渲染耗时
  • 网络连接耗时:包括业内常用到的连接复用、预连接等策略。网络层面经常会存在很多不稳定因素,所以对于节点优选和网络超时优化,我们也会基于客户端层面,以单个 VV 实例以及用户相关的上下文去做不同的优化尝试。再者就是比较常规的预加载、DNS 缓存优化等优化措施。
  • 解码耗时:解码耗时一方面与播放源格式强相关。例如对于 MP4 格式,如果想减少解码耗时,就要保证 MP4 的 moov box 在前面,避免播放器在下载了部分数据后又要到文件尾部再解析 moov 文件,增加了数据请求耗时。另一方面与设备的软解/硬解方案相关,硬解在下文会有详细介绍。对于我们自己的播放器来说,在软解方面会有一些自研的低延时模式优化。
  • 播放器策略逻辑耗时:这个模块更多的是关注指标置换和指标平衡。比如对于起播水位的优化,我们要实现在控制起播水位 buffersize 时,既保证用户对首帧无感知,又保证后续播放流畅。 下面会详细介绍我们正在进行或是已经上线的一些优化案例。业务耗时优化:预渲染预加载是当前通用的优化网络耗时的解决方案,指的是在播放当前视频时,如果网络能力允许,会提前触发后续视频的下载。预加载相当于减少了用户可感知的网络加载和网络连接的耗时。但是通用的预加载解决方案没有把播放器和网络 IO 进行强紧密的结合,在预加载时只是基于数据模块触发下载任务,而此时播放器的实例还没有创建起来。对于这种情况,我们在思考能否把播放器创建以及初始化的工作整体前置。此外,我们还发现当播放的封面图和首帧差异很大的时候,用户会感受到明显的跳变。这个跳变有可能会让用户感受到卡顿,或者是其他不舒适的感觉。所以这一部分也是我们整体的优化目标。基于以上这些考虑,就产生了我们的预渲染的解决方案 :当前任务在播放时,提前起播下一个播放任务,渲染首帧替代封面,从而降低首帧耗时。这是一种播放器多实例解决方案,除了当前播放的视频之外,还需要初始化一个额外的播放器实例以支撑下一个视频的渲染。

image.png 上图是我们相关业务实践的真实过程。在上一个视频的播放过程中,会先判断下一个视频播放的触发时间。我们会考虑几种触发时机:

  • 播放水位符合预期:在当前网络情况满足播放流畅度,且手机端的资源消耗能够支撑下一个播放器实例的初始化和渲染时,才会加载预渲染的实例。
  • 预加载完成:当前的预加载支持相关初始化和预渲染。
  • 交互的卡片滑动:在交互过程中已经证明上一个视频完成了异步 release,可以提前对下一个视频进行初始化和预渲染。

以上就是预渲染在业务侧的基本逻辑。在极致情况下,用户侧对于网络、解码、渲染耗时均是无感知的。 网络耗时优化——节点优选前文提到,在首帧出现之前还有一个很重要的数据准备环节。数据准备主要是数据模块的下载,可以分为请求开始、DNS 域名解析、DNS cache 等阶段。如何从 DNS cache 或者 IP 池中选出高质量 IP 进行后续建联,从而完成相关首帧播放以及后续播放,这就节点优选要做的事情。网络 IP 选择影响到首帧、未起播率、播放失败甚至后续卡顿等一系列数据,重要性非常高。节点优选就是要在 IP 池里选择最优 IP,保证播控和数据加载都符合预期。但是,节点优选对于点播体系来说,也存在很多潜在风险性问题。端侧本身存在较大的限制:设备之间无法实时感知彼此的择优路径。在极端情况下,如果大量用户都优选到了同一个节点,而请求量级超过了节点的负荷能力,就会影响服务端 CDN 的稳定性。因此,目前我们会把节点优选作为长尾用户的质量优化。

image.png 上图是节点优选的整体逻辑。在 DNS 解析之后,相关 IP 会经历节点优选的过程,得到最优的推荐 IP。我们再基于这个 IP 进行后续的 TCP 建联、数据上传下载,并在数据传输过程中反向把建联、下载过程中的网络状态传递给节点优选组件,进行二次训练和数据迭代。我们在节点优选过程中面临两个大的挑战:

  • 何时主动选择切换节点;
  • 如何保证切换后的节点是局部最优甚至全局最优。

节点优选策略包括:

  • 节点小黑屋:在没有节点优选的方法之前,如果一个 IP 出现了较大的问题,我们的做法是对该 IP 反复重试。但因为节点本身有问题,无论怎么重试结果都是不符合用户预期的。因此我们就尝试了小黑屋策略,把有问题的 IP 放到小黑屋一段时间,使得下一次播放时不会选到这个 IP。
  • 请求异常归因:当播放失败需要重试时,我们需要对 IP 失败进行基础性归因,弄清楚是端侧故障导致质量差,还是传输 CDN 的节点问题。对于端侧的问题导致的节点质量差(比如当我们进入电梯和地铁时,由于环境因素导致设备的丢包率很高),我们做的判断应该是尽量少的切换节点,而不是频繁切节点。而对于节点故障,则应该尽早切换。
  • 节点质量排序:在节点切换时,如果有多个 IP 可选,就涉及节点选择的择优策略,前瞻性地对节点进行排序。可以根据 QoS 或者 QoE 或者节点本身的客观指标来排序,我们的选择是在旁路场景对当前节点进行探测。比如在预加载和预连接的场景下,进行更多新节点的尝试,从而得到更多、更大覆盖度的探测。

以上介绍的节点优选是我们近期正在做的事情,也是我们想持续优化和尝试的,目的是最大化端侧播放的可用性。网络耗时优化——解码耗时解码耗时的优化是我们相比其他开源播放器在首帧上保持优势的根本原因。解码本身需要耗时。当一个视频的数据完成准备,在解码过程中至少会经历以下几个环节:

  • formater 和 demuxer
  • 音频和视频分别的解码处理和优化。
  • 端侧对音频和视频的渲染。

如果我们不进行任何优化,把以上所有所流程都托管,ffmpeg 进行解复用,MediaCodec 进行解码,整个过程基于不同机型的耗时至少在 100ms 以上。我们在解码耗时上主要做了如下三个方面的事情:解码初始化耗时一般情况下,硬解初始化的平均耗时大于 100ms。就硬解本身的原理来看,在初始化的过程中只需将相关的 Codec ID 或者 Codec name 给 OpenMAX 层,OpenMAX 层再进行解码器实例的创建。也就是说,正常的解码或初始化流程,需要先下载数据,将 header 前部分的视频流 header 信息进行解析,解析完以后获取视频的 Codec 进行初始化。对这个过程,我们当前的策略是把硬解的初始化和 header 解析以及解复用完全并行。在下发视频流时,我们会告知该视频的 codec,这样在数据下载及解复用的并行过程中已经完成了当前的硬解初始化,从而优化了整体的数据消耗。此项优化平均减少了 80ms-120ms 的首帧耗时。解码器复用在完成了异步初始化后,下一步我们就在思考能否省略初始化的步骤。以当前短视频比较常见的 feed 流的实现方式为例:当上一个视频完成了解码初始化,下一个视频是否可以直接复用上一个视频的解码器?这里我们使用了 codec_pool 进行相关解码器的复用。在上一个视频完成播放时,我们把相关的  codec 复用到下一个视频,这样就省略了两个视频初始化过程中 stop release 到 start 的耗时。这一部分优化的耗时平均在 40+ms机型能力大数据择优播放器可以最大化播放成功率,但不是所有设备都能 100% 保证硬解成功。我们在播放端有硬解的 fallback 策略,也就是当硬解失败之后,会 fallback 到软解来支撑播放顺利进行。但是这个过程经历了硬解初始化、硬解初始化失败、fallback 到软解、软解初始化、后续解码这样一连串的过程。对于这一部分,我们想利用大数据的能力来快速理解当前设备的性能,在分发的时候进行相关策略优化,也就是我们现在在尝试的机型能力大数据择优。我们会埋点追踪每次 VV 是否在客户端侧进行了相关 fallback 的二次兜底,并且把每一个设备支撑到 codec 的能力进行落地,从而能够基于用户的机型能力分发最优的决策判断,包括对应的 codec、是否硬解等,这样我们就尽量减少了视频在初始化过程中出现的被动 fallback 的情况。从线上当前的优化来看,整体 VV 的千分之三才会涉及 fallback 能力的触发。

首帧优化目标——拐点分析

前面技术性的优化都是通过做减法来保证首帧能够快速出现。但是我们在做首帧的时候也不能忘记初衷,就是让用户感知不到首帧的加载。所以我们也在探索什么叫做无感知。我们近期在尝试的拐点分析就是去探索起播时长为多少对于业务才算极致最优。于是我们把起播时长和用户离开当前视频的速率对应了起来。基于数据拟合,我们就能够看到当前业务在起播阶段用户整体的容忍程度情况。我们从某一个业务的数据观测到:

  • 用户流快速离开的第一个波峰大概是在 50ms 以内。对应的用户行为一般是快速滑动,这时视频还没有加载完,用户不感兴趣就快速滑走了。
  • 70ms 到 200 ms 左右整体数据非常平稳,在这个区间内,起播等待时长的增长对于用户的影响程度基本一致,说明用户的等待容忍度基本上就在 70ms-200ms。
  • 200ms 以后数据开始明显劣化,这时可能有大量用户开始感知到等待时间,就逐步离开了。

从整体的大盘数据来看,首帧时间要保证优化到 200ms 以内对业务才是尽量无损的。在 200ms 的数据指标下,我们有时候就可以进行相关指标置换。前面提到,在起播的时候,可以选择立即起播,也可以选择缓冲到一定水位再起播。因为缓冲到一定水位之后能保证用户后续播放的流畅度。有了以上的数据支撑,我们也就知道了在不同的业务侧用户的容忍度的边界值,在这个边界值之内,我们可以平衡各种指标进行优化。

总结与展望

我们现在做的事情是通过优化打造极致体验的点播解决方案。我们理解的极致体验优化并不是技术上的自嗨,而是尝试站在用户的角度衡量这些优化能否解决他们在真实播放场景下的痛点。我们作为点播中台,需要支撑不同的业务,在不同业务场景里要能具备满足用户需求的能力。对于点播中台的技术架构来说,我们有“三高”的愿景:

  • 高通用性:业务希望接入中台以后都能够得到基本且最优的解决方案。因此我们要保证方案的通用性,满足业务方低门槛接入。
  • 高扩展性:每一个业务方因为用户和业务场景的差异,会有不同的定制化能力的诉求。我们也在持续追求高可扩展性,和业务方共建或提供定制化能力。
  • 高质量保障:要支撑亿级用户的播放,我们就需要保证质量和稳定性。因此,建设全链路监控,提供故障诊断定位的能力是必要的,这样才能第一时间感知线上故障,或者是通过埋点体系追溯单点问题;在高质量方面,针对业务不同种场景,提供最优的点播策略解决方案。
0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论