全栈算力,加速行业AI落地 了解详情
写点什么

喜马拉雅直播秒开优化实践

  • 2023-07-12
    北京
  • 本文字数:4486 字

    阅读完需:约 15 分钟

喜马拉雅直播秒开优化实践

作者:喜马拉雅直播技术客户端 杜若尘

 

随着互联网的发展,越来越多人喜欢直播,喜马拉雅直播也在快速发展中,现如今已有秀场直播、课程直播、语音聊天室等类型。为了提升用户的使用体验,本文针对喜马拉雅直播的复杂流程进行了整体梳理,并详细说明了开展的一系列直播启播优化工作。 

背景


在直播场景中,QoE(Quality of Experience) 很重要,也就是用户体验,而 QoE 和 QoS 又是紧密相关的,作为技术开发,需要从技术的角度去思考如何提升用户体验。直播场景中,用户的体验指标有很多种:首帧、卡顿、延迟、清晰度等等。站在用户的角度出发,在进入直播间后,肯定希望能够马上看到画面,也就是启播速度要快。所以首帧优化成为首要任务。

统计口径



在做直播启播优化前,首先要思考如何去衡量启播体验,也就是说要将启播体验转化为可量化的指标,并制定体验标准以便衡量体验的好坏。启播体验问题可以转化为首帧耗时问题,首帧耗时越短,启播体验也就越好,比如以下标准:

体验等级

衡量标准

优秀

启播耗时<=500ms

良好

500ms<启播耗时<=1s

一般

1s<启播耗时<=1.5s

较差

1.5s<启播耗时<=2s

很差

启播耗时>2s


制定标准后,体验优化问题也就转化成了指标优化问题。要做指标优化,需要获取准确可靠的统计数据作为开展工作的支持,明确统计口径是什么,只有这样客户端上报的埋点数据才是有参考价值的。对于时长的统计,需要明确开始和结束时间的定义,其差值就是需要上报的首帧时间了。


对于开始时间,选择不同的入口,统计方式也不相同。直播启动入口可以分为两个类:


1. 一类是从其他页面跳转进入直播间页面的,这类方式可以将页面的创建时间作为开始时间,在 Android 中对应 Fragment 的 onCreate 回调。

2. 另一类是在直播间页面内上下滑进入其他直播间,这类方式可以将下个页面被选中时作为开始时间,在 Android 中对应 ViewPager 的 onPageSelected 回调。


对于结束时间,可以将播放器的首帧回调作为结束时间,首帧耗时即为结束时间减去开始时间。

直播全链路分析

直播整体流程环节较多,主播推流到流媒体服务器,然后流媒体服务器分发到各地的 CDN ,用户通过播放器再从 CDN 进行拉流播放,即主播端推流 -> 流媒体服务器 -> CDN 边缘节点 -> 终端设备。其中终端拉流又可以细化成许多环节,像网络请求、读取数据、解封装、解码、渲染。


可以根据不同的端大致分为三个部分:推流部分、流服务部分和拉流部分。客户端所做的主要优化工作集中在拉流部分,但推流部分、流服务部分同样也会间接对启播速度产生影响,这里也一并提一下。

现状

直播协议选择



RTMP 协议为流媒体而设计,在推流中用得比较多,同时大多 CDN 厂商支持 RTMP 协议。


HTTP-FLV 使用类似 RTMP 流式的 HTTP 长连接,由特定流媒体服务器分发,兼顾两者的优点,还可以复用现有 HTTP 分发资源的流式协议。它的实时性和 RTMP 相当,与 RTMP 相比又省去了部分协议交互时间,首帧时间更短、可拓展的功能也更多。


HLS 由苹果公司提出基于 HTTP 的流媒体网络传输协议,可用于直播场景,Android 端也同时提供相应的支持。


喜马直播移动端使用的 RTMP 推流、HTTP-FLV 拉流。

推流端—动态码率


流量控制功能可以让主播根据自己当前网络环境状态来动态调整视频推流的码率、帧率、分辨率,以及音频码率,自动适应当前网络环境及网络波动,从而保证视频能流畅发布。


