WebRtc性能自适应

本文使用 Zhihu On VSCode 创作并发布

在WebRTC音视频通话中,难免会遇到设备性能比较差,在开启多个应用的时候CPU占用比较高的问题。因此我们需要根据当前性能情况对当前视频能力做调整,优先调整的对象是对CPU消耗最多的编码。

在WebRTC中评估性能是通过对编码时长的估计,而不是系统的CPU统计,这是为什么呢?一方面是因为在不同平台甚至是统一平台不同系统版本上,CPU统计存在较大差异,另一方面CPU占用和目标能力并无法直接关联上。所以,这里选择了比较直接的指标,即编码时长,一旦编码时长超过了采集间隔,那说明当前编码存在非常大的性能瓶颈。选择合适的编码能力能够确保得到一个合理的编码时长。

1. 基本流程

WebRTC中提供了一个根据CPU占用动态调整编码能力的策略,其中CPU占用率没有从系统读取,而是使用编码时长相对采集间隔的占比来估计。主要流程如下:

流程

上述的流程运行于编码线程,由VideoStreamEncoder每隔5s触发一次检查(前3次不做任何处理)。OveruseFrameDetector是一个根据编码时长和采集间隔估计当前性能的管理类,ProcessingUsage是性能估计类,输出编码时长与采集间隔的比值。OveruseFrameDetector检测到overuse或者underuse会回调到VideoStreamEncoder,做AdaptDown或者AdaptUp。

2. 编码占用率计算

编码占用率计算接口为OveruseFrameDetector::ProcessingUsage,实现类SendProcessingUsage1。大概原理:占用率=编码时长/采集间隔,通过编码时长估计当期性能,如果编码时长超过采集间隔,那么当前性能肯定存在瓶颈。这里的编码时长和采集间隔使用指数滤波平滑。 详细实现可以见源码,这里没有必要过多介绍了。

3. Overuse和Underuse的检测

如何获取性能的指标usage_percent我们先按下不表,先看下如何得到overuse和uderuse这两个输出信号。OveruseFrameDetector根据当前编码占用率判断是否为overuse或者underuse,再根据这两个信号AdaptDown或者AdaptUp。

判断为Overuse的条件:

bool OveruseFrameDetector::IsOverusing(int usage_percent) {
  // 使用率超过overuse的阈值,一般为90
  if (usage_percent >= options_.high_encode_usage_threshold_percent) {
    ++checks_above_threshold_;
  } else {
    checks_above_threshold_ = 0;
  }
  // 连续2次超过阈值才认为是overuse
  return checks_above_threshold_ >= options_.high_threshold_consecutive_count;
}

判断为Underuse的条件:

bool OveruseFrameDetector::IsUnderusing(int usage_percent, int64_t time_now) {
  // 当前performance上升(ramp up),需要超过一定时长,否则不认为已经是underuse
  int delay = in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
  if (time_now < last_rampup_time_ms_ + delay)
    return false;

  // 低于阈值(一般overuse阈值的一半)就是underuse
  return usage_percent < options_.low_encode_usage_threshold_percent;
}

上面uderuse中,ramp up有一个delay,这个delay不是固定不变的,需要根据实际情况调整。这称作overuse退避,为了避免频繁在overuse和underuse之间切换,所以需要对上升做限制,需要满足一定的时长。

  • Quick ramp up时的阈值为kQuickRampUpDelayMs(10s)
  • 非quick ramp up时,需要做退避。
  • overuse->underuse->overuse时,如果距离最后一次ramp up时间小于40s或者连续4次检测overuse,为了避免频繁变换或者避免容易进入overuse,需要对ramp up的时长做x2退避,限制最大ramp up delay为240s。
  • 检测到overuse时,后续ramp up需要做退避;检测到underuse时,后续ramp up做quick ramp up。

退避相关算法如下:

bool check_for_backoff = last_rampup_time_ms_ > last_overuse_time_ms_;
if (check_for_backoff) {
  if (now_ms - last_rampup_time_ms_ < kStandardRampUpDelayMs ||
      num_overuse_detections_ > kMaxOverusesBeforeApplyRampupDelay) {
    // Going up was not ok for very long, back off.
    current_rampup_delay_ms_ *= kRampUpBackoffFactor;
    if (current_rampup_delay_ms_ > kMaxRampUpDelayMs)
      current_rampup_delay_ms_ = kMaxRampUpDelayMs;
  } else {
    // Not currently backing off, reset rampup delay.
    current_rampup_delay_ms_ = kStandardRampUpDelayMs;
  }
}

另外,overuse、underuser检测阈值可以配置,主要使用CpuOveruseOptions来配置。比如针对单核、双核系统overuse阈值可以降低到20、40,阈值调整到100以上可以disable自适应功能。

4. AdaptDown

当性能不足时需要做视频降级,视频降级有几种策略:

  • BALANCED,帧率和分辨率之间平衡
  • MAINTAIN_FRAMERATE,保帧率,调整分辨率
  • MAINTAIN_RESOLUTION,保分辨率,调整帧率

4.1 MAINTAIN_FRAMERATE

这次adapt down的分辨率需要比上次请求的小(否则就是ramp up了),否则不调整。

分辨率选择通过VideoStreamEncoder::VideoSourceProxy::RequestResolutionLowerThan实现:

