百度APP视频播放中的解码优化

2023-11-02

背景

在全民视频的时代,百度APP中视频播放是十分重要的业务。随着 5G 的到来,视频播放已经不满足以前的标清/高清,超清乃至于 4K 已经是旧时王谢堂前燕飞入寻常百姓家。越来越清晰的视频源,越来越复杂的视频编码,对 APP 的视频解码能力也有越来越高的要求。
与此同时,大家的手机性能越来越好,很多手机都逐步提供了强悍的硬件解码能力;而软件解码发展多年,也有其不可替代的优势。所以,如何合理利用手机的软/硬件解码能力,充分发挥其各自优势,为用户们提供更加优质的视频播放体验,就成为了我们重点优化的方向。

丨软/硬件解码优缺点

解码器有两种模式:软件解码与硬件解码。
软件解码目前业界有比较成熟的 FFmpeg ,利用 CPU 进行解码。
硬件解码发展起步较晚,在 Android 手机上,利用专用解码芯片进行解码。系统提供 MediaCodec ,用于访问底层硬件解码器。

事物都有两面性,两种解码模式各有优缺点,在很多播放器中,两种模式并存。软硬解码的优缺点,对音视频开发者其实算老生常谈了。

说明:在 Android 上,MediaCodec 更加具体来说,是 Google 提供的一套框架,因为各个芯片厂商,手机厂商实现差异,所以经常出现兼容性问题。另外 MediaCodec 的初始化流程长,且一些手机上,需要内部缓存多个帧后才对外输出第一个帧,这两个因素导致硬解在首帧解码速度上明显比软解慢。
丨效率对比

  1. 软件解码:使用 FFmpeg ,解码后得到 YUV 数据,需要通过 libyuv 转换为 RGB ,渲染上屏。

  2. 硬解码 buffer 模式:使用 MediaCodec ,解码后从 buffer 中得到 YUV 数据,需要通过 libyuv 转换为 RGB ,渲染上屏。

  3. 硬解码 surface 模式:使用 MediaCodec ,官方说明中 surface 模式为最高效的模式:解码时绑定 surface ,解码后可通过系统 API 直接上屏到 surface。

首帧解码耗时线上统计:

⬆️(百度APP版本V11.20.0.14,数据日期:2020年03月20日)

解码帧率和 CPU 占用统计需要进行压测(不进行音视频同步,完全放开解码性能),以下这两项采用线下测试数据。测试源:4K HEVC ,测试机魅族 16th。

说明:解码帧率越高,表示1秒内解码帧数越多,单帧解码耗时更少,性能更高。

模式
软解码 硬解码 buffer 模式 硬解码 surface 模式
帧率
29.4fps 55.0fps 58.8fps
CPU 占用(峰值) 79%
23%
12%
CPU 如下图:

由此可以看出,在视频播放上,MediaCodec surface 模式是效率最高的模式,既充分利用了硬解码的优势,又因为系统直接上屏降低了数据拷贝和 YUV 转换 RGB 的耗时,有效降低了 CPU 负载和对内存的消耗。

丨痛点

综上所述,在视频播放中,理想状态是尽可能地去用硬解码 surface 模式,其次是使用硬解码 buffer 模式,最后再考虑软解码。但同时需要兼顾首屏解码速度,硬解码的机型兼容性,在这些场景下需要优先使用软解码。

对此,我们需要解决以下痛点:

1. 怎么完善硬解码的兼容性判断?

2. 怎么在保证首屏解码速度的情况下,尽可能使用硬件解码?

我们的方案

痛点 1:怎么完善硬解码的兼容性判断?

主流做法:线下测试各种机型硬件解码兼容性,维护静态硬件解码黑名单。

劣势:测试人力成本高,且线下测试很难 cover 线上多种机型;手机型号不断迭代,这种方式,无法保证新的异常机型及时拉黑。

我们的方案 1:在静态硬件解码黑名单机制上,增加解码器监控。

痛点 2:怎么在保证首屏解码速度的情况下,尽可能使用硬件解码?

主流做法:需要保障解码效率的播放场景选择硬件解码,需要保证首屏解码速度则选择软件解码。

劣势:选择软件解码的场景,无法充分发挥手机硬件解码的优势。

我们的方案 2:

划定首屏解码耗时阈值,例如 200ms 。

从解码器监控模块中获取历史硬解码首屏耗时进行预测,若低于阈值,直接使用 MediaCodec surface 模式;高于阈值,使用软解起播,中途无缝切换为 MediaCodec buffer 模式。

如上述,下文具体介绍这 2 个方案。

丨方案 1:解码器监控

  1. 解码器监控模块设计

解码监控模块通过编码类型(H264/HEVC)& profile & level作为一个ID,记录各种编码方式源的软硬件解码情况;

说明:profile 指定视频的压缩率;level 指定分辨率、帧率和码率的。两者都是视频编码的重要特征。同一编码类型,解码器对不同级别的 profile/level 支持可能不同。

记录该编码方式中软硬件解码的首屏解码速度和平均解码速度。