流量控制的原理是基于当前网络情况,对用户的网络环境建模并估算它的上行带宽。如果当前上行带宽小于设置的推流码率,则会通过配置的选项分别从视频码率、分辨率、帧率、音频码率几处循序渐进地降低,以减少最终推流的上行码率,保证直播的流畅性。在网络环境恢复正常以后,上行码率也会重新恢复到初始设置值。

流媒体服务器优化—缓存 GOP



视频是由一帧帧的图像组成的,GOP(Group of Pictures)是一组可以被独立解码的图像序列,它由一组不同类型的帧组成,包括关键帧(I 帧)、预测帧(P 帧)和双向预测帧(B 帧)。关键帧是视频序列中的完整帧,没有依赖其他帧进行解码。预测帧和双向预测帧则根据前面的关键帧或其他预测帧进行编码和解码。如果拉流时返回的第一个视频帧是 B 帧或 P 帧,那是无法被成功解码的,直到收到下一个 I 帧才能被解码展示画面,从而导致首帧时间变长。因此,一般 CDN 都会缓存一到两组最近的 GOP,在收到客户端的资源访问请求时返回最近的 GOP,这样能确保客户端收到的第一个视频帧就是 I 帧,可以立马进行解码和渲染。

启播优化方案

拉流各阶段耗时拆分


对于拉流过程,可以细化成许多环节,如获取拉流地址、网络请求、读取数据、解封装、解码、渲染。因此,需要把直播启播耗时进行阶段拆解、细化各个流程,并进行数据上报。在实际数据统计中,我们会对各个环节(如网络请求部分、解封装部分)进行更精细的统计。



 下方表格列出了喜马直播业务的启播耗时各阶段的数据统计:


阶段

名称

详细说明

1

businessCost

业务耗时:主要包含获取拉流地址的耗时,以及跨进程通信给播放器设置播放地址这一阶段的耗时

2

prepareMediaSourceCost

播放器资源准备耗时

3

cdnRequestCost

请求cdn 资源耗时

3.1

dnsCost

dns 解析耗时

3.2

connectCost

网络连接耗时

3.3

firstPackageCost

建链到收到首包耗时

4

flvHeaderCost

读取flv header 耗时

5

flvScrpitTagCost

读取ScrpitTag 耗时

6

audioHeaderTagCost

读取音频头耗时(主要包含音频编码方式等信息)

7

videoHeaderTagCost

读取视频头耗时(主要包含视频分辨率、编码方式等信息)

8

firstVideoTagCost

读取第一个视频帧耗时

9

audioDecoderCreateCost

音频解码器初始化耗时

10

videoDecoderCreateCost

视频解码器初始化耗时

11

firstFrameDecodeCost

首帧解码耗时


上表中步骤 4-7 需要读取解析 flv 头部信息,其具体结构组成如下所示:



从用户点击跳转到直播间或者在直播间内上下滑动切换直播间,到最终的直播播放成功,预计会有十多个的点位进行一系列的追踪分析。详细的数据表格呈现启播过程中每一步骤的耗时,然后针对于耗时多的地方进行优化。

预获取拉流地址


一般而言,用户进入直播间的业务场景,是从一个直播列表页面点击某一个直播卡片进入直播间。这个过程中,数据流是怎么走的呢?


在启播优化改造之前,观众从直播列表页点击某个直播卡片到直播间后,需要先向服务端请求一个直播间详情接口,详情接口发现直播间当前是处于开播中状态的话,再去请求该直播间拉流地址接口,交给播放器播放。


在这个过程中,播放器必须等到进入直播间,串行请求完两个接口拿到直播流地址后才能开始播放,这个时间点是可以提前的:在直播列表页及其他直播入口拿到直播间对应的流地址,在进入直播间时直接传过去,这样一进入直播间播放器就可以拿着直播流地址开始播放了,省去了从服务器请求直播流地址的时间。对于在直播间内上下滑切换房间的情况,就需要改造上下滑接口、增加拉流地址信息。


组件渐进式加载


