阅读视图

发现新文章,点击刷新页面。

微信通话时,是如何判断“当前/对方网络不佳”的?以及我们自己怎么实现?

前阵子跟客户微信语音聊需求,说着说着突然没声了,屏幕立马弹出“对方网络不佳”的提示,或者自己这边提示"当前网络不佳",反复切WiFi、开流量都没用,最后只能换电话沟通。其实这件事我想了很久了,还是打算今天拿来好好唠唠,顺便也给自己涨涨姿势,看看到底是神不可及的技术!!还是最最最简单的网络延迟方法。

为什么需要“网络不佳”提示

在微信通话这种实时音视频场景里,用户对流畅有非常低的容忍度,一旦出现断续的声音、口型不同步、画面卡顿或通话直接掉线,用户就会迅速认为服务不可靠并中断通话或投诉。因此在界面上及时、准确地提示“当前/对方网络不佳”不仅是对用户体验的尊重,也是减少误判、引导用户采取补救措施(切换到语音、关视频、切换网络或靠近路由器)的关键。具体场景包括:地铁或电梯等移动过程中发生的小区切换导致丢包与抖动;多人群聊或屏幕共享时上行带宽被耗尽导致画面质量急剧下降等,自适应码流和重传策略提供触发条件,并提升用户对恢复机制的信任感——这些都是设计“网络不佳”提示的直接动因。

image.png

微信是如何做到的?(猜测)

从技术上看,“网络好不好”并不是一个主观判断,而是一组持续可观测、可量化的网络与媒体质量信号。在实时音视频(RTC)系统中,最基础的一层是网络层指标:丢包率(Packet Loss)反映数据在传输路径上的可靠性;抖动(Jitter)描述包到达时间的不稳定性,直接决定是否需要更大的播放缓冲;RTT(Round-Trip Time)则刻画端到端时延和链路拥塞程度。在其之上是媒体层指标:码率(Bitrate)是否能稳定达到目标值、帧率(FPS)是否持续下降、关键帧是否频繁请求;再往上是体验层的综合指标,如 MOS(Mean Opinion Score) ,通过对丢包、时延、抖动、音频 PLC 触发次数、视频卡顿时长等信号加权估算“用户主观感受”。这些指标的共同点在于:它们都来自客户端和传输层的实时统计

在微信以及主流 RTC 平台(WebRTC、Agora、Zoom、腾讯云 TRTC 等)的实现中,通常不会依赖单一指标来下结论,而是采用多信号融合 + 时间窗口判断的方式。典型做法包括:在信令层和媒体层同时采集统计数据(冗余信令),避免单一路径或单一模块失效;通过 上/下行探测包(Probe Packet) 或带宽估计算法(如基于延迟梯度、丢包反馈的 BWE)持续判断链路可用带宽;在弱网或移动场景下启用 多通路/备份链路(如 Wi-Fi + 蜂窝网络的快速切换或并行探测);在播放端使用 自适应缓冲区(Adaptive Jitter Buffer) ,根据抖动动态调整缓冲深度,以在“低延迟”和“不卡顿”之间取平衡。一旦检测到多个关键指标在一定时间窗口内持续恶化(例如丢包率超过阈值、RTT 快速上升、码率被迫下探),系统就会触发体验等级下降,并映射为“当前/对方网络不佳”的用户提示。

这种思路在公开资料中也有佐证。WebRTC 官方文档和 RFC 中详细描述了基于 RTCP 统计的带宽估计与拥塞控制模型;腾讯、字节、阿里等厂商在公开专利中多次提到 多维网络质量评估、弱网对抗与体验分级提示机制;学术与工业界关于 MOS 预测的技术文献也表明,将底层网络指标映射为用户可理解的体验标签,是大规模 RTC 系统的通用做法。

如何决策

在产品层面,“网络不佳”不是技术结论展示,而是不干扰用户体验,核心目标只有一个:在不打扰用户的前提下,帮他理解当前通话异常的原因。因此微信这类产品在设计上通常遵循以下取舍。

