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 <WebRTC/RTCLegacyStatsReport.h> 14#import <WebRTC/RTCMacros.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 // QP stats. 46 int _videoQPSum; 47 int _framesEncoded; 48 int _oldVideoQPSum; 49 int _oldFramesEncoded; 50 51 // Video receive stats. 52 NSString *_videoDecodeMs; 53 NSString *_videoDecodedFps; 54 NSString *_videoOutputFps; 55 NSString *_videoRecvBitrate; 56 NSString *_videoRecvFps; 57 NSString *_videoRecvHeight; 58 NSString *_videoRecvWidth; 59 60 // Audio send stats. 61 NSString *_audioSendBitrate; 62 NSString *_audioSendCodec; 63 64 // Audio receive stats. 65 NSString *_audioCurrentDelay; 66 NSString *_audioExpandRate; 67 NSString *_audioRecvBitrate; 68 NSString *_audioRecvCodec; 69 70 // Bitrate trackers. 71 ARDBitrateTracker *_audioRecvBitrateTracker; 72 ARDBitrateTracker *_audioSendBitrateTracker; 73 ARDBitrateTracker *_connRecvBitrateTracker; 74 ARDBitrateTracker *_connSendBitrateTracker; 75 ARDBitrateTracker *_videoRecvBitrateTracker; 76 ARDBitrateTracker *_videoSendBitrateTracker; 77} 78 79- (instancetype)init { 80 if (self = [super init]) { 81 _audioSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 82 _audioRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 83 _connSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 84 _connRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 85 _videoSendBitrateTracker = [[ARDBitrateTracker alloc] init]; 86 _videoRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; 87 _videoQPSum = 0; 88 _framesEncoded = 0; 89 } 90 return self; 91} 92 93- (NSString *)statsString { 94 NSMutableString *result = [NSMutableString string]; 95 NSString *systemStatsFormat = @"(cpu)%ld%%\n"; 96 [result appendString:[NSString stringWithFormat:systemStatsFormat, 97 (long)ARDGetCpuUsagePercentage()]]; 98 99 // Connection stats. 100 NSString *connStatsFormat = @"CN %@ms | %@->%@/%@ | (s)%@ | (r)%@\n"; 101 [result appendString:[NSString stringWithFormat:connStatsFormat, 102 _connRtt, 103 _localCandType, _remoteCandType, _transportType, 104 _connSendBitrate, _connRecvBitrate]]; 105 106 // Video send stats. 107 NSString *videoSendFormat = @"VS (input) %@x%@@%@fps | (sent) %@x%@@%@fps\n" 108 "VS (enc) %@/%@ | (sent) %@/%@ | %@ms | %@\n" 109 "AvgQP (past %d encoded frames) = %d\n "; 110 int avgqp = [self calculateAvgQP]; 111 112 [result appendString:[NSString stringWithFormat:videoSendFormat, 113 _videoInputWidth, _videoInputHeight, _videoInputFps, 114 _videoSendWidth, _videoSendHeight, _videoSendFps, 115 _actualEncBitrate, _targetEncBitrate, 116 _videoSendBitrate, _availableSendBw, 117 _videoEncodeMs, 118 _videoSendCodec, 119 _framesEncoded - _oldFramesEncoded, avgqp]]; 120 121 // Video receive stats. 122 NSString *videoReceiveFormat = 123 @"VR (recv) %@x%@@%@fps | (decoded)%@ | (output)%@fps | %@/%@ | %@ms\n"; 124 [result appendString:[NSString stringWithFormat:videoReceiveFormat, 125 _videoRecvWidth, _videoRecvHeight, _videoRecvFps, 126 _videoDecodedFps, 127 _videoOutputFps, 128 _videoRecvBitrate, _availableRecvBw, 129 _videoDecodeMs]]; 130 131 // Audio send stats. 132 NSString *audioSendFormat = @"AS %@ | %@\n"; 133 [result appendString:[NSString stringWithFormat:audioSendFormat, 134 _audioSendBitrate, _audioSendCodec]]; 135 136 // Audio receive stats. 137 NSString *audioReceiveFormat = @"AR %@ | %@ | %@ms | (expandrate)%@"; 138 [result appendString:[NSString stringWithFormat:audioReceiveFormat, 139 _audioRecvBitrate, _audioRecvCodec, _audioCurrentDelay, 140 _audioExpandRate]]; 141 142 return result; 143} 144 145- (void)parseStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 146 NSString *reportType = statsReport.type; 147 if ([reportType isEqualToString:@"ssrc"] && 148 [statsReport.reportId rangeOfString:@"ssrc"].location != NSNotFound) { 149 if ([statsReport.reportId rangeOfString:@"send"].location != NSNotFound) { 150 [self parseSendSsrcStatsReport:statsReport]; 151 } 152 if ([statsReport.reportId rangeOfString:@"recv"].location != NSNotFound) { 153 [self parseRecvSsrcStatsReport:statsReport]; 154 } 155 } else if ([reportType isEqualToString:@"VideoBwe"]) { 156 [self parseBweStatsReport:statsReport]; 157 } else if ([reportType isEqualToString:@"googCandidatePair"]) { 158 [self parseConnectionStatsReport:statsReport]; 159 } 160} 161 162#pragma mark - Private 163 164- (int)calculateAvgQP { 165 int deltaFramesEncoded = _framesEncoded - _oldFramesEncoded; 166 int deltaQPSum = _videoQPSum - _oldVideoQPSum; 167 168 return deltaFramesEncoded != 0 ? deltaQPSum / deltaFramesEncoded : 0; 169} 170 171- (void)updateBweStatOfKey:(NSString *)key value:(NSString *)value { 172 if ([key isEqualToString:@"googAvailableSendBandwidth"]) { 173 _availableSendBw = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 174 } else if ([key isEqualToString:@"googAvailableReceiveBandwidth"]) { 175 _availableRecvBw = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 176 } else if ([key isEqualToString:@"googActualEncBitrate"]) { 177 _actualEncBitrate = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 178 } else if ([key isEqualToString:@"googTargetEncBitrate"]) { 179 _targetEncBitrate = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; 180 } 181} 182 183- (void)parseBweStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 184 [statsReport.values 185 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 186 [self updateBweStatOfKey:key value:value]; 187 }]; 188} 189 190- (void)updateConnectionStatOfKey:(NSString *)key value:(NSString *)value { 191 if ([key isEqualToString:@"googRtt"]) { 192 _connRtt = value; 193 } else if ([key isEqualToString:@"googLocalCandidateType"]) { 194 _localCandType = value; 195 } else if ([key isEqualToString:@"googRemoteCandidateType"]) { 196 _remoteCandType = value; 197 } else if ([key isEqualToString:@"googTransportType"]) { 198 _transportType = value; 199 } else if ([key isEqualToString:@"bytesReceived"]) { 200 NSInteger byteCount = value.integerValue; 201 [_connRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 202 _connRecvBitrate = _connRecvBitrateTracker.bitrateString; 203 } else if ([key isEqualToString:@"bytesSent"]) { 204 NSInteger byteCount = value.integerValue; 205 [_connSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 206 _connSendBitrate = _connSendBitrateTracker.bitrateString; 207 } 208} 209 210- (void)parseConnectionStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 211 NSString *activeConnection = statsReport.values[@"googActiveConnection"]; 212 if (![activeConnection isEqualToString:@"true"]) { 213 return; 214 } 215 [statsReport.values 216 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 217 [self updateConnectionStatOfKey:key value:value]; 218 }]; 219} 220 221- (void)parseSendSsrcStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 222 NSDictionary *values = statsReport.values; 223 if ([values objectForKey:@"googFrameRateSent"]) { 224 // Video track. 225 [self parseVideoSendStatsReport:statsReport]; 226 } else if ([values objectForKey:@"audioInputLevel"]) { 227 // Audio track. 228 [self parseAudioSendStatsReport:statsReport]; 229 } 230} 231 232- (void)updateAudioSendStatOfKey:(NSString *)key value:(NSString *)value { 233 if ([key isEqualToString:@"googCodecName"]) { 234 _audioSendCodec = value; 235 } else if ([key isEqualToString:@"bytesSent"]) { 236 NSInteger byteCount = value.integerValue; 237 [_audioSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 238 _audioSendBitrate = _audioSendBitrateTracker.bitrateString; 239 } 240} 241 242- (void)parseAudioSendStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 243 [statsReport.values 244 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 245 [self updateAudioSendStatOfKey:key value:value]; 246 }]; 247} 248 249- (void)updateVideoSendStatOfKey:(NSString *)key value:(NSString *)value { 250 if ([key isEqualToString:@"googCodecName"]) { 251 _videoSendCodec = value; 252 } else if ([key isEqualToString:@"googFrameHeightInput"]) { 253 _videoInputHeight = value; 254 } else if ([key isEqualToString:@"googFrameWidthInput"]) { 255 _videoInputWidth = value; 256 } else if ([key isEqualToString:@"googFrameRateInput"]) { 257 _videoInputFps = value; 258 } else if ([key isEqualToString:@"googFrameHeightSent"]) { 259 _videoSendHeight = value; 260 } else if ([key isEqualToString:@"googFrameWidthSent"]) { 261 _videoSendWidth = value; 262 } else if ([key isEqualToString:@"googFrameRateSent"]) { 263 _videoSendFps = value; 264 } else if ([key isEqualToString:@"googAvgEncodeMs"]) { 265 _videoEncodeMs = value; 266 } else if ([key isEqualToString:@"bytesSent"]) { 267 NSInteger byteCount = value.integerValue; 268 [_videoSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 269 _videoSendBitrate = _videoSendBitrateTracker.bitrateString; 270 } else if ([key isEqualToString:@"qpSum"]) { 271 _oldVideoQPSum = _videoQPSum; 272 _videoQPSum = value.integerValue; 273 } else if ([key isEqualToString:@"framesEncoded"]) { 274 _oldFramesEncoded = _framesEncoded; 275 _framesEncoded = value.integerValue; 276 } 277} 278 279- (void)parseVideoSendStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 280 [statsReport.values 281 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 282 [self updateVideoSendStatOfKey:key value:value]; 283 }]; 284} 285 286- (void)parseRecvSsrcStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 287 NSDictionary *values = statsReport.values; 288 if ([values objectForKey:@"googFrameWidthReceived"]) { 289 // Video track. 290 [self parseVideoRecvStatsReport:statsReport]; 291 } else if ([values objectForKey:@"audioOutputLevel"]) { 292 // Audio track. 293 [self parseAudioRecvStatsReport:statsReport]; 294 } 295} 296 297- (void)updateAudioRecvStatOfKey:(NSString *)key value:(NSString *)value { 298 if ([key isEqualToString:@"googCodecName"]) { 299 _audioRecvCodec = value; 300 } else if ([key isEqualToString:@"bytesReceived"]) { 301 NSInteger byteCount = value.integerValue; 302 [_audioRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 303 _audioRecvBitrate = _audioRecvBitrateTracker.bitrateString; 304 } else if ([key isEqualToString:@"googSpeechExpandRate"]) { 305 _audioExpandRate = value; 306 } else if ([key isEqualToString:@"googCurrentDelayMs"]) { 307 _audioCurrentDelay = value; 308 } 309} 310 311- (void)parseAudioRecvStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 312 [statsReport.values 313 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 314 [self updateAudioRecvStatOfKey:key value:value]; 315 }]; 316} 317 318- (void)updateVideoRecvStatOfKey:(NSString *)key value:(NSString *)value { 319 if ([key isEqualToString:@"googFrameHeightReceived"]) { 320 _videoRecvHeight = value; 321 } else if ([key isEqualToString:@"googFrameWidthReceived"]) { 322 _videoRecvWidth = value; 323 } else if ([key isEqualToString:@"googFrameRateReceived"]) { 324 _videoRecvFps = value; 325 } else if ([key isEqualToString:@"googFrameRateDecoded"]) { 326 _videoDecodedFps = value; 327 } else if ([key isEqualToString:@"googFrameRateOutput"]) { 328 _videoOutputFps = value; 329 } else if ([key isEqualToString:@"googDecodeMs"]) { 330 _videoDecodeMs = value; 331 } else if ([key isEqualToString:@"bytesReceived"]) { 332 NSInteger byteCount = value.integerValue; 333 [_videoRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; 334 _videoRecvBitrate = _videoRecvBitrateTracker.bitrateString; 335 } 336} 337 338- (void)parseVideoRecvStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { 339 [statsReport.values 340 enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { 341 [self updateVideoRecvStatOfKey:key value:value]; 342 }]; 343} 344 345@end 346 347