直播间功能组件数量多达 34 个(如视频播放组件、红包功能组件、礼物动画功能组件等),页面层级复杂,在一些中低端机型上加载较慢,可以采用渐进式的加载方案,让优先级高的组件先进行展示。组件加载方式遵循以下三条原则:


1. 组件按需加载,用到的时候再进行加载;

2. 组件视图如果复杂,耗时问题不好解决,可以使用异步加载;

3. 组件可以按优先级串行加载,先展示主要视图,而后加载次要视图,最后加载特定条件才触发动态添加的视图。


这样设计的好处是可以减缓同一时刻加载 View 带来的压力,实现先加载核心部分的 View,再逐步去加载其他 View。比如在喜马直播间就可以先加载视频播放组件,然后再依次加载其他的功能组件。 

预渲染


1. 从外部页面进入直播间场景


直播间页面是在一个上下滑容器中进行加载的,从其他页面进入直播间遵循这样的时间线:直播间容器页面创建-> 直播间容器页面渲染 -> 直播间页面创建 -> 直播间页面渲染。当点击入口跳转的时候调用播放器进行播放,如果在直播间页面渲染出来之前第一帧画面已经解码好了,这时由于直播间页面的 TextureView 的 Surface 仍然是不可用的状态,会导致解码的视频帧无法去送显。


因此,可以在直播间容器页面创建一个 SurfaceView ,将解码的视频帧先渲染至容器页面的 SurfaceView 上(之所以用 SurfaceView ,因为它 Surface 的渲染可以放到单独线程去做,避免主线程卡顿造成画面无法渲染的问题),等到直播间 TextureView 的 Surface 可用后,再将视频帧渲染到 TextureView 上,实现无缝切换。



2. 直播间上下滑场景


针对直播间上下滑场景,因历史原因,播放器是单例,所以在直播间上下滑场景中无法进行下一个直播间的预加载,只能等到下个直播间被选中时才能进行播放,也就是上下滑过程中手指抬起后开始播放下一个视频。同时,由于直播间 view 是复用的,要等到屏幕停止滑动后,才可以去复用上个直播间的 view 。

因此,如果屏幕在滚动过程中,首帧已解码成功后,可先渲染至容器页面的 SurfaceView 上,待屏幕停止滚动直播间 TextureView 的 Surface 可用后,再将视频帧渲染到 TextureView 上。


使用 HttpDNS



网络请求中,DNS 解析是一个相对耗时的过程。在传统的 localDNS 方案中,APP 向所在网络运营商的 DNS Server 发起域名解析的请求,而运营商 DNS Server 会向 CDN 的 GSLB (Global Server Load Balancing)全局负载均衡系统发起递归查询,GSLB 通过运营 DNS Server 所属 IP 地址判断查询来自于哪个运营商和地理位置,然后返回若干合适的 CDN 边缘节点 IP 给它。


HttpDNS 使用 HTTP 协议进行域名解析,把域名解析请求直接发送到 HttpDNS 服务器,从而绕过运营商的 Local DNS ,能够避免 Local DNS 造成的域名劫持问题和调度不精准问题。由于中间少了一条链路,所以这部分的耗时大致可以省却几十毫秒。同时将节点 ip 进行缓存,下次有相同域名的请求时直接返回该 ip,省去 DNS 解析耗时。

优化播放器缓存水位管理


播放器缓冲区主要有两个水位管理概念,一个是启播水位,另一个是卡顿后启播水位。顾名思义,启播水位表示首次拉流播放时,缓冲区达到多长时间的数据可以开始播放。卡顿后启播水位表示如果缓冲区没有数据发生卡顿了,需要再下载达到多长时间的数据才可以继续播放。对于启播优化来讲,为了让用户能够更快地启播,可以将启播水位调的尽可能小。因此在直播场景下,我们将播放器的启播水位设置为了 100ms,以达到更快的启播速度。


成果与思考


经过一个季度的优化,目前喜马音频直播的秒开率达到了 90% 以上:



视频直播的秒开率达到了 85% 以上:


体验导向的应用理念


