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_level和base_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_可以作为命令决策的主要依据。
若有收获,就点个赞吧