NetEq(一) 延迟估计

总体框图

四个buffer

抖动缓冲区(packet buffer) 暂存从网络获得的音频数据包

解码缓冲区  (dec buffer ) 抖动缓冲区中的数据包通过解码器解码成为PCM原始音频数据,暂存到解码缓冲区

算法缓冲区   (algorithm buffer) NetEQ将解码缓冲区中的数据进行拉伸、平滑处理后将结果暂存到DSP算法缓冲区

语音缓冲区   (speech buffer)     算法缓冲区中的数据会被塞到语音缓冲区中,声卡每隔一段时间会从语音缓冲区中提取固定长度的语音数据播放

当需要加速,加速,丢包补偿,融合等操作时,需要经过一些算法处理解码后的数据,经过算法处理后的数据放入算法缓冲区。

延迟估计

有两个延迟需要估计,网络延迟和抖动延迟,涉及两个线程,一个线程负责接收网络发来的数据包并估计网络时延,另一个线程负责解码,算法处理,解码数据放入syncBuffer中

网络延迟

当每一帧数据包到来时,会进入NetEq的InsertPacket,首先把数据包保存到packet_buffer中。

const int ret = packet_buffer_->InsertPacket(*packet, &stats_);

目前配置的packet_buffer最多能缓存50包数据,如果packet_buffer写"爆了",会清空packet_buffer.

// Get an iterator pointing to the place in the buffer where the new packet
// should be inserted. The list is searched from the back, since the most
// likely case is that the new packet should be near the end of the list.
AudioPacketList::reverse_iterator rit = std::find_if(
    buffer_.rbegin(), buffer_.rend(), NewTimestampIsLarger(packet));

// The new packet is to be inserted to the right of |rit|. If it has the same
// timestamp as |rit|, which has a higher priority, do not insert the new
// packet to list.
if (rit != buffer_.rend() && packet.timestamp_ == rit->timestamp_) {
    return return_val;
}

// The new packet is to be inserted to the left of |it|. If it has the same
// timestamp as |it|, which has a lower priority, replace |it| with the new
// packet.
AudioPacketList::iterator it = rit.base();
if (it != buffer_.end() && packet.timestamp_ == it->timestamp_) {
    it = buffer_.erase(it);
}
buffer_.insert(it, std::move(packet));  // Insert the packet at that position.

然后调用delay_manger_的update方法,计算网络延迟,最终结果保存在target_level_中。

主要计算有两步,第一步是更新直方图。

UpdateHistogram(iat_packets);

iat_packets为距离上次数据包到来时,中间隔了多少包,正常情况下,该值为1,延迟一个包的时间到来该值为2,依次类推,iat_packets最大值为64,最多延迟63个包.

int iat_packets = packet_iat_stopwatch_->ElapsedMs() / packet_len_ms;

直方图定义了0~64共65个级别,直方图每个级别的值和为1.UpdateHistogram即根据本次的网络延迟更新直方图。

更新直方图的思路大致如下:

1.用遗忘因子(0~1)乘以0~64每个直方图对应的值,这样的话会导致直方图的和小于1.

2.把小于1的部分补充到iat_packets对应的级别中。

即这样做的目的是加重本次延迟的影响。

遗忘因子的更新公式如下:

因为iat_factor为Q15类型,kIatFactor值对应为0.9993.

static const int kIatFactor_ = 32745;
iat_factor_ += (kIatFactor_ - iat_factor_ + 3) >> 2; // 更新遗忘因子

第二步是网络延迟计算

有了上面一步的直方图更新,CalculateTargetLevel得出初步的网络延迟。

  • 统计直方图中从0级别开始到B级别概率和大于等于95%,记B为target_levelbase_target_level_.

  • DelayPeakDetector中的Update方法

该方法有两个参数inter_arrival_time和target_level,分别为前面计算出的iat_packet和target_level

当满足

inter_arrival_time > target_level + peak_detection_threshold_ ||
      inter_arrival_time > 2 * target_level

