Add stats overlay to iOS AppRTCDemo.
BUG= R=jiayl@webrtc.org Review URL: https://codereview.webrtc.org/1289623005 . Cr-Commit-Position: refs/heads/master@{#9714}
This commit is contained in:
parent
60d9b332a5
commit
d33258098b
@ -17,9 +17,10 @@
|
||||
#import "RTCPeerConnectionDelegate.h"
|
||||
#import "RTCPeerConnectionFactory.h"
|
||||
#import "RTCSessionDescriptionDelegate.h"
|
||||
#import "RTCStatsDelegate.h"
|
||||
|
||||
@interface ARDAppClient () <ARDSignalingChannelDelegate,
|
||||
RTCPeerConnectionDelegate, RTCSessionDescriptionDelegate>
|
||||
RTCPeerConnectionDelegate, RTCSessionDescriptionDelegate, RTCStatsDelegate>
|
||||
|
||||
// All properties should only be mutated from the main queue.
|
||||
@property(nonatomic, strong) id<ARDRoomServerClient> roomServerClient;
|
||||
|
||||
@ -41,12 +41,18 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) {
|
||||
- (void)appClient:(ARDAppClient *)client
|
||||
didError:(NSError *)error;
|
||||
|
||||
- (void)appClient:(ARDAppClient *)client
|
||||
didGetStats:(NSArray *)stats;
|
||||
|
||||
@end
|
||||
|
||||
// Handles connections to the AppRTC server for a given room. Methods on this
|
||||
// class should only be called from the main queue.
|
||||
@interface ARDAppClient : NSObject
|
||||
|
||||
// If |shouldGetStats| is true, stats will be reported in 1s intervals through
|
||||
// the delegate.
|
||||
@property(nonatomic, assign) BOOL shouldGetStats;
|
||||
@property(nonatomic, readonly) ARDAppClientState state;
|
||||
@property(nonatomic, weak) id<ARDAppClientDelegate> delegate;
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
#import "RTCICECandidate+JSON.h"
|
||||
#import "RTCSessionDescription+JSON.h"
|
||||
|
||||
|
||||
static NSString * const kARDDefaultSTUNServerUrl =
|
||||
@"stun:stun.l.google.com:19302";
|
||||
// TODO(tkchin): figure out a better username for CEOD statistics.
|
||||
@ -49,12 +48,55 @@ static NSInteger const kARDAppClientErrorSetSDP = -4;
|
||||
static NSInteger const kARDAppClientErrorInvalidClient = -5;
|
||||
static NSInteger const kARDAppClientErrorInvalidRoom = -6;
|
||||
|
||||
@implementation ARDAppClient {
|
||||
RTCFileLogger *_fileLogger;
|
||||
// We need a proxy to NSTimer because it causes a strong retain cycle. When
|
||||
// using the proxy, |invalidate| must be called before it properly deallocs.
|
||||
@interface ARDTimerProxy : NSObject
|
||||
|
||||
- (instancetype)initWithInterval:(NSTimeInterval)interval
|
||||
repeats:(BOOL)repeats
|
||||
timerHandler:(void (^)(void))timerHandler;
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ARDTimerProxy {
|
||||
NSTimer *_timer;
|
||||
void (^_timerHandler)(void);
|
||||
}
|
||||
|
||||
@synthesize delegate = _delegate;
|
||||
- (instancetype)initWithInterval:(NSTimeInterval)interval
|
||||
repeats:(BOOL)repeats
|
||||
timerHandler:(void (^)(void))timerHandler {
|
||||
NSParameterAssert(timerHandler);
|
||||
if (self = [super init]) {
|
||||
_timerHandler = timerHandler;
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:interval
|
||||
target:self
|
||||
selector:@selector(timerDidFire:)
|
||||
userInfo:nil
|
||||
repeats:repeats];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invalidate {
|
||||
[_timer invalidate];
|
||||
}
|
||||
|
||||
- (void)timerDidFire:(NSTimer *)timer {
|
||||
_timerHandler();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ARDAppClient {
|
||||
RTCFileLogger *_fileLogger;
|
||||
ARDTimerProxy *_statsTimer;
|
||||
}
|
||||
|
||||
@synthesize shouldGetStats = _shouldGetStats;
|
||||
@synthesize state = _state;
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize roomServerClient = _roomServerClient;
|
||||
@synthesize channel = _channel;
|
||||
@synthesize turnClient = _turnClient;
|
||||
@ -122,9 +164,31 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.shouldGetStats = NO;
|
||||
[self disconnect];
|
||||
}
|
||||
|
||||
- (void)setShouldGetStats:(BOOL)shouldGetStats {
|
||||
if (_shouldGetStats == shouldGetStats) {
|
||||
return;
|
||||
}
|
||||
if (shouldGetStats) {
|
||||
__weak ARDAppClient *weakSelf = self;
|
||||
_statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
|
||||
repeats:YES
|
||||
timerHandler:^{
|
||||
ARDAppClient *strongSelf = weakSelf;
|
||||
[strongSelf.peerConnection getStatsWithDelegate:strongSelf
|
||||
mediaStreamTrack:nil
|
||||
statsOutputLevel:RTCStatsOutputLevelDebug];
|
||||
}];
|
||||
} else {
|
||||
[_statsTimer invalidate];
|
||||
_statsTimer = nil;
|
||||
}
|
||||
_shouldGetStats = shouldGetStats;
|
||||
}
|
||||
|
||||
- (void)setState:(ARDAppClientState)state {
|
||||
if (_state == state) {
|
||||
return;
|
||||
@ -309,8 +373,17 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peerConnection:(RTCPeerConnection*)peerConnection
|
||||
didOpenDataChannel:(RTCDataChannel*)dataChannel {
|
||||
- (void)peerConnection:(RTCPeerConnection *)peerConnection
|
||||
didOpenDataChannel:(RTCDataChannel *)dataChannel {
|
||||
}
|
||||
|
||||
#pragma mark - RTCStatsDelegate
|
||||
|
||||
- (void)peerConnection:(RTCPeerConnection *)peerConnection
|
||||
didGetStats:(NSArray *)stats {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate appClient:self didGetStats:stats];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - RTCSessionDescriptionDelegate
|
||||
|
||||
30
webrtc/examples/objc/AppRTCDemo/ARDBitrateTracker.h
Normal file
30
webrtc/examples/objc/AppRTCDemo/ARDBitrateTracker.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
/** Class used to estimate bitrate based on byte count. It is expected that
|
||||
* byte count is monotonocially increasing. This class tracks the times that
|
||||
* byte count is updated, and measures the bitrate based on the byte difference
|
||||
* over the interval between updates.
|
||||
*/
|
||||
@interface ARDBitrateTracker : NSObject
|
||||
|
||||
/** The bitrate in bits per second. */
|
||||
@property(nonatomic, readonly) double bitrate;
|
||||
/** The bitrate as a formatted string in bps, Kbps or Mbps. */
|
||||
@property(nonatomic, readonly) NSString *bitrateString;
|
||||
|
||||
/** Converts the bitrate to a readable format in bps, Kbps or Mbps. */
|
||||
+ (NSString *)bitrateStringForBitrate:(double)bitrate;
|
||||
/** Updates the tracked bitrate with the new byte count. */
|
||||
- (void)updateBitrateWithCurrentByteCount:(NSInteger)byteCount;
|
||||
|
||||
@end
|
||||
45
webrtc/examples/objc/AppRTCDemo/ARDBitrateTracker.m
Normal file
45
webrtc/examples/objc/AppRTCDemo/ARDBitrateTracker.m
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 "ARDBitrateTracker.h"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@implementation ARDBitrateTracker {
|
||||
CFTimeInterval _prevTime;
|
||||
NSInteger _prevByteCount;
|
||||
}
|
||||
|
||||
@synthesize bitrate = _bitrate;
|
||||
|
||||
+ (NSString *)bitrateStringForBitrate:(double)bitrate {
|
||||
if (bitrate > 1e6) {
|
||||
return [NSString stringWithFormat:@"%.2fMbps", bitrate * 1e-6];
|
||||
} else if (bitrate > 1e3) {
|
||||
return [NSString stringWithFormat:@"%.0fKbps", bitrate * 1e-3];
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"%.0fbps", bitrate];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)bitrateString {
|
||||
return [[self class] bitrateStringForBitrate:_bitrate];
|
||||
}
|
||||
|
||||
- (void)updateBitrateWithCurrentByteCount:(NSInteger)byteCount {
|
||||
CFTimeInterval currentTime = CACurrentMediaTime();
|
||||
if (_prevTime && (byteCount > _prevByteCount)) {
|
||||
_bitrate = (byteCount - _prevByteCount) * 8 / (currentTime - _prevTime);
|
||||
}
|
||||
_prevByteCount = byteCount;
|
||||
_prevTime = currentTime;
|
||||
}
|
||||
|
||||
@end
|
||||
29
webrtc/examples/objc/AppRTCDemo/ARDStatsBuilder.h
Normal file
29
webrtc/examples/objc/AppRTCDemo/ARDStatsBuilder.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class RTCStatsReport;
|
||||
|
||||
/** Class used to accumulate stats information into a single displayable string.
|
||||
*/
|
||||
@interface ARDStatsBuilder : NSObject
|
||||
|
||||
/** String that represents the accumulated stats reports passed into this
|
||||
* class.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSString *statsString;
|
||||
|
||||
/** Parses the information in the stats report into an appropriate internal
|
||||
* format used to generate the stats string.
|
||||
*/
|
||||
- (void)parseStatsReport:(RTCStatsReport *)statsReport;
|
||||
|
||||
@end
|
||||
321
webrtc/examples/objc/AppRTCDemo/ARDStatsBuilder.m
Normal file
321
webrtc/examples/objc/AppRTCDemo/ARDStatsBuilder.m
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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 "RTCPair.h"
|
||||
#import "RTCStatsReport.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;
|
||||
|
||||
// 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];
|
||||
}
|
||||
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";
|
||||
[result appendString:[NSString stringWithFormat:videoSendFormat,
|
||||
_videoInputWidth, _videoInputHeight, _videoInputFps,
|
||||
_videoSendWidth, _videoSendHeight, _videoSendFps,
|
||||
_actualEncBitrate, _targetEncBitrate,
|
||||
_videoSendBitrate, _availableSendBw,
|
||||
_videoEncodeMs,
|
||||
_videoSendCodec]];
|
||||
|
||||
// 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:(RTCStatsReport *)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
|
||||
|
||||
- (void)parseBweStatsReport:(RTCStatsReport *)statsReport {
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.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)parseConnectionStatsReport:(RTCStatsReport *)statsReport {
|
||||
NSDictionary *values = [self dictionaryForReport:statsReport];
|
||||
NSString *activeConnection = [values[@"googActiveConnection"] firstObject];
|
||||
if (![activeConnection isEqualToString:@"true"]) {
|
||||
return;
|
||||
}
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.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)parseSendSsrcStatsReport:(RTCStatsReport *)statsReport {
|
||||
NSDictionary *values = [self dictionaryForReport:statsReport];
|
||||
NSString *trackId = [values[@"googTrackId"] firstObject];
|
||||
if (trackId.length && [trackId hasPrefix:@"ARDAMSv0"]) {
|
||||
// Video track.
|
||||
[self parseVideoSendStatsReport:statsReport];
|
||||
} else {
|
||||
// Audio track.
|
||||
[self parseAudioSendStatsReport:statsReport];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parseAudioSendStatsReport:(RTCStatsReport *)statsReport {
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.value;
|
||||
if ([key isEqualToString:@"googCodecName"]) {
|
||||
_audioSendCodec = value;
|
||||
} else if ([key isEqualToString:@"bytesSent"]) {
|
||||
NSInteger byteCount = value.integerValue;
|
||||
[_audioSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
|
||||
_audioSendBitrate = _audioSendBitrateTracker.bitrateString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parseVideoSendStatsReport:(RTCStatsReport *)statsReport {
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parseRecvSsrcStatsReport:(RTCStatsReport *)statsReport {
|
||||
NSDictionary *values = [self dictionaryForReport:statsReport];
|
||||
NSString *transportId = [values[@"transportId"] firstObject];
|
||||
if ([values[@"googFrameWidthReceived"] firstObject]) {
|
||||
[self parseVideoRecvStatsReport:statsReport];
|
||||
} else {
|
||||
[self parseAudioRecvStatsReport:statsReport];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parseAudioRecvStatsReport:(RTCStatsReport *)statsReport {
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.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)parseVideoRecvStatsReport:(RTCStatsReport *)statsReport {
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSString *key = pair.key;
|
||||
NSString *value = pair.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryForReport:(RTCStatsReport *)statsReport {
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
for (RTCPair *pair in statsReport.values) {
|
||||
NSMutableArray *values = dict[pair.key];
|
||||
if (!values) {
|
||||
values = [NSMutableArray arrayWithCapacity:1];
|
||||
dict[pair.key] = values;
|
||||
}
|
||||
[values addObject:pair.value];
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -33,3 +33,6 @@
|
||||
NSData *data))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
NSInteger ARDGetCpuUsagePercentage();
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
#import "ARDUtilities.h"
|
||||
|
||||
#import <mach/mach.h>
|
||||
|
||||
#import "RTCLogging.h"
|
||||
|
||||
@implementation NSDictionary (ARDUtilites)
|
||||
@ -93,3 +95,34 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSInteger ARDGetCpuUsagePercentage() {
|
||||
// Create an array of thread ports for the current task.
|
||||
const task_t task = mach_task_self();
|
||||
thread_act_array_t thread_array;
|
||||
mach_msg_type_number_t thread_count;
|
||||
if (task_threads(task, &thread_array, &thread_count) != KERN_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Sum cpu usage from all threads.
|
||||
float cpu_usage_percentage = 0;
|
||||
thread_basic_info_data_t thread_info_data = {};
|
||||
mach_msg_type_number_t thread_info_count;
|
||||
for (size_t i = 0; i < thread_count; ++i) {
|
||||
thread_info_count = THREAD_BASIC_INFO_COUNT;
|
||||
kern_return_t ret = thread_info(thread_array[i],
|
||||
THREAD_BASIC_INFO,
|
||||
(thread_info_t)&thread_info_data,
|
||||
&thread_info_count);
|
||||
if (ret == KERN_SUCCESS) {
|
||||
cpu_usage_percentage +=
|
||||
100.f * (float)thread_info_data.cpu_usage / TH_USAGE_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
// Dealloc the created array.
|
||||
vm_deallocate(task, (vm_address_t)thread_array,
|
||||
sizeof(thread_act_t) * thread_count);
|
||||
return lroundf(cpu_usage_percentage);
|
||||
}
|
||||
|
||||
17
webrtc/examples/objc/AppRTCDemo/ios/ARDStatsView.h
Normal file
17
webrtc/examples/objc/AppRTCDemo/ios/ARDStatsView.h
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
@interface ARDStatsView : UIView
|
||||
|
||||
- (void)setStats:(NSArray *)stats;
|
||||
|
||||
@end
|
||||
52
webrtc/examples/objc/AppRTCDemo/ios/ARDStatsView.m
Normal file
52
webrtc/examples/objc/AppRTCDemo/ios/ARDStatsView.m
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 "ARDStatsView.h"
|
||||
|
||||
#import "RTCStatsReport.h"
|
||||
|
||||
#import "ARDStatsBuilder.h"
|
||||
|
||||
@implementation ARDStatsView {
|
||||
UILabel *_statsLabel;
|
||||
ARDStatsBuilder *_statsBuilder;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_statsLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
_statsLabel.numberOfLines = 0;
|
||||
_statsLabel.font = [UIFont fontWithName:@"Roboto" size:12];
|
||||
_statsLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_statsLabel.minimumScaleFactor = 0.6;
|
||||
_statsLabel.textColor = [UIColor greenColor];
|
||||
[self addSubview:_statsLabel];
|
||||
self.backgroundColor = [UIColor colorWithWhite:0 alpha:.6];
|
||||
_statsBuilder = [[ARDStatsBuilder alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setStats:(NSArray *)stats {
|
||||
for (RTCStatsReport *report in stats) {
|
||||
[_statsBuilder parseStatsReport:report];
|
||||
}
|
||||
_statsLabel.text = _statsBuilder.statsString;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
_statsLabel.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
return [_statsLabel sizeThatFits:size];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -12,6 +12,8 @@
|
||||
|
||||
#import "RTCEAGLVideoView.h"
|
||||
|
||||
#import "ARDStatsView.h"
|
||||
|
||||
@class ARDVideoCallView;
|
||||
@protocol ARDVideoCallViewDelegate <NSObject>
|
||||
|
||||
@ -21,6 +23,9 @@
|
||||
// Called when the hangup button is pressed.
|
||||
- (void)videoCallViewDidHangup:(ARDVideoCallView *)view;
|
||||
|
||||
// Called when stats are enabled by triple tapping.
|
||||
- (void)videoCallViewDidEnableStats:(ARDVideoCallView *)view;
|
||||
|
||||
@end
|
||||
|
||||
// Video call view that shows local and remote video, provides a label to
|
||||
@ -30,6 +35,7 @@
|
||||
@property(nonatomic, readonly) UILabel *statusLabel;
|
||||
@property(nonatomic, readonly) RTCEAGLVideoView *localVideoView;
|
||||
@property(nonatomic, readonly) RTCEAGLVideoView *remoteVideoView;
|
||||
@property(nonatomic, readonly) ARDStatsView *statsView;
|
||||
@property(nonatomic, weak) id<ARDVideoCallViewDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
@ -17,6 +17,7 @@ static CGFloat const kButtonPadding = 16;
|
||||
static CGFloat const kButtonSize = 48;
|
||||
static CGFloat const kLocalVideoViewSize = 120;
|
||||
static CGFloat const kLocalVideoViewPadding = 8;
|
||||
static CGFloat const kStatusBarHeight = 20;
|
||||
|
||||
@interface ARDVideoCallView () <RTCEAGLVideoViewDelegate>
|
||||
@end
|
||||
@ -32,6 +33,7 @@ static CGFloat const kLocalVideoViewPadding = 8;
|
||||
@synthesize statusLabel = _statusLabel;
|
||||
@synthesize localVideoView = _localVideoView;
|
||||
@synthesize remoteVideoView = _remoteVideoView;
|
||||
@synthesize statsView = _statsView;
|
||||
@synthesize delegate = _delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@ -46,6 +48,10 @@ static CGFloat const kLocalVideoViewPadding = 8;
|
||||
_localVideoView.delegate = self;
|
||||
[self addSubview:_localVideoView];
|
||||
|
||||
_statsView = [[ARDStatsView alloc] initWithFrame:CGRectZero];
|
||||
_statsView.hidden = YES;
|
||||
[self addSubview:_statsView];
|
||||
|
||||
// TODO(tkchin): don't display this if we can't actually do camera switch.
|
||||
_cameraSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_cameraSwitchButton.backgroundColor = [UIColor whiteColor];
|
||||
@ -74,6 +80,13 @@ static CGFloat const kLocalVideoViewPadding = 8;
|
||||
_statusLabel.font = [UIFont fontWithName:@"Roboto" size:16];
|
||||
_statusLabel.textColor = [UIColor whiteColor];
|
||||
[self addSubview:_statusLabel];
|
||||
|
||||
UITapGestureRecognizer *tapRecognizer =
|
||||
[[UITapGestureRecognizer alloc]
|
||||
initWithTarget:self
|
||||
action:@selector(didTripleTap:)];
|
||||
tapRecognizer.numberOfTapsRequired = 3;
|
||||
[self addGestureRecognizer:tapRecognizer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -118,6 +131,12 @@ static CGFloat const kLocalVideoViewPadding = 8;
|
||||
_localVideoView.frame = bounds;
|
||||
}
|
||||
|
||||
// Place stats at the top.
|
||||
CGSize statsSize = [_statsView sizeThatFits:bounds.size];
|
||||
_statsView.frame = CGRectMake(CGRectGetMinX(bounds),
|
||||
CGRectGetMinY(bounds) + kStatusBarHeight,
|
||||
statsSize.width, statsSize.height);
|
||||
|
||||
// Place hangup button in the bottom left.
|
||||
_hangupButton.frame =
|
||||
CGRectMake(CGRectGetMinX(bounds) + kButtonPadding,
|
||||
@ -159,4 +178,8 @@ static CGFloat const kLocalVideoViewPadding = 8;
|
||||
[_delegate videoCallViewDidHangup:self];
|
||||
}
|
||||
|
||||
- (void)didTripleTap:(UITapGestureRecognizer *)recognizer {
|
||||
[_delegate videoCallViewDidEnableStats:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -87,6 +87,12 @@
|
||||
_videoCallView.statusLabel.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)appClient:(ARDAppClient *)client
|
||||
didGetStats:(NSArray *)stats {
|
||||
_videoCallView.statsView.stats = stats;
|
||||
[_videoCallView setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)appClient:(ARDAppClient *)client
|
||||
didError:(NSError *)error {
|
||||
NSString *message =
|
||||
@ -107,6 +113,11 @@
|
||||
[self switchCamera];
|
||||
}
|
||||
|
||||
- (void)videoCallViewDidEnableStats:(ARDVideoCallView *)view {
|
||||
_client.shouldGetStats = YES;
|
||||
_videoCallView.statsView.hidden = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)setLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
|
||||
|
||||
@ -270,6 +270,10 @@ static NSUInteger const kLogViewHeight = 280;
|
||||
[self disconnect];
|
||||
}
|
||||
|
||||
- (void)appClient:(ARDAppClient *)client
|
||||
didGetStats:(NSArray *)stats {
|
||||
}
|
||||
|
||||
#pragma mark - APPRTCMainViewDelegate
|
||||
|
||||
- (void)appRTCMainView:(APPRTCMainView*)mainView
|
||||
|
||||
@ -160,6 +160,13 @@
|
||||
},
|
||||
}],
|
||||
],
|
||||
'link_settings': {
|
||||
'xcode_settings': {
|
||||
'OTHER_LDFLAGS': [
|
||||
'-framework QuartzCore',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'target_name': 'apprtc_signaling',
|
||||
@ -175,6 +182,8 @@
|
||||
'examples/objc/AppRTCDemo/ARDAppClient+Internal.h',
|
||||
'examples/objc/AppRTCDemo/ARDAppEngineClient.h',
|
||||
'examples/objc/AppRTCDemo/ARDAppEngineClient.m',
|
||||
'examples/objc/AppRTCDemo/ARDBitrateTracker.h',
|
||||
'examples/objc/AppRTCDemo/ARDBitrateTracker.m',
|
||||
'examples/objc/AppRTCDemo/ARDCEODTURNClient.h',
|
||||
'examples/objc/AppRTCDemo/ARDCEODTURNClient.m',
|
||||
'examples/objc/AppRTCDemo/ARDJoinResponse.h',
|
||||
@ -189,6 +198,8 @@
|
||||
'examples/objc/AppRTCDemo/ARDSignalingChannel.h',
|
||||
'examples/objc/AppRTCDemo/ARDSignalingMessage.h',
|
||||
'examples/objc/AppRTCDemo/ARDSignalingMessage.m',
|
||||
'examples/objc/AppRTCDemo/ARDStatsBuilder.h',
|
||||
'examples/objc/AppRTCDemo/ARDStatsBuilder.m',
|
||||
'examples/objc/AppRTCDemo/ARDTURNClient.h',
|
||||
'examples/objc/AppRTCDemo/ARDWebSocketChannel.h',
|
||||
'examples/objc/AppRTCDemo/ARDWebSocketChannel.m',
|
||||
@ -251,6 +262,8 @@
|
||||
'examples/objc/AppRTCDemo/ios/ARDMainView.m',
|
||||
'examples/objc/AppRTCDemo/ios/ARDMainViewController.h',
|
||||
'examples/objc/AppRTCDemo/ios/ARDMainViewController.m',
|
||||
'examples/objc/AppRTCDemo/ios/ARDStatsView.h',
|
||||
'examples/objc/AppRTCDemo/ios/ARDStatsView.m',
|
||||
'examples/objc/AppRTCDemo/ios/ARDVideoCallView.h',
|
||||
'examples/objc/AppRTCDemo/ios/ARDVideoCallView.m',
|
||||
'examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.h',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user