ThroughputScorer
是目前真正使用的评分器,评分算法也相对复杂,会用到协议以及无线通信较多的术语。
详细评分流程
由于该评分器代码较为复杂,下面现分析完整的评分流程,最后再单独介绍下吞吐量估算的代码。
-
rssi和throughput评分: rssiAndThroughputScore
int rssiBaseScore = calculateRssiScore(candidate); int throughputBonusScore = calculateThroughputBonusScore(candidate); int rssiAndThroughputScore = rssiBaseScore + throughputBonusScore;
-
当前网络加分: currentNetworkBoost
int currentNetworkBonusMin = mScoringParams.getCurrentNetworkBonusMin(); int currentNetworkBonus = Math.max(currentNetworkBonusMin, rssiAndThroughputScore * mScoringParams.getCurrentNetworkBonusPercent() / 100); int currentNetworkBoost = (candidate.isCurrentNetwork() && !unExpectedNoInternet) ? currentNetworkBonus : 0;
currentNetworkBonusMin默认值16,可通过 config_wifiFrameworkCurrentNetworkBonusMin 配置
currentNetworkBonusPercent默认值20,可通过 config_wifiFrameworkCurrentNetworkBonusPercent 配置 -
安全加分: securityAward
int securityAward = candidate.isOpenNetwork() ? 0 : mScoringParams.getSecureNetworkBonus();
secureNetworkBonus 默认40分,可通过 config_wifiFrameworkSecureNetworkBonus 配置
-
计量网络评分: unmeteredAward
metered network(计量网络)
通常是按照流量或时间计费。int unmeteredAward = candidate.isMetered() ? 0 : mScoringParams.getUnmeteredNetworkBonus();
对于非计量网络,加1000分。
unmeteredNetworkBonus 默认1000分,可通过 config_wifiFrameworkUnmeteredNetworkBonus 配置
-
临时网络评分: savedNetworkAward
Ephemeral Network
也就是临时网络,通常是短暂连接。int savedNetworkAward = candidate.isEphemeral() ? 0 : mScoringParams.getSavedNetworkBonus();
非临时网络,加500分。
savedNetworkBonus 默认500分,可通过 config_wifiFrameworkSavedNetworkBonus 配置
-
可信网络评分: trustedAward
int trustedAward = TRUSTED_AWARD; // 1000 if (!candidate.isTrusted() || candidate.isRestricted()) { // Saved networks are not untrusted or restricted, but clear anyway savedNetworkAward = 0; unmeteredAward = 0; // Ignore metered for untrusted and restricted networks if (candidate.isCarrierOrPrivileged()) { trustedAward = HALF_TRUSTED_AWARD; } else if (candidate.getNominatorId() == NOMINATOR_ID_SCORED) { Log.e(TAG, "ScoredNetworkNominator is not carrier or privileged!"); trustedAward = 0; } else { trustedAward = 0; } }
如果是可信网络(trusted and unrestricted), 加1000分。
对于非可信网络,作如下处理:
-
carrier WiFi or privileged app config
加一半分, trustedAward = 500
-
否则
不加分, trustedAward = 0
-
-
OEM PAID:
OEM 付费网络,一般是OEM与移动运营商或其他服务提供商合作,为其设备的用户提供特定的网络服务。
int notOemPaidAward = NOT_OEM_PAID_AWARD; // 500 if (candidate.isOemPaid()) { savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway unmeteredAward = 0; // Ignore metered for oem paid networks trustedAward = 0; // Ignore untrusted for oem paid networks. notOemPaidAward = 0; }
-
OEM PRIVATE:
OEM 私有网络,通常并不会开放给普通用户,主要为设备的管理和维护提供了专用网络连接。OEM Private Network 可能用于设备的远程监控、诊断、升级或配置,由设备制造商或维护人员使用。
int notOemPrivateAward = NOT_OEM_PRIVATE_AWARD; // 500 if (candidate.isOemPrivate()) { savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway unmeteredAward = 0; // Ignore metered for oem paid networks trustedAward = 0; // Ignore untrusted for oem paid networks. notOemPaidAward = 0; notOemPrivateAward = 0; }
-
计算总分
先根据主要的字段计算出基础的分数
int scoreToDetermineBucket = unmeteredAward + savedNetworkAward + trustedAward + notOemPaidAward + notOemPrivateAward + securityAward;
上面参与计算的参数除了
securityAward
是40分,其他都是500或1000。scoreToDetermineBucket
主要用来确定一个大的分数范围,也就是决定在哪个bucket里面。下面是scoreWithinBucket
int scoreWithinBucket = rssiBaseScore + throughputBonusScore + currentNetworkBoost + bandSpecificBonus;
最后通过
scoreToDetermineBucket
和scoreWithinBucket
进行第一次打分,桶内(within bucket)分数最高500分,由getScoringBucketStepSize指定int score = scoreToDetermineBucket + Math.min(mScoringParams.getScoringBucketStepSize(), scoreWithinBucket);
scoringBucketStepSize默认500,可以通过 config_wifiScoringBucketStepSize 配置
-
根据当前网络状态调整分数:
如果当前已连接网络可以访问互联网(
currentNetworkHasInternet
),但是candidate没有互联网访问能力,那么是不会选择它的,直接赋予0分。// do not select a network that has no internet when the current network has internet. if (currentNetworkHasInternet && !candidate.isCurrentNetwork() && unExpectedNoInternet) { score = 0; }
-
根据最近选择调整分数:
如果是recently-selected network,会打一个很高的分数(TOP_TIER_BASE_SCORE = 1000000),也就是说偏向于连接最近选择过的网络。
如果多个网络都是recently-selected,那么可以通过rssi和throughtput再进行比较。
if (candidate.getLastSelectionWeight() > 0.0) { // Put a recently-selected network in a tier above everything else, // but include rssi and throughput contributions for BSSID selection. score = TOP_TIER_BASE_SCORE + rssiBaseScore + throughputBonusScore; }
以上就是ThroughputScorer
的评分流程。下面再看一下计算rssiBaseScore
和throughputBonusScore
使用到的两个函数calculateRssiScore()
和calculateThroughputBonusScore()
calculateRssiScore()
会针对6G网络的不同频宽,进行补偿加分,其他没什么特殊处理。
private int calculateRssiScore(Candidate candidate) {
int rssiSaturationThreshold = mScoringParams.getSufficientRssi(candidate.getFrequency());
int rssi = candidate.getScanRssi();
if (mScoringParams.is6GhzBeaconRssiBoostEnabled()
&& ScanResult.is6GHz(candidate.getFrequency())) {
switch (candidate.getChannelWidth()) {
case ScanResult.CHANNEL_WIDTH_40MHZ:
rssi += 3;
break;
case ScanResult.CHANNEL_WIDTH_80MHZ:
rssi += 6;
break;
case ScanResult.CHANNEL_WIDTH_160MHZ:
rssi += 9;
break;
case ScanResult.CHANNEL_WIDTH_320MHZ:
rssi += 12;
break;
default:
// do nothing
}
}
rssi = Math.min(rssi, rssiSaturationThreshold);
return (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
}
calculateThroughputBonusScore()
吞吐量最高可加320分,由throughputBonusLimit指定,可以通过 config_wifiFrameworkThroughputBonusLimit 配置
以800Mbps为分界线,分别对小于800Mbps部分(throughputUpTo800Mbps
)和大于800Mbps部分(throughputMoreThan800Mbps
)进行计算后叠加。
int throughputScoreRaw = (throughputUpTo800Mbps
* mScoringParams.getThroughputBonusNumerator() // 120
/ mScoringParams.getThroughputBonusDenominator()) // 433
+ (throughputMoreThan800Mbps
* mScoringParams.getThroughputBonusNumeratorAfter800Mbps() // 1
/ mScoringParams.getThroughputBonusDenominatorAfter800Mbps()); // 16
代入分子分母,简化后的代码如下
int throughputScoreRaw = (throughputUpTo800Mbps * 120/433 + throughputMoreThan800Mbps /16);
可以看到,800Mbps以上的占比较少,对评分结果的影响也就更小。
得到根据throughputScoreRaw
和throughputBonusLimit
将评分限制在320分以内。
最后看一下是如何拿到吞吐量的int throughput = candidate.getPredictedThroughputMbps();
吞吐量预测
下面是预测吞吐量的方法,是现在WifiNetworkSelector.java
中
private int predictThroughput(@NonNull ScanDetail scanDetail) {
if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) {
return 0;
}
int channelUtilizationLinkLayerStats = BssLoad.INVALID;
if (mWifiChannelUtilization != null) {
channelUtilizationLinkLayerStats =
mWifiChannelUtilization.getUtilizationRatio(
scanDetail.getScanResult().frequency);
}
ClientModeManager primaryManager =
mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
return mThroughputPredictor.predictThroughput(
primaryManager.getDeviceWiphyCapabilities(),
scanDetail.getScanResult().getWifiStandard(),
scanDetail.getScanResult().channelWidth,
scanDetail.getScanResult().level,
scanDetail.getScanResult().frequency,
scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
scanDetail.getNetworkDetail().getChannelUtilization(),
channelUtilizationLinkLayerStats,
mWifiGlobals.isBluetoothConnected());
}
channelUtilizationLinkLayerStats 的计算方法目前是固定的:
- 2G: 80
- 5G: 15
- 6G: 10
predictThroughput()
知道了channelUtilizationLinkLayerStats
的计算方式,我们再来看看ThroughputPredictor
的predictThroughput()
方法,传入的参数如下:
- DeviceWiphyCapabilities
- wifiStandardAp
wifi标准- channelWidthAp
频宽,比如20Mhz, 40MHz, 80MHz, 160MHz.- rssiDbm
信号强度- frequency
频率- maxNumSpatialStreamAp
最大支持的空间流数量- channelUtilizationBssLoad
bssload中的channel Utilization字段,它代表了信道繁忙的时间(百分比)。- channelUtilizationLinkLayerStats
- isBluetoothConnected
蓝牙是否已连接
具体实现如下
-
获取当前设备支持的最大空间流数量
// RX 和 TX 同步 int maxNumSpatialStreamDevice = Math.min(deviceCapabilities.getMaxNumberTxSpatialStreams(), deviceCapabilities.getMaxNumberRxSpatialStreams()); // 固定配置 if (mContext.getResources().getBoolean( R.bool.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideEnable)) { maxNumSpatialStreamDevice = mContext.getResources().getInteger( R.integer.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideValue); } // 取固定配置和芯片支持的最小值 int maxNumSpatialStream = Math.min(maxNumSpatialStreamDevice, maxNumSpatialStreamAp);
-
wifi标准确定
由于AP 和STA支持的WiFi标准一般是不一样的,所以需要同步,优先使用最高标准。
// Get minimum standard support between device and AP int wifiStandard; switch (wifiStandardAp) { case ScanResult.WIFI_STANDARD_11BE: if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE)) { wifiStandard = ScanResult.WIFI_STANDARD_11BE; break; } //FALL THROUGH case ScanResult.WIFI_STANDARD_11AX: if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AX)) { wifiStandard = ScanResult.WIFI_STANDARD_11AX; break; } //FALL THROUGH case ScanResult.WIFI_STANDARD_11AC: if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AC)) { wifiStandard = ScanResult.WIFI_STANDARD_11AC; break; } //FALL THROUGH case ScanResult.WIFI_STANDARD_11N: if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11N)) { wifiStandard = ScanResult.WIFI_STANDARD_11N; break; } //FALL THROUGH default: wifiStandard = ScanResult.WIFI_STANDARD_LEGACY; }
-
频宽(channelWidth)确定
和WiFi Standard一样,也需要看看本地设备是否支持对应的频宽,优先选择支持的最大频宽
switch (channelWidthAp) { case ScanResult.CHANNEL_WIDTH_320MHZ: if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_320MHZ)) { channelWidth = ScanResult.CHANNEL_WIDTH_320MHZ; break; } // FALL THROUGH case ScanResult.CHANNEL_WIDTH_160MHZ: if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ)) { channelWidth = ScanResult.CHANNEL_WIDTH_160MHZ; break; } // FALL THROUGH case ScanResult.CHANNEL_WIDTH_80MHZ: if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ)) { channelWidth = ScanResult.CHANNEL_WIDTH_80MHZ; break; } // FALL THROUGH case ScanResult.CHANNEL_WIDTH_40MHZ: if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ)) { channelWidth = ScanResult.CHANNEL_WIDTH_40MHZ; break; } // FALL THROUGH default: channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ; }
-
信道利用率计算
int channelUtilization = getValidChannelUtilization(frequency, channelUtilizationBssLoad, channelUtilizationLinkLayerStats, isBluetoothConnected); private int getValidChannelUtilization(int frequency, int channelUtilizationBssLoad, int channelUtilizationLinkLayerStats, boolean isBluetoothConnected) { int channelUtilization; boolean is2G = ScanResult.is24GHz(frequency); if (isValidUtilizationRatio(channelUtilizationBssLoad)) { channelUtilization = channelUtilizationBssLoad; } else if (isValidUtilizationRatio(channelUtilizationLinkLayerStats)) { channelUtilization = channelUtilizationLinkLayerStats; } else { channelUtilization = is2G ? CHANNEL_UTILIZATION_DEFAULT_2G : CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G; } if (is2G && isBluetoothConnected) { channelUtilization += CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G; channelUtilization = Math.min(channelUtilization, MAX_CHANNEL_UTILIZATION); } if (mVerboseLoggingEnabled) { StringBuilder sb = new StringBuilder(); Log.d(TAG, sb.append(" utilization (BssLoad) ").append(channelUtilizationBssLoad) .append(" utilization (LLStats) ").append(channelUtilizationLinkLayerStats) .append(" isBluetoothConnected: ").append(isBluetoothConnected) .append(" final utilization: ").append(channelUtilization) .toString()); } return channelUtilization; }
channelUtilization
的值在0-255之间。如果AP没有提供
channelUtilization
,那么就是用系统默认的channelUtilizationLinkLayerStats
;如果系统没有配置channelUtilizationLinkLayerStats,那么is2G ? CHANNEL_UTILIZATION_DEFAULT_2G : CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G
,2G是95,其他是15, 2.4G的信道利用率更高也就是更为繁忙。最后,2G情况下,如果连接了蓝牙,信道使用率会加上CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G。因为蓝牙也是在2.4G,与2.4G的wifi有竞争关系,这里粗略地估计出蓝牙对wifi的影响,大约占用1/4的信道使用。
public static final int MAX_CHANNEL_UTILIZATION = 255; public static final int CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G = MAX_CHANNEL_UTILIZATION / 4;
至此,信道使用率已经计算完成,它的值在0-255之间。
下面调用predictThroughputInternal()
方法利用前面计算出的各种参数进行吞吐量的估算。
predictThroughputInternal()
该方法是真正实现吞吐量估算。现看看它的参数
private int predictThroughputInternal(@WifiStandard int wifiStandard, boolean is11bMode,
int channelWidth, int rssiDbm, int maxNumSpatialStream, int channelUtilization,
int frequency) {
- wifiStandard
- is11bMode
11b几乎已经看不到了,最大传输速率只有11Mbps。- channelWidth
- rssiDbm
- maxNumSpatialStream
- channelUtilization
- frequency
-
基础参数计算
channelWidthFactor: 频宽因子,0(20MHz), 1(40MHz), 2(80MHz), 3(160MHz),4(320MHz)
numTonePerSym:其实就是子载波数量。表示一次信号传输可用的子载波数量。
symDurationNs: 一次传输的耗时,仅和具体的标准相关
maxBitsPerTone: tone指的是子载波,也就是单个子载波一次传输可以携带的数据长度 ???
maxNumSpatialStream: 空间流数量
最终结果如下
maxBitsPerTone
的计算单独看一下private static final int BIT_PER_TONE_SCALE = 1000; private static final int MAX_BITS_PER_TONE_LEGACY = (int) Math.round((6 * 3.0 * BIT_PER_TONE_SCALE) / 4.0); private static final int MAX_BITS_PER_TONE_11N = (int) Math.round((6 * 5.0 * BIT_PER_TONE_SCALE) / 6.0); private static final int MAX_BITS_PER_TONE_11AC = (int) Math.round((8 * 5.0 * BIT_PER_TONE_SCALE) / 6.0); private static final int MAX_BITS_PER_TONE_11AX = (int) Math.round((10 * 5.0 * BIT_PER_TONE_SCALE) / 6.0); private static final int MAX_BITS_PER_TONE_11BE = (int) Math.round((12 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
它其实是通过码率和符号位长计算出来的:比如11AC支持的最大码率是5/6, 由于其调制方式最高支持64-QAM,所以位长为6bit
-
RSSI && SNR
第一步得到的信息只能计算出理论的最高速率,还需要将环境因素考虑进去才能近似估算出实际的速率。环境因素包括信号强度RSSI和信噪比SNR。
如果配置了
config_wifiEnable6GhzBeaconRssiBoost=true
,那么需要对6G的RSSI往上微调一下,该功能可以通过配置关闭。这么做的原因是较宽的频宽虽然可以传输更多的数据,但同时也跟容易受到干扰而降低信号质量。主要还是为了提高估算的准确性。if (mContext.getResources().getBoolean(R.bool.config_wifiEnable6GhzBeaconRssiBoost) && ScanResult.is6GHz(frequency)) { switch (channelWidth) { case ScanResult.CHANNEL_WIDTH_40MHZ: rssiDbm += 3; break; case ScanResult.CHANNEL_WIDTH_80MHZ: rssiDbm += 6; break; case ScanResult.CHANNEL_WIDTH_160MHZ: rssiDbm += 9; break; case ScanResult.CHANNEL_WIDTH_320MHZ: rssiDbm += 12; break; default: // do nothing } }
SNR估算
噪声增益计算
int noiseFloorDbBoost = TWO_IN_DB * channelWidthFactor; // TWO_IN_DB = 3
channelWidthFactor
是第一步中根据频宽计算出来的。所以这里应该是根据频宽来调整预期的噪声水平。噪声强度计算
// Thermal noise floor power in dBm integrated over 20MHz with 5.5dB noise figure at 25C private static final int NOISE_FLOOR_20MHZ_DBM = -96; // A fudge factor to represent HW implementation margin in dB. // Predicted throughput matches pretty well with OTA throughput with this fudge factor. private static final int SNR_MARGIN_DB = 16; int noiseFloorDbm = NOISE_FLOOR_20MHZ_DBM + noiseFloorDbBoost + SNR_MARGIN_DB;
NOISE_FLOOR_20MHZ_DBM = -96
是20MHz下的噪声强度(以分贝毫瓦dbm为单位),它是室温状态下5.5dB的噪声系数所产生的热噪声强度;SNR_MARGIN_DB=16
是为了考虑硬件差异和其他可能的干扰因素而引入的修正值,所以这里计算出的noiseFloorDbm
会更加保守,相对更高一些。而noiseFloorDbBoost
是前面计算得来用于调整高频宽下的信噪比。计算信噪比SNR(Signal-to-Noise Ratio)
int snrDb = rssiDbm - noiseFloorDbm;
-
估算实际速率
计算实际的每个子载波传输的数据长度 bitPerTone
int bitPerTone = calculateBitPerTone(snrDb); bitPerTone = Math.min(bitPerTone, maxBitsPerTone); long bitPerToneTotal = bitPerTone * maxNumSpatialStream; long numBitPerSym = bitPerToneTotal * numTonePerSym; int phyRateMbps = (int) ((numBitPerSym * MICRO_TO_NANO_RATIO) / (symDurationNs * BIT_PER_TONE_SCALE));
calculateBitPerTone()
方法通过香农定理计算出单个子载波的速率(已经考虑了频宽的影响),然后再乘上空间流数量,子载波数量,最后得到了估算的传输速率phyRateMbps
S N R = 1 0 s n r D b 10 SNR = 10^{\frac{snrDb}{10} } SNR=1010snrDbb p s = b a n d w i d t h × log 2 ( 1 + S N R ) bps = bandwidth \times \log_{2}{(1+SNR)} bps=bandwidth×log2(1+SNR)
最后考虑信道使用率(Channel Utilization)的影响,
int airTimeFraction = calculateAirTimeFraction(channelUtilization, channelWidthFactor); int throughputMbps = (phyRateMbps * airTimeFraction) / MAX_CHANNEL_UTILIZATION;
计算方法如下,
channelWidthFactor
用来处理非20MHz的情况,因为channelUtilization是为20MHz频宽定义的。// Calculate the available airtime fraction value which is multiplied by // MAX_CHANNEL_UTILIZATION for integer representation. It is calculated as // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) * MAX_CHANNEL_UTILIZATION private int calculateAirTimeFraction(int channelUtilization, int channelWidthFactor) { int airTimeFraction20MHz = MAX_CHANNEL_UTILIZATION - channelUtilization; int airTimeFraction = airTimeFraction20MHz; // For the cases of 40MHz or above, need to take // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) ^ (2 ^ channelWidthFactor) // because channelUtilization is defined for primary 20MHz channel for (int i = 1; i <= channelWidthFactor; ++i) { airTimeFraction *= airTimeFraction; airTimeFraction /= MAX_CHANNEL_UTILIZATION; } if (mVerboseLoggingEnabled) { Log.d(TAG, " airTime20: " + airTimeFraction20MHz + " airTime: " + airTimeFraction); } return airTimeFraction; }
(1 - channelUtilization / MAX_CHANNEL_UTILIZATION) ^ (2 ^ channelWidthFactor)
是用来计算大于20MHz情况的公式,因为更高的频宽相应的需要更多的信道使用时间。
总结
与其他评分器相比,ThroughputScorer
更偏向于实际的传输速率,也更为合理。但是其评分过程涉及到一些吞吐量估算内容,需要一点无线通信的基础才能更好理解。