条件时,需要更新peak_history_,上述条件满足意味着预设的网络延迟不能满足当前网络延迟的需要。

当peak_history_个数大于等于2且本次的网络延迟小于等于history_peak_最大延迟的2倍时,target_level取target_level和peak_history_中最大延迟的最大值。

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_;
}

bool delay_peak_found = peak_detector_.Update(iat_packets, target_level);
  if (delay_peak_found) {
    target_level = std::max(target_level, peak_detector_.MaxPeakHeight());
  }
  • 最后调用LimitTargetLevel对target_level_进行调
void DelayManager::LimitTargetLevel() {
  least_required_delay_ms_ = (target_level_ * packet_len_ms_) >> 8; // 最少需要延迟的时间

  if (packet_len_ms_ > 0 && minimum_delay_ms_ > 0) {
    int minimum_delay_packet_q8 = (minimum_delay_ms_ << 8) / packet_len_ms_; // 最少延迟的包个数
    target_level_ = std::max(target_level_, minimum_delay_packet_q8);
  }

  if (maximum_delay_ms_ > 0 && packet_len_ms_ > 0) {
    int maximum_delay_packet_q8 = (maximum_delay_ms_ << 8) / packet_len_ms_; // 最大允许延迟的包个数
    target_level_ = std::min(target_level_, maximum_delay_packet_q8);
  }

  // Shift to Q8, then 75%.;
  int max_buffer_packets_q8 =
      static_cast<int>((3 * (max_packets_in_buffer_ << 8)) / 4); // 取0.75
  target_level_ = std::min(target_level_, max_buffer_packets_q8);

  // Sanity check, at least 1 packet (in Q8).
  target_level_ = std::max(target_level_, 1 << 8);
}

target_level_由maximum_delay_packet,minimum_delay_packet和max_buffer_packets_q8(抖动缓冲区容量的75%)再限制一次。

target_level_值为Q8类型

抖动延迟

在GetAudio中会调用 DecisionLogic::FilterBufferLevel估计抖动延迟,结果保存在filtered_current_level_中。

void DecisionLogic::FilterBufferLevel(size_t buffer_size_samples,
                                      Modes prev_mode) {
  // Do not update buffer history if currently playing CNG since it will bias
  // the filtered buffer level.
  if ((prev_mode != kModeRfc3389Cng) && (prev_mode != kModeCodecInternalCng)) {
    buffer_level_filter_->SetTargetBufferLevel(
        delay_manager_->base_target_level()); // buffer_level_filter_ 类似遗忘因子,取值为251~254

    size_t buffer_size_packets = 0;
    if (packet_length_samples_ > 0) {
      // Calculate size in packets.
      buffer_size_packets = buffer_size_samples / packet_length_samples_;
    }
    int sample_memory_local = 0;
    if (prev_time_scale_) {
      sample_memory_local = sample_memory_;
      timescale_countdown_ =
          tick_timer_->GetNewCountdown(kMinTimescaleInterval);
    }
    buffer_level_filter_->Update(buffer_size_packets, sample_memory_local,
                                 packet_length_samples_);
    prev_time_scale_ = false;
  }
}

计算公式如下:

BLc为抖动延迟结果,即filtered_current_level_的值

f计算公式中的B实际为网络延迟计算中的base_target_level_

Lp为一帧中包含的样本个数,SB为 抖动缓冲区 和sync_buffer中未使用数据之和,也就是所有未播放数据之和。

考虑两种特殊情况

1.f = 0,此时缓冲区延迟只和当前的延迟有关,和之前的延迟无关

2.f = 1,此时缓冲区延迟之和之前延迟相关,和当前地无关

filtered_current_level_为Q8类型

可以这样理解,当target_level_和filtered_current_level_处于基本一致的情况下,正常播放,当SB增大,说明缓冲区的数据(decode buffer 和sync buffer之和)增加,需要加快播放,同时BLc也会增加;反之,需要减速播放,所以通过target_level_和filtered_current_level_可以作为命令决策的主要依据。

若有收获,就点个赞吧

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil_baby

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值