/* * Copyright 2015 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #import "ARDStatsBuilder.h" #import "sdk/objc/api/peerconnection/RTCLegacyStatsReport.h" #import "sdk/objc/base/RTCMacros.h" #import "ARDBitrateTracker.h" #import "ARDUtilities.h" @implementation ARDStatsBuilder { // Connection stats. NSString *_connRecvBitrate; NSString *_connRtt; NSString *_connSendBitrate; NSString *_localCandType; NSString *_remoteCandType; NSString *_transportType; // BWE stats. NSString *_actualEncBitrate; NSString *_availableRecvBw; NSString *_availableSendBw; NSString *_targetEncBitrate; // Video send stats. NSString *_videoEncodeMs; NSString *_videoInputFps; NSString *_videoInputHeight; NSString *_videoInputWidth; NSString *_videoSendCodec; NSString *_videoSendBitrate; NSString *_videoSendFps; NSString *_videoSendHeight; NSString *_videoSendWidth; // QP stats. int _videoQPSum; int _framesEncoded; int _oldVideoQPSum; int _oldFramesEncoded; // Video receive stats. NSString *_videoDecodeMs; NSString *_videoDecodedFps; NSString *_videoOutputFps; NSString *_videoRecvBitrate; NSString *_videoRecvFps; NSString *_videoRecvHeight; NSString *_videoRecvWidth; // Audio send stats. NSString *_audioSendBitrate; NSString *_audioSendCodec; // Audio receive stats. NSString *_audioCurrentDelay; NSString *_audioExpandRate; NSString *_audioRecvBitrate; NSString *_audioRecvCodec; // Bitrate trackers. ARDBitrateTracker *_audioRecvBitrateTracker; ARDBitrateTracker *_audioSendBitrateTracker; ARDBitrateTracker *_connRecvBitrateTracker; ARDBitrateTracker *_connSendBitrateTracker; ARDBitrateTracker *_videoRecvBitrateTracker; ARDBitrateTracker *_videoSendBitrateTracker; } - (instancetype)init { if (self = [super init]) { _audioSendBitrateTracker = [[ARDBitrateTracker alloc] init]; _audioRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; _connSendBitrateTracker = [[ARDBitrateTracker alloc] init]; _connRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; _videoSendBitrateTracker = [[ARDBitrateTracker alloc] init]; _videoRecvBitrateTracker = [[ARDBitrateTracker alloc] init]; _videoQPSum = 0; _framesEncoded = 0; } return self; } - (NSString *)statsString { NSMutableString *result = [NSMutableString string]; NSString *systemStatsFormat = @"(cpu)%ld%%\n"; [result appendString:[NSString stringWithFormat:systemStatsFormat, (long)ARDGetCpuUsagePercentage()]]; // Connection stats. NSString *connStatsFormat = @"CN %@ms | %@->%@/%@ | (s)%@ | (r)%@\n"; [result appendString:[NSString stringWithFormat:connStatsFormat, _connRtt, _localCandType, _remoteCandType, _transportType, _connSendBitrate, _connRecvBitrate]]; // Video send stats. NSString *videoSendFormat = @"VS (input) %@x%@@%@fps | (sent) %@x%@@%@fps\n" "VS (enc) %@/%@ | (sent) %@/%@ | %@ms | %@\n" "AvgQP (past %d encoded frames) = %d\n "; int avgqp = [self calculateAvgQP]; [result appendString:[NSString stringWithFormat:videoSendFormat, _videoInputWidth, _videoInputHeight, _videoInputFps, _videoSendWidth, _videoSendHeight, _videoSendFps, _actualEncBitrate, _targetEncBitrate, _videoSendBitrate, _availableSendBw, _videoEncodeMs, _videoSendCodec, _framesEncoded - _oldFramesEncoded, avgqp]]; // Video receive stats. NSString *videoReceiveFormat = @"VR (recv) %@x%@@%@fps | (decoded)%@ | (output)%@fps | %@/%@ | %@ms\n"; [result appendString:[NSString stringWithFormat:videoReceiveFormat, _videoRecvWidth, _videoRecvHeight, _videoRecvFps, _videoDecodedFps, _videoOutputFps, _videoRecvBitrate, _availableRecvBw, _videoDecodeMs]]; // Audio send stats. NSString *audioSendFormat = @"AS %@ | %@\n"; [result appendString:[NSString stringWithFormat:audioSendFormat, _audioSendBitrate, _audioSendCodec]]; // Audio receive stats. NSString *audioReceiveFormat = @"AR %@ | %@ | %@ms | (expandrate)%@"; [result appendString:[NSString stringWithFormat:audioReceiveFormat, _audioRecvBitrate, _audioRecvCodec, _audioCurrentDelay, _audioExpandRate]]; return result; } - (void)parseStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { NSString *reportType = statsReport.type; if ([reportType isEqualToString:@"ssrc"] && [statsReport.reportId rangeOfString:@"ssrc"].location != NSNotFound) { if ([statsReport.reportId rangeOfString:@"send"].location != NSNotFound) { [self parseSendSsrcStatsReport:statsReport]; } if ([statsReport.reportId rangeOfString:@"recv"].location != NSNotFound) { [self parseRecvSsrcStatsReport:statsReport]; } } else if ([reportType isEqualToString:@"VideoBwe"]) { [self parseBweStatsReport:statsReport]; } else if ([reportType isEqualToString:@"googCandidatePair"]) { [self parseConnectionStatsReport:statsReport]; } } #pragma mark - Private - (int)calculateAvgQP { int deltaFramesEncoded = _framesEncoded - _oldFramesEncoded; int deltaQPSum = _videoQPSum - _oldVideoQPSum; return deltaFramesEncoded != 0 ? deltaQPSum / deltaFramesEncoded : 0; } - (void)updateBweStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googAvailableSendBandwidth"]) { _availableSendBw = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; } else if ([key isEqualToString:@"googAvailableReceiveBandwidth"]) { _availableRecvBw = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; } else if ([key isEqualToString:@"googActualEncBitrate"]) { _actualEncBitrate = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; } else if ([key isEqualToString:@"googTargetEncBitrate"]) { _targetEncBitrate = [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue]; } } - (void)parseBweStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateBweStatOfKey:key value:value]; }]; } - (void)updateConnectionStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googRtt"]) { _connRtt = value; } else if ([key isEqualToString:@"googLocalCandidateType"]) { _localCandType = value; } else if ([key isEqualToString:@"googRemoteCandidateType"]) { _remoteCandType = value; } else if ([key isEqualToString:@"googTransportType"]) { _transportType = value; } else if ([key isEqualToString:@"bytesReceived"]) { NSInteger byteCount = value.integerValue; [_connRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _connRecvBitrate = _connRecvBitrateTracker.bitrateString; } else if ([key isEqualToString:@"bytesSent"]) { NSInteger byteCount = value.integerValue; [_connSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _connSendBitrate = _connSendBitrateTracker.bitrateString; } } - (void)parseConnectionStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { NSString *activeConnection = statsReport.values[@"googActiveConnection"]; if (![activeConnection isEqualToString:@"true"]) { return; } [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateConnectionStatOfKey:key value:value]; }]; } - (void)parseSendSsrcStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { NSDictionary *values = statsReport.values; if ([values objectForKey:@"googFrameRateSent"]) { // Video track. [self parseVideoSendStatsReport:statsReport]; } else if ([values objectForKey:@"audioInputLevel"]) { // Audio track. [self parseAudioSendStatsReport:statsReport]; } } - (void)updateAudioSendStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googCodecName"]) { _audioSendCodec = value; } else if ([key isEqualToString:@"bytesSent"]) { NSInteger byteCount = value.integerValue; [_audioSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _audioSendBitrate = _audioSendBitrateTracker.bitrateString; } } - (void)parseAudioSendStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateAudioSendStatOfKey:key value:value]; }]; } - (void)updateVideoSendStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googCodecName"]) { _videoSendCodec = value; } else if ([key isEqualToString:@"googFrameHeightInput"]) { _videoInputHeight = value; } else if ([key isEqualToString:@"googFrameWidthInput"]) { _videoInputWidth = value; } else if ([key isEqualToString:@"googFrameRateInput"]) { _videoInputFps = value; } else if ([key isEqualToString:@"googFrameHeightSent"]) { _videoSendHeight = value; } else if ([key isEqualToString:@"googFrameWidthSent"]) { _videoSendWidth = value; } else if ([key isEqualToString:@"googFrameRateSent"]) { _videoSendFps = value; } else if ([key isEqualToString:@"googAvgEncodeMs"]) { _videoEncodeMs = value; } else if ([key isEqualToString:@"bytesSent"]) { NSInteger byteCount = value.integerValue; [_videoSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _videoSendBitrate = _videoSendBitrateTracker.bitrateString; } else if ([key isEqualToString:@"qpSum"]) { _oldVideoQPSum = _videoQPSum; _videoQPSum = value.integerValue; } else if ([key isEqualToString:@"framesEncoded"]) { _oldFramesEncoded = _framesEncoded; _framesEncoded = value.integerValue; } } - (void)parseVideoSendStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateVideoSendStatOfKey:key value:value]; }]; } - (void)parseRecvSsrcStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { NSDictionary *values = statsReport.values; if ([values objectForKey:@"googFrameWidthReceived"]) { // Video track. [self parseVideoRecvStatsReport:statsReport]; } else if ([values objectForKey:@"audioOutputLevel"]) { // Audio track. [self parseAudioRecvStatsReport:statsReport]; } } - (void)updateAudioRecvStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googCodecName"]) { _audioRecvCodec = value; } else if ([key isEqualToString:@"bytesReceived"]) { NSInteger byteCount = value.integerValue; [_audioRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _audioRecvBitrate = _audioRecvBitrateTracker.bitrateString; } else if ([key isEqualToString:@"googSpeechExpandRate"]) { _audioExpandRate = value; } else if ([key isEqualToString:@"googCurrentDelayMs"]) { _audioCurrentDelay = value; } } - (void)parseAudioRecvStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateAudioRecvStatOfKey:key value:value]; }]; } - (void)updateVideoRecvStatOfKey:(NSString *)key value:(NSString *)value { if ([key isEqualToString:@"googFrameHeightReceived"]) { _videoRecvHeight = value; } else if ([key isEqualToString:@"googFrameWidthReceived"]) { _videoRecvWidth = value; } else if ([key isEqualToString:@"googFrameRateReceived"]) { _videoRecvFps = value; } else if ([key isEqualToString:@"googFrameRateDecoded"]) { _videoDecodedFps = value; } else if ([key isEqualToString:@"googFrameRateOutput"]) { _videoOutputFps = value; } else if ([key isEqualToString:@"googDecodeMs"]) { _videoDecodeMs = value; } else if ([key isEqualToString:@"bytesReceived"]) { NSInteger byteCount = value.integerValue; [_videoRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount]; _videoRecvBitrate = _videoRecvBitrateTracker.bitrateString; } } - (void)parseVideoRecvStatsReport:(RTC_OBJC_TYPE(RTCLegacyStatsReport) *)statsReport { [statsReport.values enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [self updateVideoRecvStatOfKey:key value:value]; }]; } @end