针对硬件解码,还记录了硬件解码器是否存在崩溃;运行次数及运行期间出现异常的次数(包括解码接口抛异常;解码 block 等)。

刚安装百度APP的一段时间内,视频播放会随机使用软/硬件播放,用于采集该机器的解码器运行情况。

  1. 流程
    起播时,在 prepared 阶段,先通过静态硬解码黑名单,再细分到 编码类型 & profile & level,从解码监控模块看视频源编码方式硬解是否崩溃—>硬解是否异常过多,判断硬解码兼容性是否满足。

从解码监控模块获取当前视频源编码方式使用软硬解码首帧耗时进行首屏耗时的预测,硬解码满足特定首屏耗时时,优先使用硬解码,反之则选软件解码起播。

视频播放后,将本次的首帧解码耗时、解码器运行情况(是否崩溃、是否有异常、每帧平均耗时)更新到监控模块,用于下次播放预测。

对于硬件解码有崩溃、异常过多的情况,我们判定硬件解码存在兼容性问题,用软件解码播放完整个视频。

对于硬件解码首屏耗时超过阈值的,其实兼容性是OK的,那么在用软件解码快速起播后,我们可以用方案2进一步优化。

丨方案 2:软硬件解码器无缝切换

  1. 解码通路的统一
    为什么要统一?工欲善其事,必先利其器。

如果有一个统一的解码模块,封装软/硬解码器(包括三种解码方式),对外提供统一接入接口,那么对于 Player 仍然像使用一个普通解码器一样使用。在整个架构实现上更加合理,维护扩展也方便。

模块内部维护前后台解码器,内部状态,切换追帧等逻辑,对外无感。而I帧标识,可切换标识等,均可携带在pkt中传入,这样也不需要对解码模块增加一些解码无关的接口,接口设计更加合理。

  1. 解码器切换逻辑
    两种时机可以切换:1)播放解码到第二个 GOP;2)Player 发生 seek。

播放到第二个 GOP 切换:

播放开始时,解码模块内打开软解码器作为前台解码器;同时创建后台硬解线程,处于等待状态,并不会阻塞住前台解码任务。

Player(播放器)开始播放,把第一个 GOP 的 pkt(视频包)给解码模块,利用前台解码器(软解)的优势,快速解码首个视频帧用于渲染显示,实现快速起播。

4-5秒后,第二个 GOP 到来,pkt(视频包)携带可切换的flag通知解码模块,同时输入 GOP2 的多个 pkt 给硬解码器在后台解码,进入追帧状态。前台软解码器解码保持不变,输入一个 pkt ,解码一个帧。

当后台硬解码器 PTS 追上软解码器的 PTS ,即可关闭硬解码线程,前后台解码器切换。此过程需保证帧的连续,到达无缝切换,用户无感。

GOP2 的后续的 pkt 和后续的 GOP3/4/5……都会使用 MediaCodec buffer 模式。这样在利用软解保证首帧解码速度的同时,也最大限度的利用了 MediaCodec 的解码优势。

Player 发生 seek 切换:
这种场景逻辑比较简单,在 Player seek 时,需要调用 decoder 的 flush,我们趁此机会把前台解码器切换为硬解码器,后续一直用 MediaCodec buffer 模式即可。

  1. 保证解码器无缝切换

追帧和解码器切换过程中有两种情况:

(左图)GOP2 硬解码解码N帧后,才追上软解码,那么这些重复的 frame (灰色部分),需要进行丢弃,避免画面重复和回跳。

(右图)GOP2 硬解码解码第一帧,即已经追上软解码,那么必须填入空 pkt包,将软解码器内部缓存全部输出,避免画面跳变。

结语

目前百度APP Android端,在保障首屏速度和解码错误率没有退化的前提下,视频播放中硬件解码占比已达到 87%,如下:

在目前视频业务百花齐放的时代,编解码也在不断发展进步,各种新的编码方式层出不穷,端上也在这个方向上不断强化自身解码能力。解码作为视频播放中重要的一环,可以预见的是,后续我们仍会在端上解码不断进行探索、优化,为用户提供更优的体验。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

百度APP视频播放中的解码优化 的相关文章

