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