网络指标是实时波动的,但提示不能实时波动。
实际策略通常是:时间窗口 + 连续恶化判定,例如在 2~5 秒内持续丢包升高、RTT 上扬、码率被迫下探,才认为是“稳定性问题”,否则只是短暂抖动,直接忽略。
这也是为什么你在地铁刚进隧道那一瞬间,微信往往不会立刻弹“网络不佳”。 当然了哈~~ 也不排除微信确实没及时检测到,哈哈哈

技术方案

如果把“网络不佳”当成一个完整的技术功能来看,它并不是某个 if 判断,而是一条很清晰的过程:数据采集 → 指标聚合 → 质量评分 → 防抖与阈值 → 展示或策略处理

一、数据采集(Data Collection)

第一步解决的不是判断,而是你到底能看到什么。在 RTC 客户端里,采集通常来自三层:

  • 网络层:RTT、丢包率、抖动、发送/接收速率、重传次数
  • 传输/协议层:RTCP 统计、NACK/PLI/FIR 次数、拥塞窗口变化
  • 媒体层:编码码率、实际渲染帧率、卡顿时长、音频 PLC 触发次数

注意:这些数据不是按事件上报,而是以固定周期(如 200ms / 500ms / 1s)持续采样,形成时间序列。

二、指标聚合(Aggregation)

原始指标是噪声极大的,不能直接用。现实情况下我们系统一定要收集:

  • 滑动时间窗(如最近 3s / 5s)
  • 计算均值、P95、变化斜率
  • 标记异常峰值(Spike)而不是立刻判坏

举个栗子:
一次 200ms 的 RTT 飙升,可能是 GC、系统调度或基站抖动;
RTT 连续 5 秒单调上升 + 丢包同步增加,才是链路拥塞的信号。

其实这个操作就是把瞬时的网络状态,转换成一个网络趋势,方便判断是否要提示用户!

三、质量评分(Quality Scoring)

接下来不是直接出网络好/网络坏,而是要有体验层映射。常见方式如下:

  • 规则加权
    score = w1*丢包 + w2*RTT + w3*卡顿 + w4*帧率下降
  • 分档映射
    优 / 良 / 可接受 / 差(对应 MOS 区间)

四、提示以及处理

这里的提示我们必须做防抖,不能反复频繁提示用户!

进入阈值:评分连续低于 X,持续 ≥ T 秒 退出阈值:评分连续高于 Y(Y > X),持续 ≥ T′ 秒 状态锁定:同一状态不重复触发提示

然后就是处理了, UI 层:展示「当前 / 对方网络不佳」。 要做的处理:

-   自动降码率 / 降分辨率
-   关闭视频保音频
-   切备用链路 / 重连
  • 统计层:上报埋点,用于后续策略优化

也就是说, “网络不佳”往往是系统已经做了很多努力之后的结果告知 ,而不是直接哇啦哇啦告诉用户,你踏马网废了。

整体流程示意


flowchart LR

A[原始数据采集<br/>RTT / 丢包 / 帧率] --> B[时间窗口聚合<br/>均值 / 趋势]

B --> C[质量评分<br/>MOS / 等级]

C --> D[防抖 & 阈值判断<br/>状态机]

D --> E[UI 提示<br/>网络不佳]

D --> F[自适应策略<br/>降码率/切链路]


我们如何实现呢?(ReactNative)

前文拆解的这套网络检测逻辑,并非微信独有的技术壁垒,在工程实践中,我们完全可以自己完成一套方案。下面直接用React Native结合WebRTC的实操举例,别眨眼,我要写代码了。(可以眨眼)

技术选型与依赖

在RN项目中,基于WebRTC做数据采集是最稳妥的选择,第一步先安装核心依赖:

yarn add react-native-webrtc