随机推荐

  • LTP4.0 docker 安装使用说明;ltp工具包使用说明

    ltp4 0 6月份放出来了 一个模型进行多任务学习 立马测试了一下效果 确实不错 github链接 https github com HIT SCIR ltp 1 首先下载docker 使用pytorch1 4版本 python版本3 7
  • riscv-xv6单步调试5 锁与多核调度

    0 序 本次主要记录xv6锁的实现和在多核调度中的应用 同步与互斥 1 spinlock的实现 位于 spinlock h struct spinlock uint locked Is the lock held 0表示没被获取 1表示被某
  • 从一路高歌到遭多国“封杀”,ChatGPT未来将是什么样子?

    IT有得聊 是机械工业出版社旗下IT专业资讯和服务平台 致力于帮助读者在广义的IT领域里 掌握更专业 更实用的知识与技能 快速提升职场竞争力 点击蓝色微信名可快速关注我们 人工智能技术的发展已经逐渐改变了我们的生活和工作方式 其中 语言模型
  • 每日一题:子串的最大差

    子串的最大差 题目 Daimayuan Online Judge 枚举每个子串不现实 肯定会超时 子串1的最大值 子串1的最小值 子串2的最大值 子串2的最小值 即为 子串1的最大值 子串2的最大值 子串1的最小值 子串2的最小值 可以从贡
  • Linux环境下Selenium截图乱码及字体安装及与字符集区别

    概述 参考Java实现HTML页面截图功能 在使用Selenium对HTML页面进行截图时 一段没有任何问题的代码 在Windows环境下执行成功 但放到使用很久的测试环境Linux服务器也没有问题 但是部署到刚申请不久的阿里云生产Linu
  • yolov7 mask 使用学习笔记

    目录 yolov7 mask trt安装笔记 安装detectron方法1 OK 安装detectron方法2 pip install regex 4 1 无法找到头文件 math h fatal error C1083 安装cocoapi
  • React 16入门教程

    React 16入门教程 技术胖 讲的一般 React16 8版本 https jspang com posts 2019 05 04 new react base html E7 AC AC01 E8 8A 82 EF BC 9Areac
  • dinky报错:Communications link failure The last packet sent successfully to the server was 0 millisecon

    dinky链接MySQL报错 说明 MySQL版本是5 x版本 driver版本是 8 x版本 com mysql cj jdbc exceptions CommunicationsException Communications link
  • java对象序列化流设置类中的某个成员变量不参与序列化和反序列化操作

    我们用对象反序列化流输出类的name变量 然后我们给这个name变量定义一个transient关键字修饰 private transient String name 然后我们再次用反序列化读取 此时我们就不再能拿到name的值了 因为他已经
  • 项目部署到tomcat里css,js等文件失效

    项目在IDEA里面跑的好好的 到了tomcat里面样式就失效了 如何处理 其实这是我们开发中忽略了一个细节导致的 就是如果您的JavaWeb的模板使用的是thymeleaf模板的话 那么您在引入css或者js等文件的时候 一定要用下面的格式
  • h3c虚拟服务器无效,请教:H3C模拟器中防火墙无法PING通PC(虚拟主机) - H3C技术论坛 - 51CTO技术论坛_中国领先的IT技术社区...

    H3C网络模拟器版本 HCL 2 0 2 1 防火墙F1060 使用虚拟主机 PC 连接防火墙G1 0 1端口 防火墙G1 0 1端口IP地址 192 168 0 1 24 PC机端口IP地址 192 168 0 2 24 配置命令 sec
  • ros的入门知识

    ros的入门教程链接 linux ros安装 学前小案例 键盘控制小海龟的移动 创建工作空间与功能包 发布者publisher 订阅者subscriber ros的基础知识点 不同的linux系统安装的ros版本是不一样的 linux18
  • nmake简介

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 nmake学习初步 前言 一 nmake在哪里 二 Makefile编写 1 hello c实例代码 2 Makefile 3 扩展 前言 一直使用vs的IDE 最近编译sq
  • tomcat守护进程

    如何写一个linux系统下的tomcat守护进程呢 上菜 1 编写守护进程shell脚本 下面这个脚本可以直接拿过来用 只需要改URL 和 tomcat启动目录即可 bin bash echo Start URL http 127 0 0
  • 六、线性队列

    序言 结构图 队列结构 队列常用操作 队列的实现 序言 线性队列是用数组实现的队列 队列遵循的原则FIFO first in first out 通常我们说的线性队列 为了节省数组的空间使用 都是循环队列 结构图 队列结构 typedef
  • 【函数(上)(双重for循环)】

    1 循环 1 双重for循环 概述 循环嵌套是指在一个循环语句中再定义一个循环语法结构 例如 嵌套一个for循环 这样for循环语句我们称之为双重for循环 双重for循环语法 for 外循环的初始 外循环的条件 外循环的表达式 for 内
  • LoadOfTheRoot提权学习

    端口碰撞 端口试探 port knocking 是一种通过连接尝试 从外部打开原先关闭端口的方法 一旦收到正确顺序的连接尝试 防火墙就会动态打开一些特定的端口给允许尝试连接的主机 端口试探的主要目的是防治攻击者通过端口扫描的方式对主机进行攻
  • swagger3的配置和使用(一)

    目录 Swagger3简介 Swagger的组成 Swagger的Springboot配置 maven添加依赖 创建swagger的配置类 访问路径 application yml环境配置 API分组 Swagger常用注解 注解说明 用于
  • URLSearchParams快速解析URL查询参数

    浏览器 Window 内置的 URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串 再也不用 字符串分割的方式去解析 url query 参数了 一 URLSearchParams 构造函数 URLSearc
  • 百度APP视频播放中的解码优化

    背景 在全民视频的时代 百度APP中视频播放是十分重要的业务 随着 5G 的到来 视频播放已经不满足以前的标清 高清 超清乃至于 4K 已经是旧时王谢堂前燕飞入寻常百姓家 越来越清晰的视频源 越来越复杂的视频编码 对 APP 的视频解码能力