针对webrtc在弱网的码率自适应机制的学习。列举了几个影响因素。有时间的话,再来继续深入研究。
编码发送层优先级
// RTP/RTCP initialization.
// We add the highest spatial layer first to ensure it'll be prioritized
// when sending padding, with the hope that the packet rate will be smaller,
// and that it's more important to protect than the lower layers.
// TODO(nisse): Consider moving registration with PacketRouter last, after the
// modules are fully configured.
for (const RtpStreamSender& stream : rtp_streams_) {
constexpr bool remb_candidate = true;
transport->packet_router()->AddSendRtpModule(stream.rtp_rtcp.get(),
remb_candidate);
}
发送端利用带宽估计算法估算上行带宽,然后根据策略进行码率分配(rtcsdk上层会指定每一层的码率),到了webrtc后,webrtc会在此带宽配置下,按顺序优先分配码率,先小流,再中流,最后大流,当大流(或中流)分配到的码率小于大流(或中流)的最小码率时就会停发大流(或中流)。
比如: 估算出来的带宽是1 mbps,小流分配了256kbps,中流分配了512kbps,这个时候留给大流的只有256kbps,但是如果大流指定了最小带宽大小256kbps,则大流就会被停发。
DegradationPreference策略
// Based on the spec in
// https://w3c.github.io/webrtc-pc/#idl-def-rtcdegradationpreference.
// These options are enforced on a best-effort basis. For instance, all of
// these options may suffer some frame drops in order to avoid queuing.
// TODO(sprang): Look into possibility of more strictly enforcing the
// maintain-framerate option.
// TODO(deadbeef): Default to "balanced", as the spec indicates?
enum class DegradationPreference {
// Don't take any actions based on over-utilization signals. Not part of the
// web API.
DISABLED,
// On over-use, request lower resolution, possibly causing down-scaling.
MAINTAIN_FRAMERATE,
// On over-use, request lower frame rate, possibly causing frame drops.
MAINTAIN_RESOLUTION,
// Try to strike a "pleasing" balance between frame rate or resolution.
BALANCED,
};
a)DISABLED:禁用,不进行任何调整;
b)MAINTAIN_FRAMERATE:保持帧率,降低分辨率(主流默认用这个策略);
c)MAINTAIN_RESOLUTION:保持分辨率,降低帧率(双流默认用这个策略);
d)BALANCED:平衡,降级时先尝试降低帧率,如果当前分辨率不适合再降低帧率,则降低分辨率,然后再尝试降低帧率,依次交替进行;升级时也是先尝试升高帧率,如果当前分辨率不适合升高帧率,则升高分辨率,依次交替进行。
field_trials_ += "WebRTC-Video-BalancedDegradation/Enabled/"; //启用Balanced降级策略
field_trials_ += "WebRTC-Video-BalancedDegradationSettings/pixels:76800|172800|307200|691200|1555200|2764800,fps:7|10|15|15|15|15/";
编码耗时统计
方法如下:
编码使用率=视频编码时间/采集间隔时间(对分子分母进行样本收集并做平滑处理)
视频编码时间 = 本帧编码结束时间 – 本帧编码开始时间
采集间隔时间 = 本帧编码开始时间 – 上帧编码开始时间
系统每5秒执行一次CheckForOveruse任务,
const int64_t kCheckForOveruseIntervalMs = 5000;
const int64_t kTimeToFirstCheckForOveruseMs = 100;
当编码使用率连续2次超过阈值上限,则判定为OverUse触发降级AdaptDown;反之当编码使用率低于阈值下限,则判定为UnderUse触发升级AdaptUp。为避免震荡,判定为UnderUse时会有迟滞处理。
void OveruseFrameDetector::CheckForOveruse(
OveruseFrameDetectorObserverInterface* observer) {
RTC_DCHECK_RUN_ON(&task_checker_);
RTC_DCHECK(observer);
++num_process_times_;
if (num_process_times_ <= options_.min_process_count ||
!encode_usage_percent_)
return;
int64_t now_ms = rtc::TimeMillis();
if (IsOverusing(*encode_usage_percent_)) {
// If the last thing we did was going up, and now have to back down, we need
// to check if this peak was short. If so we should back off to avoid going
// back and forth between this load, the system doesn't seem to handle it.
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;
}
}
last_overuse_time_ms_ = now_ms;
in_quick_rampup_ = false;
checks_above_threshold_ = 0;
++num_overuse_detections_;
observer->AdaptDown();
} else if (IsUnderusing(*encode_usage_percent_, now_ms)) {
last_rampup_time_ms_ = now_ms;
in_quick_rampup_ = true;
observer->AdaptUp();
}
int rampup_delay =
in_quick_rampup_ ? kQuickRampUpDelayMs : current_rampup_delay_ms_;
RTC_LOG(LS_VERBOSE) << " Frame stats: "
" encode usage "
<< *encode_usage_percent_ << " overuse detections "
<< num_overuse_detections_ << " rampup delay "
<< rampup_delay;
}
不同运行环境的编码使用率上下限阈值默认设置如下(%):
硬编:200/150
软编:85/42
mac软编:单核20/10,双核40/20,其他核心数85/42。(目前没用到)
QP统计
与CPU使用度检测类似,初始化过程发生在编码器重新创建的时候(流初始化,或者编码格式变化)。
QualityScaler通过统计、计算编码后的每幅图像的量化参数(QP,Quantization Parameter,相当于图像的复杂度),当一系列图像的平均QP超过阈值时会调整分辨率(H264的合法范围是24~37),超过37要降分辨率,低于24要提高分辨率。
QP检测的主体代码在quality_scaler.cc的QualityScaler类中,后者作为observer注册到VideoStreamEncoder中,VideoStreamEncoder内完成了相关流程的控制。
void StartDelayedTask() {
RTC_DCHECK_EQ(state_, State::kNotStarted);
state_ = State::kCheckingQp;
TaskQueueBase::Current()->PostDelayedTask(
ToQueuedTask([this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), this] {
if (!this_weak_ptr) {
// The task has been cancelled through destruction.
return;
}
RTC_DCHECK_EQ(state_, State::kCheckingQp);
RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
switch (quality_scaler_->CheckQp()) {
case QualityScaler::CheckQpResult::kInsufficientSamples: {
result_.observed_enough_frames = false;
// After this line, |this| may be deleted.
DoCompleteTask();
return;
}
case QualityScaler::CheckQpResult::kNormalQp: {
result_.observed_enough_frames = true;
// After this line, |this| may be deleted.
DoCompleteTask();
return;
}
case QualityScaler::CheckQpResult::kHighQp: {
result_.observed_enough_frames = true;
result_.qp_usage_reported = true;
state_ = State::kAwaitingQpUsageHandled;
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
callback = ConstructCallback();
quality_scaler_->fast_rampup_ = false;
// After this line, |this| may be deleted.
quality_scaler_->handler_->OnReportQpUsageHigh(callback);
return;
}
case QualityScaler::CheckQpResult::kLowQp: {
result_.observed_enough_frames = true;
result_.qp_usage_reported = true;
state_ = State::kAwaitingQpUsageHandled;
rtc::scoped_refptr<QualityScalerQpUsageHandlerCallbackInterface>
callback = ConstructCallback();
// After this line, |this| may be deleted.
quality_scaler_->handler_->OnReportQpUsageLow(callback);
return;
}
}
}),
GetCheckingQpDelayMs());
}
每帧编码图像的QP值会被加入样本集中,然后周期性调用CheckQpTask(默认每2秒,但前一次执行结果会影响下一次执行的周期)。
- 当平均QP值超过阈值上限,则判定为QpHigh触发AdaptDown(另外编码器超码率丢帧率>=60%也会被判定为QpHigh);
- 当平均QP值低于阈值下限,则判定为QpLow触发AdaptUp。(QP升降级对保分辨率的双流无效)
qp的变化通过回调进行反馈。在函数ReportQPHigh中会修改fast_rampup_ = false;
目的是分辨率一旦降低后,CheckQP调用的周期将变长,再提高分辨率的话,需要等很长时间。
经在不同终端上的测试,不同运行平台的QP值上下限阈值,当前版本的默认QP值设置如下:
硬终端:22 ~ 45(插件中自己定义)
Windows:24 ~ 37(已经改成24 ~ 42)
Android:24 ~ 37(已经改成24 ~ 45)
ios/mac:28 ~ 39(已经改成28 ~ 45)
特殊环境下如果QP阈值默认设置不合适,可以通过WebRTC-Video-QualityScaling这个FieldTrial来设置修改,但一般情况下不建议修改。
降分辨率策略
交替乘 2/3 and 3/4以缩小分辨率到最接近目标分辨率,然后这个最接近的分辨率。
Alternately scale down by 3/4 and 2/3. This results in fractions which are effectively scalable.
For instance, starting at 1280×720 will result in the series
(3/4) => 960×540,
(1/2) => 640×360,
(3/8) => 480×270,
(1/4) => 320×180,
(3/16) => 240×125,
(1/8) => 160×90.
// Generates a scale factor that makes |input_pixels| close to |target_pixels|,
// but no higher than |max_pixels|.
Fraction FindScale(int input_width,
int input_height,
int target_pixels,
int max_pixels,
bool variable_start_scale_factor) {
// This function only makes sense for a positive target.
RTC_DCHECK_GT(target_pixels, 0);
RTC_DCHECK_GT(max_pixels, 0);
RTC_DCHECK_GE(max_pixels, target_pixels);
const int input_pixels = input_width * input_height;
// Don't scale up original.
if (target_pixels >= input_pixels)
return Fraction{1, 1};
Fraction current_scale = Fraction{1, 1};
Fraction best_scale = Fraction{1, 1};
if (variable_start_scale_factor) {
// Start scaling down by 2/3 depending on |input_width| and |input_height|.
if (input_width % 3 == 0 && input_height % 3 == 0) {
// 2/3 (then alternates 3/4, 2/3, 3/4,...).
current_scale = Fraction{6, 6};
}
if (input_width % 9 == 0 && input_height % 9 == 0) {
// 2/3, 2/3 (then alternates 3/4, 2/3, 3/4,...).
current_scale = Fraction{36, 36};
}
}
// The minimum (absolute) difference between the number of output pixels and
// the target pixel count.
int min_pixel_diff = std::numeric_limits<int>::max();
if (input_pixels <= max_pixels) {
// Start condition for 1/1 case, if it is less than max.
min_pixel_diff = std::abs(input_pixels - target_pixels);
}
// Alternately scale down by 3/4 and 2/3. This results in fractions which are
// effectively scalable. For instance, starting at 1280x720 will result in
// the series (3/4) => 960x540, (1/2) => 640x360, (3/8) => 480x270,
// (1/4) => 320x180, (3/16) => 240x125, (1/8) => 160x90.
while (current_scale.scale_pixel_count(input_pixels) > target_pixels) {
if (current_scale.numerator % 3 == 0 &&
current_scale.denominator % 2 == 0) {
// Multiply by 2/3.
current_scale.numerator /= 3;
current_scale.denominator /= 2;
} else {
// Multiply by 3/4.
current_scale.numerator *= 3;
current_scale.denominator *= 4;
}
int output_pixels = current_scale.scale_pixel_count(input_pixels);
if (output_pixels <= max_pixels) {
int diff = std::abs(target_pixels - output_pixels);
if (diff < min_pixel_diff) {
min_pixel_diff = diff;
best_scale = current_scale;
}
}
}
best_scale.DivideByGcd();
return best_scale;
}