neteq的peak detector解析
正文
neteq中存在一个模块peakDetector用于做峰值检测,用于处理下述的场景,在突发抖动过程中,突发的高抖动不足以拉大直方图的97分位,但又持续了一小段时间需要响应处理, 还存在一定的周期行为,所以在neteq中额外增加了peakDetector,用于弥补直方图不足以对抗该场景的问题;
运行机制解析:
-
整体思想:
短时间内检测到多个(>=3)个iat过大的点时候,进入峰值状态, 将peak iat插入队列中,随着时间插值衰减peak iat,衰减到比较小的时候,退出峰值状态 -
名词解释:
峰值点间隔: peak_period,当前峰值点和上一个峰值点的时间间隔大小
峰值: height,大小为iat的大小
iat: inter_arrival_time, 包间传输抖动 -
peakDetector是如何检测到峰值发生的?
(数组中峰值点的数量>=2) 且 (计时器距离最后一个峰值点的发生时刻 <= 2*统计到的最大峰值间隔) -
生成峰值点的条件?
C1. iat > current_level + 200ms or iat > 2 * current_level的时认为可能有峰值
C2. C1.出现后短期内再次出现(10s内),认为确实检测到了peak点,这个时候将该采样点放入到数组中,将iat作为峰值以及和上一次c1发生的时间作为峰值间隔,重置计时器 -
一些额外的控制:
E1. 采样点的峰值间隔上限被限制成1s
E2. C2的检测中,C1出现后,没有在短期内(10s)出现的时候, 但中期出现了(20s)C1,重新开始C2
E3. C2的检测中,再次检测到尖峰点和上一次已经相隔太远(20s),清理整个数组, 重新开始c1
- 峰值状态如何衰减退出?
P1. 一旦处于峰值状态,(时间间隔超过统计的最大峰值间隔 且 最高峰值高于1.5iat),开始峰值衰减
P2. 峰值衰减 ,是通过不断增加新的平滑峰值点(peak_period为max(计时器, 800ms),峰值为0.5 x max_peak)到数组中,由于数组是定长(最大为8),采样点先入先出队列方式管理,第一轮最多8s后,max_peak会被降低0.5 同时max_peakPeriod会从最大1s被更新成最大800ms
P3. p2增加新的衰减峰值点的时候,计时器会被重置,当最高峰值不再高于1.5 x iat的时,p2没有新的衰减点增加的时候,计时器就会不断累加,直到达到2*统计到的最大峰值间隔后,系统退出峰值状态,
一个实际例子:
短时间内连续引入三个峰值后, 第一个峰值到达peakDetector后,开始计时峰值间隔,peakDetector中统计到两个峰值点,进入峰值状态,一个峰值为27 iat,一个为25 iat, 峰值间隔由于都超过了1s,所以被设置成了最大的1s
处于峰值状态后,但随后没有峰值点了,会开始进行折半插值,并通过先入先出的方式,将起初的最大峰值点给排出队列
整体上就是折半衰减,经历的周期分别为8s (1000ms * 8) -> 6s (800ms * 8) -> 6s (800ms *8) -> 6s (800 * ms), 最终最大峰值衰减到1的时候,无法高于1.5倍current_level, 不会再进行插值加入新的峰值点,计时器无法被重置,计时器超过2s后,认为不再处于峰值范围
2.2 源码解析
a) 当interval_arrival_iat_packet 超过 target_level + 2 || interval_arrival_iat_packet > 2 * target_level的时候,认为这是一个峰值点
b) 记录该峰值点的高度(iat_packet)和该峰值点离上一个峰值点的间隔(period), 然后放入记录数组
bool DelayPeakDetector::Update(uint16_t seq, int inter_arrival_time, bool reordered, int target_level) {
current_iat_ = inter_arrival_time;
iat_count++;
if (reordered) {
reordered_count++;
}
if (inter_arrival_time > max_peak_) {
max_peak_ = inter_arrival_time;
}
if (ignore_reordered_packets_ && reordered) {
return CheckPeakConditions();
}
int peak_height = -1;
if (inter_arrival_time > target_level + peak_detection_threshold_ || inter_arrival_time > 2 * target_level) {
// A delay peak is observed.
if (!peak_period_stopwatch_) {
// This is the first peak. Reset the period counter.
peak_period_stopwatch_ = tick_timer_->GetNewStopwatch();
} else if (peak_period_stopwatch_->ElapsedMs() > 0) {
if (peak_period_stopwatch_->ElapsedMs() <= kMaxPeakPeriodMs) {
// This is not the first peak, and the period is valid.
// Store peak data in the vector.
peak_count++;
// 记录该峰值点的高度(iat_packet)和该峰值点离上一个峰值点的间隔(period), 然后放入记录数组
Peak peak_data;
peak_data.period_ms = peak_period_stopwatch_->ElapsedMs() > 1000 ? 1000 : peak_period_stopwatch_->ElapsedMs();
peak_data.peak_height_packets = inter_arrival_time;
peak_history_.push_back(peak_data);
peak_height = peak_data.peak_height_packets;
//LOGD("peak detect height %d,period %d", inter_arrival_time, peak_period_stopwatch_->ElapsedMs());
while (peak_history_.size() > kMaxNumPeaks) {
// Delete the oldest data point.
peak_history_.pop_front();
}
peak_period_stopwatch_ = tick_timer_->GetNewStopwatch();
} else if (peak_period_stopwatch_->ElapsedMs() <= 2 * kMaxPeakPeriodMs) {
// Invalid peak due to too long period. Reset period counter and start
// looking for next peak.
peak_period_stopwatch_ = tick_timer_->GetNewStopwatch();
} else {
// More than 2 times the maximum period has elapsed since the last peak
// was registered. It seams that the network conditions have changed.
// Reset the peak statistics.
Reset();
}
}
}
// LOGD("[Neteq] iat-peak seq %d,iat %d,peak %d", seq, inter_arrival_time, peak_height);
MakeSmoothPeak();
return CheckPeakConditions();
}
c) 当记录数组中出现了峰值点>2个,且此时离峰值点间隔没有超过 2倍的峰值点间隔,则认为还处于检测到峰值点的状态
bool DelayPeakDetector::CheckPeakConditions() {
size_t s = peak_history_.size();
if (s >= kMinPeaksToTrigger && peak_period_stopwatch_->ElapsedMs() <= 2 * MaxPeakPeriod()) {
peak_found_ = true;
} else {
peak_found_ = false;
}
return peak_found_;
}
d) 每个采样点进来后,会不断判断记录数组中的max_height和当前进来的点的iat,如果进来的采样点的iat比较小,会对记录数组做插值插入新的峰值点,本质是对峰值点做平滑衰减,将peak渐渐平滑下来,知道平滑到接近当前的iat水平后,记录数组不会在加入新的值,就会出发c)的逻辑判断,认为此时不再处于峰值范围
void DelayPeakDetector::MakeSmoothPeak() {
if (peak_found_) {
if (peak_period_stopwatch_->ElapsedMs() >= MaxPeakPeriod()) {
int maxPeakHeight = MaxPeakHeight();
if (maxPeakHeight > (current_iat_ * 3 / 2) && current_iat_ != 0) {
// 不断的对历史最高峰值点进行平滑衰减,插入新的峰值点
// 直到不在高于 1.5的当前iat,既认为接近当前的iat时候,不再插值
// peak_period_stopwatch_将不再更新,则CheckPeakConditions()认为
// 不再判定最近有峰值点,脱离峰值状态
Peak peak_data;
peak_data.period_ms = peak_period_stopwatch_->ElapsedMs() >= 800 ? 800 : peak_period_stopwatch_->ElapsedMs();//todo 800?hardcode
peak_data.peak_height_packets = maxPeakHeight * 1 / 2;
peak_history_.push_back(peak_data);
// LOGD("[DelayPeakDetector] MakeAutoSmoothPeak peak %d,make peakHeight %d,period %d, ElapsedMs %d",
// maxPeakHeight,
// peak_data.peak_height_packets,
// peak_data.period_ms,
// peak_period_stopwatch_->ElapsedMs());
while (peak_history_.size() > kMaxNumPeaks) {
// Delete the oldest data point.
peak_history_.pop_front();
}
peak_period_stopwatch_ = tick_timer_->GetNewStopwatch();
}
}
}
}
3. 局限性
虽然peakDetector 和 inter-arrival time已经被relative_time替代,但还是可以研究局限性
1.由于输入是iat, 对于拥塞延迟,突发延迟拥塞情况下,iat只有起初一个点是峰值,peakDetector无法检测到该场景为峰值场景,突发的延迟拥塞退避的快的话
2.仔细观察smooth peak的这个条件, 要求current_iat_ != 0, 但如果current_iat = 0 持续两秒导致没有进行新的peak点的插值,会导致比较快退出peak状态,不知道官方是否刻意而为之