播放器秒开优化

开篇

先说结论:

字节跳动就曾给出过一份数据:对一部分型号的 Android 手机,播放首帧时长从平均 170ms 优化到 100ms,带来了 0.6% 左右的用户播放时长提升。

衡量指标:

  • 播放秒开率,指的是播放器开始初始化到视频第一帧画面渲染出来的时间不超过 1s 的次数在总的播放次数中的比例。
  • 播放平均首帧时长,指的是播放器开始初始化到视频第一帧画面渲染出来的平均耗时。

回归探讨,首先要搞明白从数据到播放器都经历了什么,再逐个讨论优化点。

我们大致可以分为下面几个阶段:

  • 业务侧结合优化
  • DNS 解析
  • TCP 连接
  • HTTP 响应
  • 音视频探测
  • 音视频解码
  • 缓冲和起播策略
  • 渲染

业务侧结合优化

首先要看客户端上进入直播间的业务场景是什么样的?一般而言,都是从一个直播列表页面,点击某一个直播卡片(Cell)即进入直播间。

优化点:

1. 提前获取直播流地址
2. 结合 HTTPDNS 选择最佳 CDN 节点
3. 使用 URL 替代 VID
   - 将视频 URL 直接封装在 Model 中,避免多一次请求 URL 的时间开销。
4. 上下滑短视频提前加载播放器:
5. 封面图清晰度降级:
   - 当预加载、预渲染等优化做的较好时,封面图可以适当降低清晰度,节省带宽和流量。
   - 可以优先做预加载预渲染,在必要时才加载封面图。

DNS 解析

获取到直播流地址,需要进行dns解析得到www.ip+port/资源路径。这步优化就是构建dns缓存池,避免去运营商解析这个过程。DNS解析可以在几十毫秒到几百毫秒内完成,本地的话可以保证稳定再几十毫秒。

1. 优化 DNS 解析过程:
           -统计 DNS 解析耗时。没有缓存命中时,DNS 解析通常需要 300ms 以上的时间。有缓存命中则耗时很短。 DNS 解析耗时较长的原因是需要递归到根域名服务器查询。

//libavformat/tcp.c 文件中的 tcp_open
int64_t start = av_gettime();
if (!hostname[0])
    ret = getaddrinfo(NULL, portstr, &hints, &ai);
else
    ret = getaddrinfo(hostname, portstr, &hints, &ai);
int64_t end = av_gettime();

        - 没采用ipv6地址,需要设置只查询ipv4。

hints.ai_family = AF_INET;

        - 采用 HTTPDNS 和 LocalDNS 结合的方式可以提升 DNS 解析速度和准确率。在 App 启动时预解析热门域名,缓存在本地。
2. 实现 HTTPDNS:
     - 方案一是 IP 直连,将域名解析到的 IP 直接嵌入到 URL 中。(建议使用nginx作为中间层即可避免。)但存在两个问题:
     1. 如果服务端采用 302/307 跳转,客户端依然IP 直连,造成客户端没正确使用跳转ip导致url资源定向错误。
     2. 使用 HTTPS 时,证书验证会失败。

AVDictionary **dict = ffplayer_get_opt_dict(ffplayer, opt_category);
av_dict_set(dict, "headers", "Host: www.example.com", 0);

         - 方案二是替换 FFmpeg 的 DNS 实现,自行实现 HTTPDNS 解析逻辑。常见的做法是做一层播控服务,客户端请求播控服务获取到实际的播放地址以及各种其他的信息,然后再走 IP 直连就没问题。

//tcp.c 中 getaddreinfo
if (my_getaddreinfo) {
    ret = my_getaddreinfo(hostname, portstr, &hints, &ai);
} else {
    ret = getaddrinfo(hostname, portstr, &hints, &ai);

}


