webrtc视频接收端带宽预测(二)---卡尔曼滤波

本文主要参考文章 http://www.xuebuyuan.com/1248366.html
接收端估计结合文档http://tools.ietf.org/html/draft-alvestrand-rtcweb-congestion-02以及源码webrtc/modules/remote_bitrate_estimator/overuse_detector.cc
添加了一些自己的理解。

webrtc中的带宽自适应算法分为两种:
1、发端带宽控制 
原理是由rtcp中的丢包统计来动态的增加或减少带宽,在减少带宽时使用TFRC算法来增加平滑度。
2、收端带宽估算
原理是并由收到rtp数据,估出带宽;用卡尔曼滤波,对每一帧的发送时间和接收时间进行分析,从而得出网络带宽利用情况,修正估出的带宽。

两种算法相辅相成,收端将估算的带宽发送给发端,发端结合收到的带宽以及丢包率,调整发送的带宽。

下面具体分析两种算法:


2 接收端带宽估算算法分析

Each frame is assigned a receive time t(i), which corresponds to the
time at which the whole frame has been received (ignoring any packet
losses). A frame is delayed relative to its predecessor if t(i)-t(i-
1)>T(i)-T(i-1), i.e., if the arrival time difference is larger than
the timestamp difference.
We define the (relative) inter-arrival time, d(i) as
d(i) = t(i)-t(i-1)-(T(i)-T(i-1))
Since the time ts to send a frame of size L over a path with a
capacity of C is roughly
ts = L/C
we can model the inter-arrival time as

d(i) = (L(i)-L(i-1))/C+ w(i) = dL(i)/C+w(i)

带宽估算模型:

d(i) = dL(i) / c + w(i) 

其中,d(i)两帧数据的网络传输时间差,dL(i)两帧数据的大小差, c为网络传输能力, w(i)是我们关注的重点, 它主要由三个因素决定:发送速率, 网络路由能力, 以及网络传输能力。w(i)符合高斯分布, 有如下结论:当w(i)增加是,占用过多带宽(over-using);当w(i)减少时,占用较少带宽(under-using);为0时,用到恰好的带宽。所以,只要我们能计算出w(i),即能判断目前的网络使用情况,从而增加或减少发送的速率。

可进一步表示成

 d(i) = dL(i) / C(i) + m(i) + v(i) 

其中,dL(i)两帧数据的大小差, c为网络传输能力,m(i)为网络抖动(可能大于0:比之前的网络延迟增大;也可以小于0:比之前的网络延迟减小),v(i)为测量误差

kalman-filters算法原理:
  • theta_hat(i) = [1/C_hat(i) m_hat(i)]^T // i时间点的状态由C, m共同表示,theta_hat(i)即此时的估算值
  • z(i) = d(i) - h_bar(i)^T * theta_hat(i-1) //d(i)为测试值,可以很容易计算出, 后面的可以认为是d(i-1)对d(i)的估算值, 因此z(i)就是d(i)的偏差,即residual
  • theta_hat(i) = theta_hat(i-1) + z(i) * k_bar(i) //好了,这个就是我们要的结果,关键是k值的选取,下面两个公式即是取k值的。最优估计值
  • k_bar(i) = (E(i-1) * h_bar(i)) / (var_v_hat + h_bar(i)^T * E(i-1) * h_bar(i))//k_bar(i)为卡尔曼增益,(var_v_hat + h_bar(i)^T * E(i-1) * h_bar(i))即为代码中的denom
  • E(i) = (I - K_bar(i) * h_bar(i)^T) * E(i-1) + Q(i) // h_bar(i)由帧的数据包大小算出,E(i)为估计误差协方差阵, Q(i)为过程噪声协方差
    由此可见,需要设置初始状态、非测量值的变量有E(0),Q(1),C_hat(0),m_hat(0),
    我们只需要知道当前帧的长度,发送时间,接收时间以及前一帧的状态,就可以计算出网络使用情况。

结合卡尔曼滤波的数学公式,可以看出基本匹配。

Paste_Image.png

接下来具体看一下代码

