• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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