3. 提升 HTTPDNS 的有效率:
   - 在网络切换、滑动场景、内部刷新操作时,之前缓存的 HTTPDNS IP 可能会失效。
   - 可以实现一个轮询机制,定期更新 HTTPDNS IP 的缓存,保证 IP 的有效性。
   - 同时要处理各种网络切换或内部刷新时更新 IP 缓存的情况。

        DNS 优化是网络优化的重点,通过 HTTPDNS 和 LocalDNS 结合使用,可以大幅降低 DNS 解析耗时,从而优化首屏时间。

TCP 连接

1. 优化 TCP 建连耗时:
   - TCP 建连耗时反映了客户端到 CDN 服务器节点的点对点延时情况。
   - 它主要受限于三个因素:用户网络条件、用户到 CDN 边缘节点链路、CDN 边缘节点的稳定性。
   - 可以结合用户所在城市、运营商情况, 优化 CDN 调度, 使用 HTTPDNS 给用户分配更优的连接链路, 从而优化建连耗时。

2. 通过 TCP Fast Open (TFO) 优化 TCP 建连时长:
   - TFO 是对 TCP 三次握手的简化, 可在握手过程中交换数据, 减少一次 RTT。
   - TFO 流程包括:首次请求获取 Cookie, 后续连接携带 Cookie 进行验证和快速建连。
   - TFO 可减少 15% 的 HTTP 传输延迟, 全页面下载时间平均节省 10%, 最高可达 40%。

3. 通过 TCP 预连接和连接复用优化建连时长:
   - 缓存 IP 对应的 Socket 连接, 提供预连接接口给业务层使用。
   - 业务层可对下一个直播间提前做预连接, 真正拉流时复用这个连接。
   - 可对高频域名持续预连接, 提高预连接命中率。需注意网络切换时刷新缓存。

4. 避免首帧网络带宽争抢:
   - 在实现第三点基础上,且短视频上下滑场景, 快速滑动时可能会导致预加载的视频数据未被使用, 反而占用了后续视频的首帧带宽。
   - 可检测到快速滑动时, 及时中断预连接的 Socket, 避免带宽争抢。

HTTP 响应

优化 HTTP 响应耗时:

  • 如果请求有缓存命中, 响应时间一般在 50-200ms 左右。

提升 CDN 边缘节点命中率:

  • 避免在 URL 参数中带有随机值, 否则会降低缓存命中率。

  • 服务端可对热门内容进行预热, 提高边缘节点的缓存命中率。

优化短视频第一次 Get 请求:

  • 设置 HTTP 请求的 Range =文件长度 来省去第一次 Get 请求, 优化首帧时长。

音视频探测

5.1 优化音视频流探测耗时

- 直播业务中,视频流格式通常是固定的,无需复杂的探测过程:
  - 可以在播放器中直接读取固定的信息,开始播放,无需经过耗时的 avformat_find_stream_info() 函数。
- 针对 avformat_find_stream_info() 函数的优化:
  - 在外部设置较小的 probesize 和 analyzeduration 参数,这两参数决定控制该avformat_find_stream_info 读取的数据量大小和分析时长,频率越高,延时越低。

av_dict_set_int(&ffp->format_opts, "fpsprobesize", 0, 0);


  - 服务端标准化转码,确定视频格式,计算出最小的 probesize 和 analyzeduration。
  - 直接修改 avformat_find_stream_info() 的实现逻辑,针对固定格式进行优化。
  - 尝试去掉 avformat_find_stream_info() 步骤,自定义初始化解码环境。

5.2 短视频前置 moov box

- 在网络点播场景下,如果 MP4 视频的 moov box 在文件尾部,会导致播放器需要下载完整个文件才能开始播放。

- 可以在服务端使用 FFmpeg 将 moov box 移动到文件头部,优化播放体验:

 ffmpeg -i bad.mp4 -movflags faststart good.mp4

5.3 提前创建解码器
- 对于直播场景尤其有效,因为视频格式比较固定

音视频解码

6.1、提前创建解码器

