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的条件:
boolOveruseFrameDetector::IsOverusing(intusage_percent) {
// 使用率超过overuse的阈值,一般为90
if (usage_percent>=options_.high_encode_usage_threshold_percent) {
++checks_above_threshold_;
} else {
checks_above_threshold_=0;
}
// 连续2次超过阈值才认为是overuse
returnchecks_above_threshold_>=options_.high_threshold_consecutive_count;
}
判断为Underuse的条件:
boolOveruseFrameDetector::IsUnderusing(intusage_percent, int64_ttime_now) {
// 当前performance上升(ramp up),需要超过一定时长,否则不认为已经是underuse
intdelay=in_quick_rampup_?kQuickRampUpDelayMs : current_rampup_delay_ms_;
if (time_now<last_rampup_time_ms_+delay)
returnfalse;
// 低于阈值(一般overuse阈值的一半)就是underuse
returnusage_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。
退避相关算法如下:
boolcheck_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.1MAINTAIN_FRAMERATE
这次adapt down的分辨率需要比上次请求的小(否则就是ramp up了),否则不调整。
分辨率选择通过VideoStreamEncoder::VideoSourceProxy::RequestResolutionLowerThan实现:
boolRequestResolutionLowerThan(intpixel_count,
intmin_pixels_per_frame,
bool*min_pixels_reached) {
...
// MAINTAIN_FRAMERATE或者BALANCED时才能调整分辨率
if (!source_||!IsResolutionScalingEnabled(degradation_preference_)) {
returnfalse;
}
// 根据像素个数来做分辨率选择,像素个数变为原来的3/5
constintpixels_wanted= (pixel_count*3) /5;
if (pixels_wanted>=sink_wants_.max_pixel_count) {
returnfalse;
}
// 分辨率降低有限制
if (pixels_wanted<min_pixels_per_frame) {
*min_pixels_reached=true;
returnfalse;
}
// 更新sink wants到source
sink_wants_.max_pixel_count=pixels_wanted;
sink_wants_.target_pixel_count=absl::nullopt;
source_->AddOrUpdateSink(video_stream_encoder_,
GetActiveSinkWantsInternal());
returntrue;
}
4.2MAINTAIN_RESOLUTION
帧率不能低于降低到2帧以下。
分辨率选择通过VideoStreamEncoder::VideoSourceProxy::RequestFramerateLowerThan实现:
webrtc:
intRequestFramerateLowerThan(intfps) {
// 帧率降低到2/3
intframerate_wanted= (fps*2) /3;
returnRestrictFramerate(framerate_wanted) ?framerate_wanted : -1;
}
boolRestrictFramerate(intfps) {
...
// MAINTAIN_RESOLUTION或BALANCED时才能降低帧率
if (!source_||!IsFramerateScalingEnabled(degradation_preference_))
returnfalse;
// 帧率不能降低到2fps
constintfps_wanted=std::max(kMinFramerateFps, fps);
if (fps_wanted>=sink_wants_.max_framerate_fps)
returnfalse;
// 更新sink wants到source
sink_wants_.max_framerate_fps=fps_wanted;
source_->AddOrUpdateSink(video_stream_encoder_,
GetActiveSinkWantsInternal());
returntrue;
}
4.3 BALANCED
有时候需要在帧率和分辨率之间找一个平衡点(最新的WebRTC代码中使用BalancedDegradationSettings来实现,这里以老代码为例)。balace采取先降帧率,再降分辨率的策略,即先RestrictFramerate,再走MAINTAIN_FRAMERATE逻辑。
先限制帧率:
intMinFps(intpixels) {
if (pixels<=320*240) {
return7;
} elseif (pixels<=480*270) {
return10;
} elseif (pixels<=640*480) {
return15;
} else {
returnstd::numeric_limits<int>::max();
}
}
再走再走MAINTAIN_FRAMERATE逻辑调整分辨率。
5、AdaptUp
当检测性能处于underuse的时候,视频能力需要升级。和AdaptDown一样,也有三种方式,BALANCED、MAINTAIN_FRAMERATE、MAINTAIN_RESOLUTION。
这里对向上调整做了限制,只有向下调整过才能向上调整。
5.1MAINTAIN_FRAMERATE
boolRequestHigherResolutionThan(intpixel_count) {
...
// MAINTAIN_FRAMERATE或者BALANCED时才能调整分辨率
if (!source_||!IsResolutionScalingEnabled(degradation_preference_)) {
returnfalse;
}
// 像素x4上调
intmax_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)
returnfalse;
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());
returntrue;
}
5.2MAINTAIN_RESOLUTION
和AdaptUp类似,也是按照固定倍数上调。
intRequestHigherFramerateThan(intfps) {
// 调整速率,down是2/3,up是3/2
intframerate_wanted=fps;
if (fps!=std::numeric_limits<int>::max())
framerate_wanted= (fps*3) /2;
returnIncreaseFramerate(framerate_wanted) ?framerate_wanted : -1;
}
5.3 BALANCED
先升分辨率,再升帧率(走MAINTAIN_FRAMERATE逻辑)。
intMaxFps(intpixels) {
if (pixels<=320*240) {
return10;
} elseif (pixels<=480*270) {
return15;
} else {
returnstd::numeric_limits<int>::max();
}
}
6、总结
总结一下,性能自适应代码逻辑也不是很负载,主要的思想也就几点:
采取编码时长占采集间隔的比例得到性能的估计
根据性能估计得到overuse、underuse信号做adapt down和adapt up
从overuse到underuse需要谨慎,避免频繁切换
向上调整和向下调整策略主要有三个:保帧率、保分辨率、帧率和分辨率平衡。
原文https://zhuanlan.zhihu.com/p/352435623
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