void OveruseDetector::UpdateKalman(int64_t t_delta,  
                                   double ts_delta,  
                                   uint32_t frame_size,  
                                   uint32_t prev_frame_size) {  
  const double min_frame_period = UpdateMinFramePeriod(ts_delta);  //最近60帧中,发送间隔最小值
  const double drift = CurrentDrift();  
  // Compensate for drift  
  const double t_ts_delta = t_delta - ts_delta / drift;  //即d(i)  
  double fs_delta = static_cast<double>(frame_size) - prev_frame_size;   
  
  // Update the Kalman filter  
  const double scale_factor =  min_frame_period / (1000.0 / 30.0);  
  E_[0][0] += process_noise_[0] * scale_factor;  //过程噪声协方差
  E_[1][1] += process_noise_[1] * scale_factor;  
  
  if ((hypothesis_ == kBwOverusing && offset_ < prev_offset_) ||  
      (hypothesis_ == kBwUnderusing && offset_ > prev_offset_)) {  
    E_[1][1] += 10 * process_noise_[1] * scale_factor;  
  }  
  
  const double h[2] = {fs_delta, 1.0}; //即h_bar  
  const double Eh[2] = {E_[0][0]*h[0] + E_[0][1]*h[1],  
                        E_[1][0]*h[0] + E_[1][1]*h[1]};  
  
  const double residual = t_ts_delta - slope_*h[0] - offset_; //即z(i), slope为1/C  
  
  const bool stable_state =  
      (BWE_MIN(num_of_deltas_, 60) * fabsf(offset_) < threshold_);  
  // We try to filter out very late frames. For instance periodic key  
  // frames doesn't fit the Gaussian model well.  
  if (fabsf(residual) < 3 * sqrt(var_noise_)) {  
    UpdateNoiseEstimate(residual, min_frame_period, stable_state);  
  } else {  
    UpdateNoiseEstimate(3 * sqrt(var_noise_), min_frame_period, stable_state);  
  }  
  
  const double denom = var_noise_ + h[0]*Eh[0] + h[1]*Eh[1];  
  
  const double K[2] = {Eh[0] / denom,  
                       Eh[1] / denom}; //即k_bar  
  
  const double IKh[2][2] = {{1.0 - K[0]*h[0], -K[0]*h[1]},  
                            {-K[1]*h[0], 1.0 - K[1]*h[1]}};  
  const double e00 = E_[0][0];  
  const double e01 = E_[0][1];  
  
  // Update state  
  E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1];  
  E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1];  
  E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1];  
  E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1];  
  
  // Covariance matrix, must be positive semi-definite  
  assert(E_[0][0] + E_[1][1] >= 0 &&  
         E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 &&  
         E_[0][0] >= 0);  
  
  
  slope_ = slope_ + K[0] * residual; //1/C  
  prev_offset_ = offset_;  
  offset_ = offset_ + K[1] * residual; //theta_hat(i)  
  
  Detect(ts_delta);  
}  
更新噪声估计

The variance var_v = sigma(v,i)^2 is estimated using an exponential
averaging filter, modified for variable sampling rate

var_v_hat = beta*sigma(v,i-1)^2 + (1-beta)*z(i)^2
beta = (1-alpha)^(30/(1000 * f_max))

where

f_max = max {1/(T(j) - T(j-1))}

for j in i-K+1...i is the highest rate at which frames have been captured by the camera the last K frames and alpha is a filter coefficient typically chosen as a
number in the interval [0.1, 0.001]. Since our assumption that v(i)
should be zero mean WGN is less accurate in some cases, we have
introduced an additional outlier filter around the updates of
var_v_hat. If z(i) > 3 var_v_hat the filter is updated with 3
sqrt(var_v_hat) rather than z(i). For instance v(i) will not be
white in situations where packets are sent at a higher rate than the
channel capacity, in which case they will be queued behind each
other.

结合代码可见,

var_v_hat = beta*sigma(v,i-1)^2 + (1-beta)*z(i)^2

并没有用z(i),而是用avg_noise_ -z(i)。

If z(i) > 3 var_v_hat the filter is updated with 3
sqrt(var_v_hat) rather than z(i)

这句话的实现内容如下:

  if (fabsf(residual) < 3 * sqrt(var_noise_)) {  
    UpdateNoiseEstimate(residual, min_frame_period, stable_state);  
  } else {  
    UpdateNoiseEstimate(3 * sqrt(var_noise_), min_frame_period, stable_state);  
  }  

可见,webrtc中使用的卡尔曼滤波器是一个自适应的滤波器,即噪声方差自适应变化。

void OveruseEstimator::UpdateNoiseEstimate(double residual,//测量值和预测值之差
                                           double ts_delta,//最小采样间隔
                                           bool stable_state) {
  if (!stable_state) {
    return;
  }
  // Faster filter during startup to faster adapt to the jitter level
  // of the network. |alpha| is tuned for 30 frames per second, but is scaled
  // according to |ts_delta|.
  double alpha = 0.1;// [0.1, 0.001]
  if (num_of_deltas_ > 10*kFrameNumberPerSec) {
    alpha = 0.001;
  }
  // Only update the noise estimate if we're not over-using. |beta| is a
  // function of alpha and the time delta since the previous update.
  const double beta = pow(1 - alpha, ts_delta * kFrameNumberPerSec/ 1000.0);
  avg_noise_ = beta * avg_noise_
              + (1 - beta) * residual;
  var_noise_ = beta * var_noise_
              + (1 - beta) * (avg_noise_ - residual) * (avg_noise_ - residual);
  if (var_noise_ < 1) {
    var_noise_ = 1;
  }
}
过载检测

