自创视频解码前丢帧的“挂档算法”

2024-01-14 15:20
文章标签 算法 视频 解码 自创

本文主要是介绍自创视频解码前丢帧的“挂档算法”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、察看播放器当前运转情况,主要判断依据是DECODER之前拿到的FRAME时间戳,与当前播放时间相减,得到的提前量。这个提前量就表示了播放器卡或者不卡。比如我这里放行几帧之后,DECODER解得比较吃力,于是DECODER线程抢占了DEMUX线程的时间片,那么下面几帧到来的提前量会更小了。

1、汽车加速是一个渐变过程,不可能直接从1档换到4档跳变。同样地,播放器的运行状态也应该理解成一个渐变过程,尤其是对于CBR恒定码率的播放。如果做成只根据当前帧的时间提前量来判断的跳变,可以预想到会出现这样的情况;在高码率上,一开始由于解码来不及,我立刻丢帧,连丢若干帧之后,下面几帧到达DECODER的时间大大提前,所以放行,结果DEOCDER连续解这几帧显得非常吃力,并且挤占了其他线程的时间片,播放器进入最恶劣的情况,于是音频就断了;下几个视频帧到达时判断状态不佳,被连续丢弃,由于连续丢弃造成时间空闲,那么再下几帧的到达时间又大大提前……播放器就进入良性-恶性的不断摇摆中,音频还是要断的

2、汽车的刹车应该理解为一个跳变过程。事实上只能说是比较快的渐变,但我们总希望刹车越灵越好,最好直接从140kmh直接跳到0kmh,这样的车才是最安全的。播放器上面一样,一旦播放情况恶化,希望它能立刻作出反应,把当前到达DECODER的帧丢弃,以在最短时间内改善播放性能,否则只要恶化的情况持续片刻,比如几百毫秒,那么脆弱的音频就会断掉了。

3、两档之间应该有个用来确认加速有效的时间段,汽车不可能挂了二档后立刻就挂三档,而是应该在挂二档后确认一下这次提速是安全的,然后看看是否还有提速的空间,再挂三档。

4、车能开多快,应该是看当前路况上还有多少提速空间,而不是当前行驶速度。车速和路况有一个“最佳适合点”。比如我现在开到80kmh,和路况正好适合,那么就没必要提速到90kmh,如果一提速,可能就要撞车了。同样地,比如视频帧到达时间提前了300ms, 这是一个比较合适的提前量区间,如果我认为既然可以提前到300ms,那不妨放行更多帧去解码,那么很可能你会在2秒后看到提前量已经降到了50ms,这时就必须丢弃更多的帧来调整了。

5、越低的档,其加速过程越短;比如从1档加速到2档只需要很短的时间;而从4档换到5档,就需要比较长的加速时间。播放器上面也一样,在放行更多帧之前,需要越来越长的时间用以观察是否符合提档的标准。