bool RequestResolutionLowerThan(int pixel_count,
                                int min_pixels_per_frame,
                                bool* min_pixels_reached) {
    ...
    // MAINTAIN_FRAMERATE或者BALANCED时才能调整分辨率
    if (!source_ || !IsResolutionScalingEnabled(degradation_preference_)) {
        return false;
    }
    // 根据像素个数来做分辨率选择,像素个数变为原来的3/5
    const int pixels_wanted = (pixel_count * 3) / 5;
    if (pixels_wanted >= sink_wants_.max_pixel_count) {
        return false;
    }
    // 分辨率降低有限制
    if (pixels_wanted < min_pixels_per_frame) {
        *min_pixels_reached = true;
        return false;
    }

    // 更新sink wants到source
    sink_wants_.max_pixel_count = pixels_wanted;
    sink_wants_.target_pixel_count = absl::nullopt;
    source_->AddOrUpdateSink(video_stream_encoder_,
                                GetActiveSinkWantsInternal());
    return true;
}

4.2 MAINTAIN_RESOLUTION

帧率不能低于降低到2帧以下。

分辨率选择通过VideoStreamEncoder::VideoSourceProxy::RequestFramerateLowerThan实现:

webrtc:

int RequestFramerateLowerThan(int fps) {
  // 帧率降低到2/3
  int framerate_wanted = (fps * 2) / 3;
  return RestrictFramerate(framerate_wanted) ? framerate_wanted : -1;
}

bool RestrictFramerate(int fps) {
    ...
    // MAINTAIN_RESOLUTION或BALANCED时才能降低帧率
    if (!source_ || !IsFramerateScalingEnabled(degradation_preference_))
      return false;
    
    // 帧率不能降低到2fps
    const int fps_wanted = std::max(kMinFramerateFps, fps);
    if (fps_wanted >= sink_wants_.max_framerate_fps)
      return false;
    
    // 更新sink wants到source
    sink_wants_.max_framerate_fps = fps_wanted;
    source_->AddOrUpdateSink(video_stream_encoder_,
                             GetActiveSinkWantsInternal());
    return true;
}

4.3 BALANCED

有时候需要在帧率和分辨率之间找一个平衡点(最新的WebRTC代码中使用BalancedDegradationSettings来实现,这里以老代码为例)。balace采取先降帧率,再降分辨率的策略,即先RestrictFramerate,再走MAINTAIN_FRAMERATE逻辑。

先限制帧率:

int MinFps(int pixels) {
  if (pixels <= 320 * 240) {
    return 7;
  } else if (pixels <= 480 * 270) {
    return 10;
  } else if (pixels <= 640 * 480) {
    return 15;
  } else {
    return std::numeric_limits<int>::max();
  }
}

再走再走MAINTAIN_FRAMERATE逻辑调整分辨率。

5. AdaptUp

当检测性能处于underuse的时候,视频能力需要升级。和AdaptDown一样,也有三种方式,BALANCED、MAINTAIN_FRAMERATE、MAINTAIN_RESOLUTION。

这里对向上调整做了限制,只有向下调整过才能向上调整。

5.1 MAINTAIN_FRAMERATE

bool RequestHigherResolutionThan(int pixel_count) {
    ...
    // MAINTAIN_FRAMERATE或者BALANCED时才能调整分辨率
    if (!source_ || !IsResolutionScalingEnabled(degradation_preference_)) {
        return false;
    }

    // 像素x4上调
    int max_pixels_wanted = pixel_count;
    if (max_pixels_wanted != std::numeric_limits<int>::max())
        max_pixels_wanted = pixel_count * 4;

    if (max_pixels_wanted <= sink_wants_.max_pixel_count)
        return false;

    sink_wants_.max_pixel_count = max_pixels_wanted;
    if (max_pixels_wanted == std::numeric_limits<int>::max()) {
        // Remove any constraints.
        sink_wants_.target_pixel_count.reset();
    } else {
        sink_wants_.target_pixel_count = GetHigherResolutionThan(pixel_count);
    }

    // 更新sink wants到source
    source_->AddOrUpdateSink(video_stream_encoder_,
                                GetActiveSinkWantsInternal());
    return true;
}

5.2 MAINTAIN_RESOLUTION

和AdaptUp类似,也是按照固定倍数上调。

int RequestHigherFramerateThan(int fps) {
    // 调整速率,down是2/3,up是3/2
    int framerate_wanted = fps;
    if (fps != std::numeric_limits<int>::max())
        framerate_wanted = (fps * 3) / 2;

    return IncreaseFramerate(framerate_wanted) ? framerate_wanted : -1;
}

5.3 BALANCED

先升分辨率,再升帧率(走MAINTAIN_FRAMERATE逻辑)。

int MaxFps(int pixels) {
  if (pixels <= 320 * 240) {
    return 10;
  } else if (pixels <= 480 * 270) {
    return 15;
  } else {
    return std::numeric_limits<int>::max();
  }
}

6. 总结

总结一下,性能自适应代码逻辑也不是很负载,主要的思想也就几点:

  • 采取编码时长占采集间隔的比例得到性能的估计
  • 根据性能估计得到overuse、underuse信号做adapt down和adapt up
  • 从overuse到underuse需要谨慎,避免频繁切换
  • 向上调整和向下调整策略主要有三个:保帧率、保分辨率、帧率和分辨率平衡。

原文  WebRtc性能自适应 - 知乎

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

猜你喜欢

转载自blog.csdn.net/yinshipin007/article/details/132197210
今日推荐