通过优化直播启播速度,不同渠道直播入口的播放转化率都得到了不同程度的提升,因此可以得出一个体验导向的应用理念:在引入任何优化时,首先需要评估方案的整体收益;之后需要设计整体优化方案进行开发调优;通过 AB 实验评估每项技术的业务指标影响,刻画性能指标与业务指标的关联;反复以上迭代过程。

未来展望

H.265


H.265 旨在在有限带宽下传输更高质量的网络视频,仅需 H.264 的一半带宽即可播放相同质量的视频。因此,对于相同质量的视频,H.265 只需下载 H.264 约一半的数据量即可进行解码渲染,有利于提高启播速度。

 

上下滑多播放器实例


上下滑是喜马切换直播间的一个重要的方式,后续可以使用多个播放器实例,在手指按下开始滑动的时候就去静音播放下一个直播间的资源,待手指抬起确定滑入下个直播间后再取消静音播放,同时停止上个直播间的播放。相比于单播放器实例,这样可以省去拖动屏幕这段时间的耗时。


解码器复用



通常情况下,视频正常播放时解码器都需要进行 create()、configure()、start() 等初始化操作,而播放其他视频时,每次播放都必须重新进行这一初始化流程。根据上面的解码器状态流转图,如果播放完一个视频后不去 release 、stop 解码器资源,而是直接用于解码下一个视频,实现解码器的复用,就可以省去创建及初始化解码器的耗时。


解码器复用的核心条件是支持自适应播放属性,此属性是指 Android 提供的一种无缝切换不同分辨率视频的能力,可以由系统接口(android.media.MediaCodecInfo.CodecCapabilities.isFeatureSupported(String)) 查询是否支持。同时还要求当前视频编码格式要保持相同,分辨率不能超过上个视频的分辨率。

 

2023-07-12 17:202864

评论

发布
暂无评论
发现更多内容

switch 语句

Hello

再下一城 三六零收购织语CCwork深化“智慧办公”生态布局

人称T客

四个和成长有关的小故事

霍太稳@极客邦科技

团队管理 TGO鲲鹏会 团队组织 职业成长

UML 建模

师哥

比Webpack更高效的Rollup入门指南

费马

大前端 Rollup 打包 webpack

初步架构想法

极客大学架构师训练营

if语句

Hello

架构师训练营第1周学习总结

Season

极客大学架构师训练营

【大厂面试04期】讲讲一条MySQL更新语句是怎么执行的?

NotFound9

MySQL 数据库 后端

解决出海网络难题 融云保障 MiniJoy 千万印度用户流畅互动

Geek_116789

食堂就餐卡系统设计

GalaxyCreater

数据类型转换

Hello

架构师训练营第一周总结

Hanson

极客时间第0期架构师训练营第一周总结

2流程序员

剖析Golang Context:从使用场景到源码分析

伴鱼技术团队

源码分析 并发编程 程序语言 Context Go 语言

「架构师训练营」Week01 作业+总结

PowerZhang

极客大学架构师训练营

游戏夜读 | 毛利率有多少?

game1night

架构课程心得

dj_cd

极客大学架构师训练营

位运算

Hello

第一周.UML课后作业

西柚

UML

食堂就餐卡系统设计

Lane

食堂就餐卡系统设计

Season

极客大学架构师训练营

架构师训练营作业--Week1

吴炳华

神奇的梦想

霍太稳@极客邦科技

身心健康 个人成长 目标管理

讲一个程序员如何副业月赚三万的真实故事

非著名程序员

程序员 独立开发者 副业赚钱 提升认知

[Go] 写一个守护协程的通用套路是什么?

eddix

pattern Go 语言

重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)

小傅哥

设计模式 小傅哥 重构 代码质量 代码坏味道

架构设计作业1——食堂就餐卡系统设计

Andy风

架构师训练营作业

Hanson

架构师训练营第一周总结

Hugo

食堂就餐卡系统设计

Coder的技术之路

喜马拉雅直播秒开优化实践_音视频(前端)_喜马拉雅技术团队_InfoQ精选文章