

这两张图展示了完整的弱网策略框架和参数配置。下面展开具体实现细节和代码级要点。
一、弱网检测机制
1.1 检测指标采集
弱网检测不是看单一指标,而是多维度综合判断。核心采集四个指标,用 3-5 秒滑动窗口取加权平均,避免瞬时波动误触发降级。
1.2 综合评分模型
不要用单一阈值判断(比如"丢包>5%就降级"),而是用加权评分:
plaintext
score = 100
- w1 * (1 - bandwidth / targetBitrate) // 带宽不足扣分
- w2 * max(0, (rtt - 100) / 100) // RTT 超过 100ms 开始扣分
- w3 * packetLossRate * 10 // 丢包率扣分
- w4 * (queueSize / maxQueueSize) * 50 // 队列堆积扣分
// 典型权重: w1=0.4, w2=0.2, w3=0.25, w4=0.15
根据 score 映射到 L0-L4 等级:score > 80 → L0,60-80 → L1,40-60 → L2,20-40 → L3,<20 → L4。
二、分级降级策略详解
2.1 降级优先级
降级不是随意组合,有严格的优先级顺序:
降码率 → 降帧率 → 降分辨率 → 纯音频
原因:
降码率成本最低:Android 4.4+ 支持动态调整,不中断编码器,用户感知最小
降帧率成本中等:画面会卡但不断流,用户容忍度较高
降分辨率成本最高:需要 stop→reconfigure→start,200-500ms 中断,画面突变明显
纯音频是兜底:网络极差时保住音频比什么都卡强
2.2 各降级动作实现
降码率(首选方案)
java
// Android API 19+ 支持动态调整码率,无需重启编码器
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate);
codec.setParameters(params);
// 生效延迟约 1-3 帧,几乎无感知
关键点:
码率不要一次降到底,每次降 30%,观察 3-5 秒再决定是否继续降
码率下限建议不低于 300Kbps(480p),否则画质不可用
降码率的同时适当增大 IDR 间隔(2s→3s),减少关键帧占用的带宽
降帧率
java
// 方案1: 调整 Camera 帧率
Camera.Parameters params = camera.getParameters();
params.setPreviewFpsRange(15000, 15000); // 15fps
camera.setParameters(params);
// 方案2: 软跳帧(更灵活,兼容性好)
private int frameCounter = 0;
void onFrameAvailable() {
frameCounter++;
if (currentLevel >= L2 && frameCounter % 2 == 0) {
return; // 30fps → 15fps,丢掉每隔一帧
}
feedToEncoder(frame);
}
关键点:
软跳帧比硬调 Camera 帧率更灵活,可以精确控制丢帧比例
丢帧要均匀分布(每 N 帧丢 1 帧),不要连续丢
丢帧后时间戳要连续,不能跳跃,否则播放端会卡
降分辨率(最后手段)
java
// 必须重启编码器,约 200-500ms 中断
codec.stop();
MediaFormat newFormat = MediaFormat.createVideoFormat(
"video/avc", newWidth, newHeight);
newFormat.setInteger(MediaFormat.KEY_BIT_RATE, newBitrate);
newFormat.setInteger(MediaFormat.KEY_FRAME_RATE, newFps);
newFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 4);
codec.configure(newFormat, surface, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
codec.start();
// 重新发送 SPS/PPS + IDR 帧给 RTMP
关键点:
分辨率切换会导致画面短暂模糊/黑屏,需要 UI 层面做过渡(如显示"正在优化画质..."提示)
切换后必须立即请求一个 IDR 帧,确保播放端能正确重置解码器
分辨率档位建议预设几档:1080p → 720p → 480p → 360p,不要连续无级缩放
三、恢复机制(关键)
降级容易恢复难。恢复策略做得不好会导致反复抖动:升一档→网络变差→降回来→再升→再降,用户体验极差。
3.1 滞后回升
降级触发:连续 3 秒指标低于阈值就降
恢复触发:连续 8-15 秒指标稳定在上一档才试探回升
这个时间差是核心——降级快、恢复慢,避免在网络波动的灰色地带反复横跳。
3.2 阶梯试探
恢复时每次只升一档,不跳级。升档后观察 5 秒:
指标稳定 → 继续升一档
指标恶化 → 立即回退到降级前状态,并锁定 30 秒不再尝试升级
3.3 防抖动锁定
java
class NetworkRecoveryManager {
int currentLevel = L2;
int lastFailedLevel = -1;
long lastFailTime = 0;
static final long LOCK_DURATION = 30_000; // 30秒锁定
boolean canTryRecover() {
// 锁定期内不允许恢复
if (System.currentTimeMillis() - lastFailTime < LOCK_DURATION) {
return false;
}
return currentLevel > L0;
}
void onRecoverFailed(int level) {
lastFailedLevel = level;
lastFailTime = System.currentTimeMillis();
}
}
四、补充策略
除了降码率/分辨率,还有几个辅助手段:
4.1 FEC 前向纠错
在 L2+ 网络下启用 FEC,主动添加冗余包(20-40% 冗余度)。以带宽换丢包恢复,比重传延迟低。典型方案用 Reed-Solomon 或 XOR 冗余。
4.2 关键帧请求(PLI/FIR)
网络波动导致花屏时,主动向编码端请求 IDR 帧,让播放端快速恢复。RTMP 协议本身不支持 PLI,但可以通过应用层信令通知推流端插入 IDR:
java
// 推流端收到 PLI 请求,立即请求 IDR
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
codec.setParameters(params);
4.3 编码档位降级
将编码 Profile 从 High → Main → Baseline 逐级降级。Baseline 没有复杂的预测和滤波,编码速度快、功耗低,但压缩率差。这个手段在发热+弱网同时出现时特别有效。
4.4 音频优先保障
音频码率低(64-128Kbps)但体验最关键——视频卡可以忍,音频断了用户会直接退出。L4 纯音频模式下:
关闭视频编码,节省 CPU 和带宽
音频降码率到 48Kbps(仍可听清人声)
带宽恢复后先恢复音频到正常码率,再恢复视频
五、实际效果参考
某直播 App 实施这套分级降级策略后的效果(线上 A/B 测试数据):
核心收益在于:分级降级让直播在弱网下"能看"而不是"断掉",恢复机制避免了画质抖动,用户感知到的是"画质变模糊了但一直在播",而不是"反复卡顿黑屏"。