The offset estimate m(i) is compared with a threshold gamma_1. An
estimate above the threshold is considered as an indication of over-
use. Such an indication is not enough for the detector to signal
over-use to the rate control subsystem. Not until over-use has been
detected for at least gamma_2 milliseconds and at least gamma_3
frames, a definitive over-use will be signaled. However, if the
offset estimate m(i) was decreased in the last update, over-use will
not be signaled even if all the above conditions are met. Similarly,
the opposite state, under-use, is detected when m(i) < -gamma_1. If
neither over-use nor under-use is detected, the detector will be in
the normal state.
评价:思想正确,但是在具体实现要按实际设置gamma_1,gamma_2,gamma_3。
以及其他状态的阀值。

BandwidthUsage OveruseDetector::Detect(double ts_delta) {  
 if (num_of_deltas_ < 2) {  
   return kBwNormal;  
 }  
 const double T = BWE_MIN(num_of_deltas_, 60) * offset_; //即gamma_1  
 if (fabsf(T) > threshold_) {  
   if (offset_ > 0) {  
     if (time_over_using_ == -1) {  
       // Initialize the timer. Assume that we've been  
       // over-using half of the time since the previous  
       // sample.  
       time_over_using_ = ts_delta / 2;  
     } else {  
       // Increment timer  
       time_over_using_ += ts_delta;  
     }  
     over_use_counter_++;  
     if (time_over_using_ > kOverUsingTimeThreshold  //kOverUsingTimeThreshold是gamma_2, gamama_3=1  
         && over_use_counter_ > 1) {  
       if (offset_ >= prev_offset_) {  
         time_over_using_ = 0;  
         over_use_counter_ = 0;  
         hypothesis_ = kBwOverusing;  
       }  
     }  
   } else {  
     time_over_using_ = -1;  
     over_use_counter_ = 0;  
hypothesis_ = kBwUnderusing;  
   }  
 } else {  
   time_over_using_ = -1;  
   over_use_counter_ = 0;  
   hypothesis_ = kBwNormal;  
 }
UpdateThreshold(T, now_ms);  
return hypothesis_;  
}  
更新阀值
void OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) {
  if (!in_experiment_)
    return;

  if (last_update_ms_ == -1)
    last_update_ms_ = now_ms;

  if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) {
    // Avoid adapting the threshold to big latency spikes, caused e.g.,
    // by a sudden capacity drop.
    last_update_ms_ = now_ms;
    return;
  }

  const double k = fabs(modified_offset) < threshold_ ? k_down_ : k_up_;
  const int64_t kMaxTimeDeltaMs = 100;
  int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);
  threshold_ +=
      k * (fabs(modified_offset) - threshold_) * time_delta_ms;

  const double kMinThreshold = 6;
  const double kMaxThreshold = 600;
  threshold_ = std::min(std::max(threshold_, kMinThreshold), kMaxThreshold);

  last_update_ms_ = now_ms;
}

注意:

threshold_ 和offset之前是一种相互影响的关系。

码率控制

Rate control
The rate control at the receiving side is designed to increase the
receive-side estimate of the available bandwidth A_hat as long as the
detected state is normal. Doing that assures that we, sooner or
later, will reach the available bandwidth of the channel and detect
an over-use.
As soon as over-use has been detected the receive-side estimate of
the available bandwidth is decreased. In this way we get a recursive
and adaptive estimate of the available bandwidth.
In this document we make the assumption that the rate control
subsystem is executed periodically and that this period is constant.
The rate control subsystem has 3 states: Increase, Decrease and Hold.
"Increase" is the state when no congestion is detected; "Decrease" is
the state where congestion is detected, and "Hold" is a state that
waits until built-up queues have drained before going to "increase"
state.
The state transitions (with blank fields meaning "remain in state") are:

Paste_Image.png

参考文档:

http://www.swarthmore.edu/NatSci/echeeve1/Ref/Kalman/ScalarKalman.html
http://tools.ietf.org/html/draft-alvestrand-rtcweb-congestion-02

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,437评论 0 23
  • 人的一生好比客栈, 每个早晨都有新的来客。 喜乐,沮丧,卑劣,某个瞬间的觉知, 像是意外的访客翩然到来。 欢迎并热...
    iRelaxSpace阅读 642评论 0 0
  • 小白一只,有错误敬请指出 官方Demo下载:https://docs.jiguang.cn/jpush/resou...
    orilme阅读 320评论 0 0
  • 我喜欢夏天 也讨厌夏天 夏天多美啊 有漂亮的裙子 好吃的冰淇淋 可爱的蝴蝶蜻蜓和动物 还有好看的你 夏天多热啊 我...
    阿锶阅读 197评论 2 0
  • Android内核的Log输出 Android内核是基于Linxu kernel的,因此其log机制也是通用的,在...
    J_Coder阅读 17,341评论 1 2