说了这么多,我还是贴上代码和注释吧。

  1 None.gif bool JudgeDrop(UINT16 FrameSeqNum, s64 FrameTimestamp)   // FrameSeqNum是当前帧在整个视频文件中处于第几帧的序号,FrameTimestamp是当前帧的时间戳
  2 ExpandedBlockStart.gif {
  3InBlock.gif    static int DropLevel = 5;  //我设置从1档到5档,5档是播放性能最快的情况,所有帧全部放行。比如解QVGA的视频时,就能走到最快档。我们以放行所有帧开局
  4InBlock.gif    static unsigned int DropInertia = 0;  //掉帧的惯性。每换一次档,需要观察DropInertia数量的帧后,才能再次决定是否换档。阀值越高播放器越稳定,适合CBR,阀值越低则播放器越灵敏,适合VBR,具体阀值的设置就凭经验了,对不同平台显然是不同的。
  5InBlock.gif    static s64 TotalSkew = 0;  //在DropInertia数量的帧里,统计总的时间提前量
  6InBlock.gif    static int CanKickUp = 0;  //必须CanKickUp次数地证明可以挂更高档后,才允许升档。这时用来实现如上面第5点描述的,后面可以看到CanKickUp的值和当前档数成正比
  7InBlock.gif    s64 skew;    
  8InBlock.gif
  9InBlock.gif#define UP_SHIFT         400000  //提前400毫秒才可以证明当前播放足够流畅,满足提档要求
 10InBlock.gif#define NORMAL_SHIFT         300000  //正常播放时提前大概300毫秒。如果提前量落在300毫秒~400毫秒,那就不用提档了。
 11InBlock.gif#define WORSE_SHIFT         250000  //情况不太好咯,需要丢帧咯
 12InBlock.gif#define WORST_SHIFT        200000    //吃力啊,多丢一些
 13InBlock.gif#define DEAD_SHIFT        150000    //去死吧,彻底搞不定
 14InBlock.gif#define CAN_KCIKUP_TIMES     2    //CanKickUp和当前档数的比值。这个CAN_KCIKUP_TIMES越高,提速越不容易
 15InBlock.gif
 16InBlock.gif
 17InBlock.gif
 18InBlock.gif
 19InBlock.gif    if( FrameSeqNum > pRvInfoM->ufFramesPerSecond  )
 20ExpandedSubBlockStart.gif    {
 21InBlock.gif        skew = FrameTimestamp - (pClkM->GetTimeMicroseconds()); //计算当前到达帧和当前播放时间的提前量
 22InBlock.gif        TotalSkew += skew;    //加到累计提前量里头
 23InBlock.gif        DropInertia++;        //掉帧的惯性。
 24ExpandedSubBlockEnd.gif    }
 25InBlock.gif    else    //第一秒里的帧全部放行,我们先试探一下能放到什么程度。另一方面,第一帧到达frame render后,才能知道播放时间。
 26ExpandedSubBlockStart.gif    {
 27InBlock.gif        return false;
 28ExpandedSubBlockEnd.gif    }
 29InBlock.gif
 30InBlock.gif    //printf("skew = %lld, DropInertia = %d\n", skew, DropInertia);
 31InBlock.gif
 32InBlock.gif    if( DropInertia >= (pRvInfoM->ufFramesPerSecond >> 1) )  //我们已经统计了半秒钟内所有帧的提前量情况,应该判断是否挂档了
 33ExpandedSubBlockStart.gif    {
 34InBlock.gif        int DestLevel;
 35InBlock.gif        s64 AverageSkew = TotalSkew / DropInertia;    //取得平均提前量
 36InBlock.gif
 37InBlock.gif        //printf("AverageSkew = %lld\n", AverageSkew);
 38InBlock.gif
 39InBlock.gif        TotalSkew = 0;      //完成任务,让统计提前量的工作,从下一帧重新开始
 40InBlock.gif        DropInertia = 0;  //同上
 41InBlock.gif
 42InBlock.gif        if( AverageSkew > UP_SHIFT )   //真强,播放够快
 43ExpandedSubBlockStart.gif        {
 44InBlock.gif            DestLevel = DropLevel + 1;  //我们可以考虑提一档
 45ExpandedSubBlockEnd.gif        }
 46InBlock.gif        else if( AverageSkew < DEAD_SHIFT )  //最慢最慢的情况了,慢得跟蜗牛似的
 47ExpandedSubBlockStart.gif        {
 48InBlock.gif            DestLevel = 1;        //去死吧,挂最低档,除了参考帧和关键帧,全部给我扔掉
 49ExpandedSubBlockEnd.gif        }
 50InBlock.gif        else if( AverageSkew < WORST_SHIFT )  //不是吧,这么慢
 51ExpandedSubBlockStart.gif        {
 52InBlock.gif            DestLevel = 2;        //丢掉1/2的帧数
 53ExpandedSubBlockEnd.gif        }
 54InBlock.gif        else if( AverageSkew < WORSE_SHIFT )  //偏慢了
 55ExpandedSubBlockStart.gif        {
 56InBlock.gif            DestLevel = 3;        //丢掉1/3的帧看看
 57ExpandedSubBlockEnd.gif        }
 58InBlock.gif        else if( AverageSkew < NORMAL_SHIFT )
 59ExpandedSubBlockStart.gif        {
 60InBlock.gif            DestLevel = 4;        //没事丢帧玩,丢个1/4意思意思
 61ExpandedSubBlockEnd.gif        }
 62InBlock.gif        else  // NORMAL_SHIFT ~ UP_SHIFT,正常播放性能
 63ExpandedSubBlockStart.gif        {
 64InBlock.gif            DestLevel = DropLevel;    //维持现有档数,不换了。
 65ExpandedSubBlockEnd.gif        }
 66InBlock.gif
 67InBlock.gif        if( DestLevel > DropLevel )  //情况比现在要好些,考虑提档
 68ExpandedSubBlockStart.gif        {
 69InBlock.gif            CanKickUp++;    //你证明了自己的实力,不错不错,但要继续证明实力才可以。
 70InBlock.gif
 71InBlock.gif            if( CanKickUp > CAN_KCIKUP_TIMES * DropLevel )  //恭喜恭喜,终于可以提档了。在这里可以看到换越高档越难。1到2档只要连续达到提档条件2次,2到3需要达到条件4次,3到4需要6次,4到5需要8次。而连续8次统计就是需要4秒的时间,其间只要有一次没达到提档标准,就重新来过,要求很严格哦。
 72ExpandedSubBlockStart.gif            {
 73InBlock.gif                CanKickUp = 0;  //准备计算下一次提档条件
 74InBlock.gif                DropLevel++;  //修成正果,提档!
 75ExpandedSubBlockEnd.gif            }
 76InBlock.gif            
 77InBlock.gif            //printf("new drop level = %d\n", DropLevel);
 78ExpandedSubBlockEnd.gif        }
 79InBlock.gif        else if( DestLevel < DropLevel ) //不行嘛,菜菜的,要降级咯
 80ExpandedSubBlockStart.gif        {
 81InBlock.gif            CanKickUp = 0;  //不论前面满足提档标准多少次,只要一次不成,就重新来过。
 82InBlock.gif            DropLevel = DestLevel; //刹车是跳变
 83InBlock.gif            //printf("new drop level = %d\n", DropLevel);
 84ExpandedSubBlockEnd.gif        }
 85InBlock.gif        else
 86ExpandedSubBlockStart.gif        {
 87InBlock.gif            CanKickUp = 0;  //DestLevel == DropLevel, 情况正常,没必要给你提档了。
 88ExpandedSubBlockEnd.gif        }
 89InBlock.gif
 90ExpandedSubBlockEnd.gif    }
 91InBlock.gif
 92InBlock.gif
 93InBlock.gif    if(    ( DropLevel >= 2 )&& ( DropLevel <= 4 ) &&  //2~4档都需要丢帧的
 94InBlock.gif        ( FrameSeqNum % DropLevel == 0 ) &&    //根据帧的序号抽取这几分几的帧来丢弃
 95InBlock.gif        (!pDecodeM->bInputFrameIsReference )  ) //如果是参考帧,比如B帧,那是不能丢的,不然后面的B帧全部解不出来了
 96ExpandedSubBlockStart.gif    {
 97InBlock.gif        //printf("drop frame seq = %d, DropLevel = %d\n", FrameSeqNum, DropLevel);
 98InBlock.gif        return true;  //是的,丢!
 99ExpandedSubBlockEnd.gif    }
100InBlock.gif    else if( ( ( DropLevel == 1 ) || ( skew < 0 )) &&  //在最坏情况上,或者当前帧到达时已经延迟了,那么就丢弃除了参考帧外其他的所有帧
101InBlock.gif             (!pDecodeM->bInputFrameIsReference ) )
102ExpandedSubBlockStart.gif    {
103InBlock.gif        return true;  //
104ExpandedSubBlockEnd.gif    }
105InBlock.gif    else
106ExpandedSubBlockStart.gif    {
107InBlock.gif        return false//真不容易,通过了严格的盘查,放行~~
108ExpandedSubBlockEnd.gif    }
109InBlock.gif
110ExpandedBlockEnd.gif}
111 None.gif


这段代码的运行效果良好,让video decoder腾出更多时间来给audio decoder运作,保证了音频输出的流畅连贯。


本文转自Walzer博客园博客,原文链接:http://www.cnblogs.com/walzer/archive/2007/05/01/733916.html,如需转载请自行联系原作者


这篇关于自创视频解码前丢帧的“挂档算法”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_34074740/article/details/90088290
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/605621

相关文章

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

用js控制视频播放进度基本示例代码

《用js控制视频播放进度基本示例代码》写前端的时候,很多的时候是需要支持要网页视频播放的功能,下面这篇文章主要给大家介绍了关于用js控制视频播放进度的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言html部分:JavaScript部分:注意:总结前言在javascript中控制视频播放

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1