播放器可以创建一个解码器复用池,当解码参数一致时,可以复用解码器。这样一来,业务也可以透传给播放器码流相关的信息,让播放器提前创建解码器来降低播放器首帧渲染时间。

解码器需要的信息通常包括:SPS、PPS、VPS(H.265)。

6.2、优化解码器刷新操作

IJKPlayer 播放器在完成音视频探测后,开始进行解码时,如果使用硬解,解码器会在开始做一次刷新解码器的操作,这个操作其实没有必要,但是会有一定的耗时,影响首包到渲染时长。去除这一次刷新操作,首帧时长收益 10-20ms。

缓冲区和起播策略

7.1 优化 Buffer 填充耗时

ffmpeg优化参数:BUFFERING_CHECK_PER_MILLISECONDS 减少检查缓冲区填充情况的时间间隔,从 500ms 降低到 50ms,可以减少 200ms 左右的缓冲耗时

MIN_MIN_FRAMES 最小帧处理。从 10 帧降低到 5 帧,首屏时间可减少 300ms 左右,且卡顿率只上升 2%。

7.2 流媒体服务器侧 GOP 缓存

具体gop指标,可参考下列文章设置。

基于 http-flv 的端到端延迟优化-CSDN博客

7.3 服务端快速下发策略

- CDN 服务端可以配置快速启动模式,在拉取直播流时以 5 倍于平时的带宽速度下发前 1 秒的数据,优化首屏秒开速度。速度提升 100ms 左右

7.4 提升 HLS 的播放秒开

- 直接开播:播放器策略影响大,如 iOS AVPlayer 需要 3 个 ts 切片才开始播放,IJKPlayer 使用水位线策略更快。
- 开播 Seek:IJKPlayer 支持 seek-at-start 能力,可直接下载目标位置数据而不需从头加载;服务端可以根据内容打点切成多个 m3u8,优化 Seek 响应速度。
- 解决 HLS Seek 黑屏问题:stream 的 first_timestamp 未正确初始化,导致无法找到 Seek 位置;IJKPlayer 需要解决跨断层 ts Seek 问题。

7.5  IJKPlayer 优化 

设置 Surface 时重置解码器的等待时长

- 在没有设置 Surface 时,让解码器线程直接等待,而不进入加锁的取 buffer 操作,避免后续设置 Surface 时因等待锁而造成的延迟。

7.6 视频预加载

- 提前下载一部分视频数据,达到快速起播的目的。但需要结合业务场景,调试出最合适的预加载策略。

7.7 视频本地缓存

- 将视频数据缓存到本地,下次播放时可直接从本地请求,节省带宽,提升首帧秒开速度。
- 需要考虑视频数据分片管理和缓存清理策略。

综合运用上述优化手段,可以大幅提升播放器的首帧渲染和秒开性能。

渲染

8.1 播放器预渲染

  • 在拿到视频 URL 后就开始进行 prepare 操作,进行解封装、解码和渲染,待首帧渲染完成后即可等待 play 指令播放。

  • 预渲染可以优化掉解封装、解码和渲染的耗时,但会给 CPU 和 GPU 带来额外消耗,需要根据机型性能选择性开启。

  • 如果同时开启了预加载,还需要进行策略优化,协调好预加载和预渲染的时机和流程。

8.2 预渲染首帧代替封面图

  • 使用播放器预渲染的首帧来代替传统的封面图,可以节省封面图下载的流量,降低下载封面图导致的带宽争抢。

  • 但需要有一个兜底策略,比如当预渲染未完成时,再选择加载封面图。

总结

结合公司实力进行优化,优先考虑缓存提速比如dns http这类连接耗时,tcp改进为fast open,cdn gop缓存机制这个较为重要。其次ijkplay源码,流媒体更改为格式固定版本,去除协议探测耗时。其次考虑源码参数,比如播放器缓存队列启动大小,引入池化技术。

参考文献

播放器秒开优化丨音视频工业实战-腾讯云开发者社区-腾讯云

学习资料分享

0voice · GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值