1/* 2 * Copyright 2015 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import "ARDStatsBuilder.h" 12 13#import "RTCPair.h" 14#import "RTCStatsReport.h" 15 16#import "ARDBitrateTracker.h" 17#import "ARDUtilities.h" 18 19@implementation ARDStatsBuilder { 20 // Connection stats. 21 NSString *_connRecvBitrate; 22 NSString *_connRtt; 23 NSString *_connSendBitrate; 24 NSString *_localCandType; 25 NSString *_remoteCandType; 26 NSString *_transportType; 27 28 // BWE stats. 29 NSString *_actualEncBitrate; 30 NSString *_availableRecvBw; 31 NSString *_availableSendBw; 32 NSString *_targetEncBitrate; 33 34 // Video send stats. 35 NSString *_videoEncodeMs; 36 NSString *_videoInputFps; 37 NSString *_videoInputHeight; 38 NSString *_videoInputWidth; 39 NSString *_videoSendCodec; 40 NSString *_videoSendBitrate; 41 NSString *_videoSendFps; 42 NSString *_videoSendHeight; 43 NSString *_videoSendWidth; 44 45 // Video receive stats. 46 NSString *_videoDecodeMs; 47 NSString *_videoDecodedFps; 48 NSString *_videoOutputFps; 49 NSString *_videoRecvBitrate; 50 NSString *_videoRecvFps; 51 NSString *_videoRecvHeight; 52 NSString *_videoRecvWidth; 53 54 // Audio send stats. 55 NSString *_audioSendBitrate; 56 NSString *_audioSendCodec; 57 58 // Audio receive stats. 59 NSString *_audioCurrentDelay; 60 NSString *_audioExpandRate; 61 NSString *_audioRecvBitrate; 62 NSString *_audioRecvCodec; 63 64 // Bitrate trackers. 65 ARDBitrateTracker *_audioRecvBitrateTracker; 66 ARDBitrateTracker *_audioSendBitrateTracker; 67 ARDBitrateTracker *_connRecvBitrateTracker; 68 ARDBitrateTracker *_connSendBitrateTracker; 69 ARDBitrateTracker *_videoRecvBitrateTracker; 70 ARDBitrateTracker *_videoSendBitrateTracker; 71} 72 73- (instancetype)init { 74 if (self = [super init]) { 75 _audioSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 76 _audioRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 77 _connSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 78 _connRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 79 _videoSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 80 _videoRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 81 } 82 return self; 83} 84 85- (NSString *)statsString { 86 NSMutableString *result = [NSMutableString string]; 87 NSString *systemStatsFormat = @"(cpu)%ld%%\n"; 88 [result appendString:[NSString stringWithFormat:systemStatsFormat, 89 (long)ARDGetCpuUsagePercentage()]]; 90 91 // Connection stats. 92 NSString *connStatsFormat = @"CN %@ms | %@->%@/%@ | (s)%@ | (r)%@\n"; 93 [result appendString:[NSString stringWithFormat:connStatsFormat, 94 _connRtt, 95 _localCandType, _remoteCandType, _transportType, 96 _connSendBitrate, _connRecvBitrate]]; 97 98 // Video send stats. 99 NSString *videoSendFormat = @"VS (input) %@x%@@%@fps | (sent) %@x%@@%@fps\n" 100 "VS (enc) %@/%@ | (sent) %@/%@ | %@ms | %@\n"; 101 [result appendString:[NSString stringWithFormat:videoSendFormat, 102 _videoInputWidth, _videoInputHeight, _videoInputFps, 103 _videoSendWidth, _videoSendHeight, _videoSendFps, 104 _actualEncBitrate, _targetEncBitrate, 105 _videoSendBitrate, _availableSendBw, 106 _videoEncodeMs, 107 _videoSendCodec]]; 108 109 // Video receive stats. 110 NSString *videoReceiveFormat = 111 @"VR (recv) %@x%@@%@fps | (decoded)%@ | (output)%@fps | %@/%@ | %@ms\n"; 112 [result appendString:[NSString stringWithFormat:videoReceiveFormat, 113 _videoRecvWidth, _videoRecvHeight, _videoRecvFps, 114 _videoDecodedFps, 115 _videoOutputFps, 116 _videoRecvBitrate, _availableRecvBw, 117 _videoDecodeMs]]; 118 119 // Audio send stats. 120 NSString *audioSendFormat = @"AS %@ | %@\n"; 121 [result appendString:[NSString stringWithFormat:audioSendFormat, 122 _audioSendBitrate, _audioSendCodec]]; 123 124 // Audio receive stats. 125 NSString *audioReceiveFormat = @"AR %@ | %@ | %@ms | (expandrate)%@"; 126 [result appendString:[NSString stringWithFormat:audioReceiveFormat, 127 _audioRecvBitrate, _audioRecvCodec, _audioCurrentDelay, 128 _audioExpandRate]]; 129 130 return result; 131} 132 133- (void)parseStatsReport:(RTCStatsReport *)statsReport { 134 NSString *reportType = statsReport.type; 135 if ([reportType isEqualToString:@"ssrc"] && 136 [statsReport.reportId rangeOfString:@"ssrc"].location != NSNotFound) { 137 if ([statsReport.reportId rangeOfString:@"send"].location != NSNotFound) { 138 [self parseSendSsrcStatsReport:statsReport]; 139 } 140 if ([statsReport.reportId rangeOfString:@"recv"].location != NSNotFound) { 141 [self parseRecvSsrcStatsReport:statsReport]; 142 } 143 } else if ([reportType isEqualToString:@"VideoBwe"]) { 144 [self parseBweStatsReport:statsReport]; 145 } else if ([reportType isEqualToString:@"googCandidatePair"]) { 146 [self parseConnectionStatsReport:statsReport]; 147 } 148} 149 150#pragma mark - Private 151 152- (void)parseBweStatsReport:(RTCStatsReport *)statsReport { 153 for (RTCPair *pair in statsReport.values) { 154 NSString *key = pair.key; 155 NSString *value = pair.value; 156 if ([key isEqualToString:@"googAvailableSendBandwidth"]) { 157 _availableSendBw = 158 [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 159 } else if ([key isEqualToString:@"googAvailableReceiveBandwidth"]) { 160 _availableRecvBw = 161 [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 162 } else if ([key isEqualToString:@"googActualEncBitrate"]) { 163 _actualEncBitrate = 164 [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 165 } else if ([key isEqualToString:@"googTargetEncBitrate"]) { 166 _targetEncBitrate = 167 [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 168 } 169 } 170} 171 172- (void)parseConnectionStatsReport:(RTCStatsReport *)statsReport { 173 NSDictionary *values = [self dictionaryForReport:statsReport]; 174 NSString *activeConnection = [values[@"googActiveConnection"] firstObject]; 175 if (![activeConnection isEqualToString:@"true"]) { 176 return; 177 } 178 for (RTCPair *pair in statsReport.values) { 179 NSString *key = pair.key; 180 NSString *value = pair.value; 181 if ([key isEqualToString:@"googRtt"]) { 182 _connRtt = value; 183 } else if ([key isEqualToString:@"googLocalCandidateType"]) { 184 _localCandType = value; 185 } else if ([key isEqualToString:@"googRemoteCandidateType"]) { 186 _remoteCandType = value; 187 } else if ([key isEqualToString:@"googTransportType"]) { 188 _transportType = value; 189 } else if ([key isEqualToString:@"bytesReceived"]) { 190 NSInteger byteCount = value.integerValue; 191 [_connRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 192 _connRecvBitrate = _connRecvBitrateTracker.bitrateString; 193 } else if ([key isEqualToString:@"bytesSent"]) { 194 NSInteger byteCount = value.integerValue; 195 [_connSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 196 _connSendBitrate = _connSendBitrateTracker.bitrateString; 197 } 198 } 199} 200 201- (void)parseSendSsrcStatsReport:(RTCStatsReport *)statsReport { 202 NSDictionary *values = [self dictionaryForReport:statsReport]; 203 NSString *trackId = [values[@"googTrackId"] firstObject]; 204 if (trackId.length && [trackId hasPrefix:@"ARDAMSv0"]) { 205 // Video track. 206 [self parseVideoSendStatsReport:statsReport]; 207 } else { 208 // Audio track. 209 [self parseAudioSendStatsReport:statsReport]; 210 } 211} 212 213- (void)parseAudioSendStatsReport:(RTCStatsReport *)statsReport { 214 for (RTCPair *pair in statsReport.values) { 215 NSString *key = pair.key; 216 NSString *value = pair.value; 217 if ([key isEqualToString:@"googCodecName"]) { 218 _audioSendCodec = value; 219 } else if ([key isEqualToString:@"bytesSent"]) { 220 NSInteger byteCount = value.integerValue; 221 [_audioSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 222 _audioSendBitrate = _audioSendBitrateTracker.bitrateString; 223 } 224 } 225} 226 227- (void)parseVideoSendStatsReport:(RTCStatsReport *)statsReport { 228 for (RTCPair *pair in statsReport.values) { 229 NSString *key = pair.key; 230 NSString *value = pair.value; 231 if ([key isEqualToString:@"googCodecName"]) { 232 _videoSendCodec = value; 233 } else if ([key isEqualToString:@"googFrameHeightInput"]) { 234 _videoInputHeight = value; 235 } else if ([key isEqualToString:@"googFrameWidthInput"]) { 236 _videoInputWidth = value; 237 } else if ([key isEqualToString:@"googFrameRateInput"]) { 238 _videoInputFps = value; 239 } else if ([key isEqualToString:@"googFrameHeightSent"]) { 240 _videoSendHeight = value; 241 } else if ([key isEqualToString:@"googFrameWidthSent"]) { 242 _videoSendWidth = value; 243 } else if ([key isEqualToString:@"googFrameRateSent"]) { 244 _videoSendFps = value; 245 } else if ([key isEqualToString:@"googAvgEncodeMs"]) { 246 _videoEncodeMs = value; 247 } else if ([key isEqualToString:@"bytesSent"]) { 248 NSInteger byteCount = value.integerValue; 249 [_videoSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 250 _videoSendBitrate = _videoSendBitrateTracker.bitrateString; 251 } 252 } 253} 254 255- (void)parseRecvSsrcStatsReport:(RTCStatsReport *)statsReport { 256 NSDictionary *values = [self dictionaryForReport:statsReport]; 257 NSString *transportId = [values[@"transportId"] firstObject]; 258 if ([values[@"googFrameWidthReceived"] firstObject]) { 259 [self parseVideoRecvStatsReport:statsReport]; 260 } else { 261 [self parseAudioRecvStatsReport:statsReport]; 262 } 263} 264 265- (void)parseAudioRecvStatsReport:(RTCStatsReport *)statsReport { 266 for (RTCPair *pair in statsReport.values) { 267 NSString *key = pair.key; 268 NSString *value = pair.value; 269 if ([key isEqualToString:@"googCodecName"]) { 270 _audioRecvCodec = value; 271 } else if ([key isEqualToString:@"bytesReceived"]) { 272 NSInteger byteCount = value.integerValue; 273 [_audioRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 274 _audioRecvBitrate = _audioRecvBitrateTracker.bitrateString; 275 } else if ([key isEqualToString:@"googSpeechExpandRate"]) { 276 _audioExpandRate = value; 277 } else if ([key isEqualToString:@"googCurrentDelayMs"]) { 278 _audioCurrentDelay = value; 279 } 280 } 281} 282 283- (void)parseVideoRecvStatsReport:(RTCStatsReport *)statsReport { 284 for (RTCPair *pair in statsReport.values) { 285 NSString *key = pair.key; 286 NSString *value = pair.value; 287 if ([key isEqualToString:@"googFrameHeightReceived"]) { 288 _videoRecvHeight = value; 289 } else if ([key isEqualToString:@"googFrameWidthReceived"]) { 290 _videoRecvWidth = value; 291 } else if ([key isEqualToString:@"googFrameRateReceived"]) { 292 _videoRecvFps = value; 293 } else if ([key isEqualToString:@"googFrameRateDecoded"]) { 294 _videoDecodedFps = value; 295 } else if ([key isEqualToString:@"googFrameRateOutput"]) { 296 _videoOutputFps = value; 297 } else if ([key isEqualToString:@"googDecodeMs"]) { 298 _videoDecodeMs = value; 299 } else if ([key isEqualToString:@"bytesReceived"]) { 300 NSInteger byteCount = value.integerValue; 301 [_videoRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 302 _videoRecvBitrate = _videoRecvBitrateTracker.bitrateString; 303 } 304 } 305} 306 307- (NSDictionary *)dictionaryForReport:(RTCStatsReport *)statsReport { 308 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 309 for (RTCPair *pair in statsReport.values) { 310 NSMutableArray *values = dict[pair.key]; 311 if (!values) { 312 values = [NSMutableArray arrayWithCapacity:1]; 313 dict[pair.key] = values; 314 } 315 [values addObject:pair.value]; 316 } 317 return dict; 318} 319 320@end 321 322