这个库自带的getStats方法,是网络质量判断的核心入口,里面包含了所有关键数据维度:

  • RTT(往返延迟)
  • packetsLost / packetsSent(丢包数/发送数)
  • jitter(抖动)
  • bitrate(码率,通过bytesSent差分计算得出)
  • frameRate(帧率,部分平台支持)

这里要明确一个核心认知:无需刻意计算网络状态,重点是精准读取传输过程中的原生统计数据。

image.png

数据采集(定时 + 时间序列)

const statsBuffer: StatSample[] = [];

setInterval(async () => {
  const stats = await pc.getStats();
  const parsed = parseStats(stats);

  statsBuffer.push({
    rtt: parsed.rtt,
    packetLoss: parsed.packetLoss,
    jitter: parsed.jitter,
    bitrate: parsed.bitrate,
    ts: Date.now(),
  });

  // 只保留最近5秒的数据
  prune(statsBuffer, 5000);
}, 1000);

这里有两个至关重要的细节:切勿依赖单次数据快照,必须保留时间维度的连续数据。缺少这两点,后续的防抖处理和趋势判断都会沦为空谈。

指标聚合 + 质量评分(可解释优先)

function calcQuality(samples: StatSample[]) {
  const avgLoss = mean(samples.map(s => s.packetLoss));
  const avgRtt = mean(samples.map(s => s.rtt));
  const avgJitter = mean(samples.map(s => s.jitter));

  let score = 100;

  if (avgLoss > 0.05) score -= 30;
  if (avgRtt > 300) score -= 30;
  if (avgJitter > 50) score -= 20;

  return score;
}

这种规则加权的评分方式,在真实工程场景中应用极广。核心原因很简单:可调优、可回滚、可追溯,出现问题时能快速定位到具体异常指标。

阈值 + 防抖(用状态机思路,别堆if判断)

let badSince: number | null = null;
let state: 'GOOD' | 'BAD' = 'GOOD';

function updateState(score: number) {
  const now = Date.now();

  if (score < 60) {
    if (!badSince) badSince = now;
    if (now - badSince > 3000 && state !== 'BAD') {
      state = 'BAD';
      showNetworkBad();
    }
  } else {
    badSince = null;
    if (state === 'BAD' && score > 75) {
      state = 'GOOD';
      hideNetworkBad();
    }
  }
}

这段逻辑的核心要点很明确:评分低于60分时触发预警判定,持续3秒无改善才切换至异常状态;恢复时需评分超过75分才回切正常状态。这一步的设计直接决定提示功能的专业性,有效避免频繁误报影响用户体验。

举例方便所以使用打分制,也可以其他的

然后UI展示轻提示

{state === 'BAD' && (
  <View style={styles.badNetwork}>
    <Text>醒醒!!你踏马网废了</Text>
  </View>
)}

采用轻量提示设计,不弹窗、不弹出 Toast、不抢占用户操作焦点,仅安静告知用户:当前网络存在异常,非设备故障或操作问题。

image.png

自动降级处理,这点很重要

在真实项目中,网络异常提示绝非仅展示一句文案,更重要的是触发对应的自适应应对策略:

例如当异常状态持续5秒:

  • 自动降低视频码率
  • 下调视频分辨率或帧率

当异常状态持续10秒:

  • 提示用户关闭视频,优先保障音频通话通畅

当状态恢复正常时:

  • 缓慢提升码率,避免一次性拉满导致再次卡顿

这里有个核心工程原则务必记牢:恢复要慢,降级要快。

总结

从技术角度来看,判断通话网络好坏,其实就是三件事:持续采集指标、观察趋势、连续判定。瞬时波动不算数,只有连续多秒丢包、抖动高、延迟大,才真正算网络不佳。再配合降码率、先保音频、延迟提示的策略,就能在用户几乎感觉不到的情况下保证体验。核心逻辑很朴素,但工程上最难的是防抖、聚合和兜底

❌