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