关键词:WebRTC、视频编码、帧丢弃、分辨率降级、自适应码率、视频质量控制
一、为什么需要丢帧和降分辨率?
在实时音视频通信中,网络带宽和终端 CPU 是两个最常见的瓶颈。当网络带宽不足时,如果继续以高分辨率、高帧率发送视频,就会造成:
- 网络拥塞:数据包在路由器队列中积压,延迟急剧上升;
- 丢包率升高:数据包被网络中间节点丢弃,画面出现马赛克;
- 编码质量下降:编码器被迫用更高 QP(量化参数)压缩图像,花屏严重。
当终端 CPU 过载时,编码时延超过帧间隔,就会造成帧率下降、卡顿,甚至整个系统响应缓慢。
WebRTC 的解决方案是构建一套多层次、自适应的视频质量控制体系,在保证通话流畅的前提下,动态地调整帧率、分辨率和码率。
二、整体架构一览
WebRTC 视频发送链路的质量控制涉及以下几个核心模块:
摄像头/采集源
│
▼
[VideoAdapter] ← 源端帧率控制 & 分辨率缩放(裁剪 + 缩放)
│
▼
[VideoStreamEncoder] ← 核心编码器管理,多重丢帧决策点
│
├── [FrameDropper] ← 漏桶算法丢帧(码率过载保护)
├── [CongestionWindow Drop] ← 拥塞窗口回退丢帧
├── [EncoderQueue Drop] ← 编码队列积压丢帧
└── [DropDueToSize] ← 分辨率-码率不匹配丢帧
│
▼
[实际编码器 H264/VP8/VP9/AV1]
│
▼
[ResourceAdaptationProcessor] ← 自适应降级决策中枢
├── [OveruseFrameDetector] ← CPU 过载检测
├── [QualityScaler] ← QP 质量监控
└── [VideoStreamAdapter] ← 降分辨率 / 降帧率执行
下面我们逐层深入每一个模块的实现细节。
三、第一层:VideoAdapter------源端帧率与分辨率控制
VideoAdapter(路径:media/base/video_adapter.cc)是视频流在进入编码器之前的第一道"关卡",承担两项核心职责:
- 帧率控制:根据目标帧率决定是否丢弃当前帧;
- 分辨率适配:将输入帧裁剪(Crop)并缩放(Scale)到目标分辨率。
3.1 核心入口函数 AdaptFrameResolution()
cpp
bool VideoAdapter::AdaptFrameResolution(
int in_width, int in_height, int64_t in_timestamp_ns,
int* cropped_width, int* cropped_height,
int* out_width, int* out_height)
返回值说明:
true:帧应该被保留并编码(同时输出裁剪/缩放后的尺寸);false:帧应该被丢弃。
完整决策流程图如下:
AdaptFrameResolution() 入口
│
▼
计算 max_pixel_count ← 取 resolution_request 和 output_format_request 中更严格的限制
│
▼
max_pixel_count <= 0 ? ──是──▶ 丢帧(return false)
│否
▼
DropFrame(timestamp) ? ──是──▶ 丢帧(return false)
│否
▼
计算目标宽高比(横/竖屏分别处理)
│
▼
FindScale() 计算最佳缩放比例
│
▼
roundUp() 对齐 resolution_alignment_
│
▼
计算 out_width / out_height
│
▼
scale_resolution_down_to 是否有值?
│是 │否
▼ ▼
进一步限制输出尺寸 直接输出结果
│
▼
输出裁剪 & 缩放后的尺寸(return true)
3.2 帧率控制:DropFrame()
cpp
bool VideoAdapter::DropFrame(int64_t in_timestamp_ns) {
int max_fps = max_framerate_request_;
if (output_format_request_.max_fps)
max_fps = std::min(max_fps, *output_format_request_.max_fps);
framerate_controller_.SetMaxFramerate(max_fps);
return framerate_controller_.ShouldDropFrame(in_timestamp_ns);
}
FramerateController 是一个简单的令牌桶(Token Bucket),根据时间戳判断当前帧是否需要被丢弃。比如目标 15fps,当前输入 30fps,就会丢弃一半的帧。
max_fps 来自两个来源,取最小值:
max_framerate_request_:来自OnSinkWants(),由下游(编码器)设置;output_format_request_.max_fps:来自OnOutputFormatRequest(),由应用层或采集源设置。
3.3 分辨率缩放:FindScale() 算法
FindScale() 函数是分辨率降级的核心算法,它使用交替缩放策略寻找最接近目标像素数的缩放比:
初始状态: scale = 1/1
│
▼ 交替缩放步骤:× 3/4 和 × 2/3
1280×720 → (×3/4) → 960×540
→ (×2/3) → 640×360
→ (×3/4) → 480×270
→ (×2/3) → 320×180
→ (×3/4) → 240×135
→ (×2/3) → 160×90
算法规则:
- 如果输入宽高均为 3 的倍数,首先以 2/3 缩放;
- 之后交替乘以 3/4 和 2/3;
- 在所有满足
output_pixels <= max_pixels的候选中,选择与target_pixels差值最小的那个。
为何选择 3/4 和 2/3 交替而非简单的 1/2?
因为 1/2 的步长过大,会从 1080p 直接跳到 540p,中间缺少过渡档位。3/4 × 2/3 ≈ 0.5,两步合计效果相当于缩减一半,但中间增加了一个过渡点(如 960×540),使画质变化更平滑。
缩放比例参数含义:
| 参数 | 含义 |
|---|---|
target_pixel_count |
目标像素数,尽量接近该值 |
max_pixel_count |
像素数上限,不能超过 |
scale.numerator / scale.denominator |
分数形式的缩放比(先约分) |
分辨率对齐: 输出尺寸必须是 resolution_alignment_ 的整数倍(由编码器要求决定,常见值为 2、4、16),通过 roundUp() 向上取整但不超过原始尺寸。
3.4 外部控制接口
| 接口 | 调用方 | 作用 |
|---|---|---|
OnOutputFormatRequest() |
采集源/应用层 | 设置最大分辨率、帧率、目标宽高比 |
OnSinkWants() |
编码器/下游 Sink | 设置 max_pixel_count、max_fps、resolution_alignment |
AdaptFrameResolution() |
视频处理管线 | 每帧调用,获取帧是否丢弃及输出尺寸 |
四、第二层:VideoStreamEncoder------多重丢帧决策
VideoStreamEncoder(路径:video/video_stream_encoder.cc)是 WebRTC 视频发送链路的核心枢纽,在帧进入实际硬件/软件编码器之前,设置了四道丢帧防线。
4.1 第一道防线:编码队列积压 & 拥塞窗口丢帧
OnIncomingFrame()
│
├── queue_overload(编码队列积压)? ──是──▶ 丢帧(kEncoderQueue)
│
└── cwnd_frame_drop(拥塞窗口回退)? ──是──▶ 丢帧(kCongestionWindow)
│
└── 进入 MaybeEncodeVideoFrame()
拥塞窗口丢帧(cwnd_frame_drop) 是网络拥塞控制的一部分:
cpp
// 当 cwnd 压缩比 > 1% 且目标码率高于编码器最低码率时
if (cwnd_reduce_ratio > 0.01 && target_bitrate.bps() > 0 &&
target_bitrate.bps() > send_codec_.minBitrate * 1000) {
// 计算每隔多少帧丢一帧(最多丢一半:间隔最小为 2)
cwnd_frame_drop_interval_ = std::max(
2, static_cast<int>(target_bitrate.bps() / reduce_bitrate_bps));
}
cwnd_frame_counter_++ % cwnd_frame_drop_interval_.value() == 0 即每隔 N 帧丢一帧(N ≥ 2,即最多丢 50% 帧)。
为何不直接降低编码分辨率? 因为拥塞窗口反馈是瞬时的,丢帧响应更快(毫秒级),而调整编码器配置需要重新初始化,响应慢(百毫秒级)。
4.2 第二道防线:时间戳异常丢帧
cpp
if (incoming_frame.ntp_time_ms() <= last_captured_timestamp_) {
// 时间戳相同或倒退,丢弃此帧
ProcessDroppedFrame(incoming_frame, kBadTimestamp);
return;
}
此处保证视频帧的 NTP 时间戳严格单调递增,避免 RTP 时间线混乱。
4.3 第三道防线:分辨率-码率不匹配丢帧(DropDueToSize)
cpp
bool VideoStreamEncoder::DropDueToSize(uint32_t source_pixel_count) const {
// ...
if (bitrate_bps < 300000 /* qvga */) {
return pixel_count > 320 * 240; // 码率<300kbps但分辨率>QVGA,丢帧
} else if (bitrate_bps < 500000 /* vga */) {
return pixel_count > 640 * 480; // 码率<500kbps但分辨率>VGA,丢帧
}
return false;
}
对应的码率-分辨率阈值表:
| 当前可用码率 | 允许的最大分辨率 | 说明 |
|---|---|---|
| < 300 kbps | 320×240(QVGA) | 低码率强制限制分辨率 |
| 300~500 kbps | 640×480(VGA) | 中低码率允许 VGA |
| ≥ 500 kbps | 无限制(由编码器配置决定) | 正常码率范围 |
| 编码器有码率限制 | 由 ResolutionBitrateLimits 决定 |
优先使用编码器提供的限制 |
当 DropDueToSize() 返回 true 时,帧会被存入 pending_frame_,同时通知 stream_resource_manager_.OnFrameDroppedDueToSize(),触发分辨率降级流程(详见第五章)。
4.4 第四道防线:编码器暂停(EncoderPaused)
cpp
bool VideoStreamEncoder::EncoderPaused() const {
// 当目标码率为 0(网络断了)时,编码器进入暂停状态
return encoder_target_bitrate_bps_.value_or(0) == 0;
}
暂停时帧同样存入 pending_frame_,恢复后发送最新的一帧(避免发送已经过时的帧)。
五、第三层:FrameDropper------漏桶算法精细丢帧
FrameDropper(路径:modules/video_coding/utility/frame_dropper.cc)是基于漏桶(Leaky Bucket)算法 实现的码率过载保护器,它在编码之后通过观测实际编码帧大小来决定下一帧是否需要丢弃。
5.1 漏桶模型
编码帧(大小 = framesize_kbits)
│
▼ Fill()
┌───────────────┐
│ accumulator │ ← 当前桶内积累的比特数
│ (水桶) │
└───────┬───────┘
│ Leak()
▼ 每帧漏出 target_bitrate/fps 比特
accumulator > accumulator_max_ ?
│是
▼ UpdateRatio() → 提升 drop_ratio_
DropFrame() 按照 drop_ratio_ 决定下一帧是否丢弃
关键参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
target_bitrate_ |
300 kbps | 目标码率 |
incoming_frame_rate_ |
30 fps | 输入帧率 |
accumulator_max_ |
target_bitrate × 0.5s | 桶的容量(半秒的数据量) |
kAccumulatorCapBufferSizeSecs |
3.0 s | 桶的最大上限(防止屏幕共享大帧引发过多丢帧) |
kDefaultMaxDropDurationSecs |
4.0 s | 连续丢帧最大持续时间 |
kLargeDeltaFactor |
3 | 超过平均帧大小 3 倍则视为"大帧" |
5.2 Fill() ------ 向桶中注入数据
cpp
void FrameDropper::Fill(size_t framesize_bytes, bool delta_frame) {
float framesize_kbits = 8.0f * framesize_bytes / 1000.0f;
if (!delta_frame) { // I 帧(关键帧)
// 计算分摊次数 = min(1/关键帧频率, 帧率/2)
// 将 I 帧大小分摊到后续多帧,避免单帧过大导致突发丢帧
large_frame_accumulation_count_ = ...;
large_frame_accumulation_chunk_size_ = framesize_kbits / count;
framesize_kbits = 0; // 本帧不立即注入
} else { // P 帧(差异帧)
if (framesize_kbits > kLargeDeltaFactor × 平均帧大小) {
// 超大 P 帧(如场景切换)也做分摊处理
...
} else {
delta_frame_size_avg_kbits_.Apply(1, framesize_kbits); // 更新平均帧大小
}
}
accumulator_ += framesize_kbits;
CapAccumulator(); // 限制桶的最大深度
}
I 帧分摊逻辑详解:
I 帧通常比 P 帧大 10-30 倍,如果直接注入桶中,会瞬间触发大量丢帧。WebRTC 的做法是将 I 帧的比特数分摊到后续 N 帧(N ≈ 1/关键帧频率,通常为 15~30 帧),每帧 Leak() 时额外少漏出一个 chunk,从而平滑处理 I 帧带来的突发。
5.3 Leak() ------ 从桶中漏出数据
cpp
void FrameDropper::Leak(uint32_t input_framerate) {
// 每帧应消耗的比特数
float expected_bits_per_frame = target_bitrate_ / input_framerate;
// 如果有大帧分摊,减少本帧漏出量
if (large_frame_accumulation_count_ > 0) {
expected_bits_per_frame -= large_frame_accumulation_chunk_size_;
--large_frame_accumulation_count_;
}
accumulator_ -= expected_bits_per_frame;
if (accumulator_ < 0.0f) accumulator_ = 0.0f;
UpdateRatio(); // 根据桶的水位更新丢帧比例
}
5.4 UpdateRatio() ------ 动态调整丢帧比例
accumulator > 1.3 × accumulator_max_ ?
│是 → 快速反应(base=0.8)
│否 → 正常反应(base=0.9)
│
▼
accumulator > accumulator_max_ ?
│是 → drop_ratio_ 增大(趋向 1.0)
│ → 同时设置 drop_next_ = true(立即丢下一帧)
│否 → drop_ratio_ 减小(趋向 0.0)
drop_ratio_ 是一个指数滤波值(ExpFilter),变化平滑,避免丢帧行为抖动。
5.5 DropFrame() ------ 最终丢帧决策
drop_ratio_ >= 0.5 时(高丢帧率模式):
limit = 1/(1 - drop_ratio_) - 1 (每保留1帧,中间丢 limit 帧)
drop_count_ < limit → 丢帧
drop_count_ >= limit → 保帧并重置计数
drop_ratio_ 在 (0, 0.5) 时(低丢帧率模式):
limit = -(1/drop_ratio_ - 1) (每丢1帧,中间保留 |limit| 帧)
按负计数器逻辑决定丢/保
举例说明:
| drop_ratio_ | 丢帧模式 | limit 值 | 效果 |
|---|---|---|---|
| 0.75 | 高丢帧率 | 3 | 丢3帧保1帧 |
| 0.5 | 边界 | 1 | 丢1帧保1帧 |
| 0.33 | 低丢帧率 | -2 | 丢1帧保2帧 |
| 0.25 | 低丢帧率 | -3 | 丢1帧保3帧 |
| 0.0 | 不丢帧 | - | 全部保留 |
5.6 FrameDropper 在 VideoStreamEncoder 中的调用时序
每帧到达
│
▼
MaybeEncodeVideoFrame()
│
├── frame_dropper_.Leak(framerate_fps) ← 先漏水(每帧都调用)
│
├── frame_dropper_.Enable(...) ← 控制是否启用
│ (trusted rate controller时关闭)
│
└── frame_dropper_.DropFrame() ?
│是 → 丢帧(kMediaOptimization)
│否 → EncodeVideoFrame()
编码完成后(RunPostEncode):
frame_dropper_.Fill(frame_size.bytes(), !keyframe) ← 注入实际帧大小
六、第四层:资源自适应------CPU与质量触发降级
当上面三层的丢帧还不够时(比如持续高负载),WebRTC 会通过资源自适应处理器触发更根本的变化:降低分辨率或帧率。
6.1 CPU 过载检测:OveruseFrameDetector
路径:video/adaptation/overuse_frame_detector.cc
OveruseFrameDetector 监控每帧的编码时长占帧间隔的比例(即编码 CPU 使用率),通过定时任务周期性检查:
定期调用 CheckForOveruse()
│
├── encode_usage_percent_ > 85% (high_threshold)?
│ 连续 2 次超阈值? → AdaptDown()(降级)
│
└── encode_usage_percent_ < 42% (low_threshold)?
满足观察期? → AdaptUp()(升级)
关键阈值表:
| 参数 | 默认值 | 含义 |
|---|---|---|
high_encode_usage_threshold_percent |
85% | 超过此值触发 AdaptDown |
low_encode_usage_threshold_percent |
42% | 低于此值触发 AdaptUp |
min_frame_samples |
120 帧 | 最少需要 120 帧样本才能做判断 |
high_threshold_consecutive_count |
2 次 | 连续 2 次检测到过载才触发降级 |
frame_timeout_interval_ms |
1500 ms | 超过此时间没有帧,重置统计 |
6.2 QP 质量监控:QualityScaler
路径:modules/video_coding/utility/quality_scaler.cc
QP(Quantization Parameter)是编码器质量的直接反映:QP 越高,压缩越猛,质量越差。QualityScaler 通过监控平均 QP 来判断是否需要降分辨率:
每 2 秒执行一次 CheckQp()
│
├── 丢帧率 >= 60%? → 报告 kHighQp(触发降分辨率)
│
├── 平均 QP > high_threshold? → kHighQp
│
└── 平均 QP <= low_threshold? → kLowQp(触发升分辨率)
不同编码器的 QP 阈值参考(以常见编解码器为例):
| 编解码器 | QP Low(升级阈值) | QP High(降级阈值) | 说明 |
|---|---|---|---|
| VP8 | 29 | 95 | 软件编码器典型值 |
| VP9 | 32 | 100 | 效率更高,QP范围更大 |
| H264 | 24 | 37 | QP范围 0~51 |
| H265 | 26 | 39 | 类似 H264 |
6.3 降级决策:VideoStreamAdapter
路径:call/adaptation/video_stream_adapter.cc
收到 AdaptDown() 信号后,VideoStreamAdapter 根据**降级偏好(DegradationPreference)**决定具体的降级策略:
AdaptDown() 信号到来
│
▼
switch (degradation_preference_)
│
├── MAINTAIN_FRAMERATE → DecreaseResolution()(只降分辨率)
│
├── MAINTAIN_RESOLUTION → DecreaseFramerate()(只降帧率)
│
├── BALANCED → 先尝试 DecreaseFramerate()
│ 若帧率已是最低 → DecreaseResolution()
│
└── DISABLED → 不做任何降级
降级偏好策略对比:
| 策略 | 降级方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| MAINTAIN_FRAMERATE | 只降分辨率 | 视频会议 | 运动流畅 | 画面模糊 |
| MAINTAIN_RESOLUTION | 只降帧率 | 共享内容、PPT | 清晰度高 | 运动卡顿 |
| BALANCED | 先降帧率再降分辨率 | 通用场景 | 均衡 | 行为复杂 |
| DISABLED | 不降级 | 特殊需求 | 最高质量 | 可能崩溃 |
分辨率降级步长(DecreaseResolution):
cpp
// 每次降级,目标像素数降为当前的 3/5(约等于边长缩小到 sqrt(3/5) ≈ 0.775)
int GetLowerResolutionThan(int pixel_count) {
return (pixel_count * 3) / 5;
}
例如:1280×720 = 921600 像素 → × 3/5 = 552960 ≈ 960×576 → × 3/5 = 331776 ≈ 640×480
帧率降级步长(DecreaseFramerate):
BALANCED 模式下,帧率降级的目标帧率由 BalancedDegradationSettings::MinFps() 查表决定,根据当前分辨率从配置表中获取最低帧率目标。
6.4 降级结果的传递链路
VideoStreamAdapter::DecreaseResolution()
│
▼ 设置 VideoSourceRestrictions
{ max_pixels_per_frame = target_pixels }
│
▼
VideoStreamEncoder::OnVideoSourceRestrictionsUpdated()
│
▼
VideoAdapter::OnSinkWants()
{ sink_wants.max_pixel_count = target_pixels }
│
▼
下一帧进入 AdaptFrameResolution() 时生效
整个调用链实现了"上层决策、下层执行"的分层架构,每一层职责清晰。
七、所有丢帧类型汇总
WebRTC 在统计模块 SendStatisticsProxy 中对丢帧原因进行了精细的分类统计:
| 丢帧类型 | 枚举值 | 触发位置 | 触发原因 |
|---|---|---|---|
| 采集源丢帧 | kSource |
VideoAdapter | 帧率过高,超过目标帧率 |
| 时间戳异常 | kBadTimestamp |
VideoStreamEncoder | NTP 时间戳相同或倒退 |
| 编码队列积压 | kEncoderQueue |
VideoStreamEncoder | 新帧到达时上一帧还未编完 |
| 码率过载 | kMediaOptimization |
FrameDropper | 漏桶积累超限 |
| 拥塞窗口回退 | kCongestionWindow |
VideoStreamEncoder | 网络拥塞,cwnd 减小 |
| 编码器内部丢帧 | kEncoder |
编码器 | 编码器自身决策 |
八、分辨率降级触发条件汇总
| 触发条件 | 检测模块 | 检测周期 | 降级动作 |
|---|---|---|---|
| CPU 编码耗时占比 > 85% | OveruseFrameDetector | ~5 秒 | 降分辨率/帧率 |
| 平均 QP > 高阈值 | QualityScaler | 2 秒 | 降分辨率/帧率 |
| 丢帧率 >= 60% | QualityScaler | 2 秒 | 降分辨率/帧率 |
| 码率 < 300kbps 且分辨率 > QVGA | DropDueToSize | 每帧 | 存入 pending_frame_,触发降级 |
| 带宽不匹配(BandwidthQualityScaler) | BandwidthQualityScaler | 2 秒 | 降分辨率/帧率 |
九、完整数据流总结
下面是一个完整帧的生命周期(正常场景下):
1. 摄像头采集帧(如 1080p@30fps)
│
▼
2. VideoAdapter::AdaptFrameResolution()
→ 检查帧率:是否超过 max_fps?超过则丢帧
→ 检查分辨率:根据 OnSinkWants() 的限制,计算缩放比
→ 输出裁剪/缩放后的尺寸(如 720p@15fps)
│
▼
3. VideoStreamEncoder::OnIncomingFrame()
→ queue_overload / cwnd_frame_drop 检查 → 可能丢帧
→ 时间戳合法性检查 → 可能丢帧
│
▼
4. MaybeEncodeVideoFrame()
→ EncoderPaused() 检查 → 可能暂存帧
→ DropDueToSize() 检查 → 可能丢帧并触发降级
→ frame_dropper_.Leak() → 漏水
→ frame_dropper_.DropFrame() → 漏桶丢帧决策
│
▼
5. EncodeVideoFrame()
→ 实际调用硬件/软件编码器
→ 生成 H264/VP8/VP9 码流
│
▼
6. RunPostEncode()
→ frame_dropper_.Fill(frame_size) → 向漏桶注入实际帧大小
→ OveruseFrameDetector 更新编码时长统计
→ QualityScaler 更新 QP 统计
│
▼
7. [异步] 资源自适应处理器评估
→ 若持续过载 → AdaptDown() → 降低分辨率/帧率
→ 若长期空闲 → AdaptUp() → 尝试提升分辨率/帧率
十、工程实践经验与调优建议
10.1 常见问题排查
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 帧率持续低于设定值 | VideoAdapter 帧率控制或 FrameDropper 触发 | 查看 VAdapt Drop Frame 日志 |
| 分辨率突然降低 | CPU/QP 触发降级 | 查看 Scaling down resolution 日志 |
| 视频卡顿但帧率正常 | QP 过高,质量差 | 检查 QualityScaler 日志及 QP 值 |
| 带宽占用超过设定 | FrameDropper 未启用(trusted rate controller) | 确认 encoder_info 配置 |
10.2 关键日志关键词
VAdapt Drop Frame→ VideoAdapter 丢帧Scaling down resolution→ 分辨率降级Scaling down framerate→ 帧率降级Drop Frame: target bitrate→ FrameDropper 丢帧CheckForOveruse: encode usage→ CPU 过载检测结果Checking average QP→ QP 质量检查结果Dropping frame. Too large for target bitrate→ DropDueToSize 触发
十一、总结
WebRTC 的视频质量自适应体系可以用一句话概括:从快到慢、从浅到深的分层保护机制。
| 层次 | 模块 | 响应时间 | 影响范围 |
|---|---|---|---|
| 第一层(最快) | cwnd 丢帧 / 队列丢帧 | < 10ms | 单帧 |
| 第二层 | FrameDropper 漏桶 | 100ms 级 | 连续帧 |
| 第三层 | VideoAdapter 帧率控制 | 帧间隔 | 持续帧率 |
| 第四层(最慢) | CPU/QP 触发降分辨率 | 2~5 秒 | 全局配置 |
这套机制的精妙之处在于:
- 从轻到重:先丢单帧,实在不行再降分辨率,尽量保留视觉质量;
- 快慢结合:快速的丢帧机制应对瞬时波动,慢速的降级机制应对持续压力;
- 可逆设计:所有降级都有对应的升级路径(AdaptUp),网络好转后自动恢复;
- 可观测性:每种丢帧都有独立计数器和日志,方便问题排查。
理解这套体系不仅有助于调试 WebRTC 视频质量问题,也为设计其他实时音视频系统提供了优秀的参考范本。
参考源码路径:
modules/video_coding/utility/frame_dropper.ccmedia/base/video_adapter.ccvideo/video_stream_encoder.ccvideo/adaptation/overuse_frame_detector.ccmodules/video_coding/utility/quality_scaler.cccall/adaptation/video_stream_adapter.ccvideo/adaptation/video_stream_encoder_resource_manager.cc