From c31d4d03244f15681520e65bd48fd0fa5c7821a3 Mon Sep 17 00:00:00 2001 From: "fischman@webrtc.org" Date: Thu, 5 Sep 2013 21:49:58 +0000 Subject: [PATCH] AppRTCDemo(iOS): prefer ISAC as audio codec This makes audio flow well bidirectionally to an iPod Touch (5th gen). Also: - Update to new turnserver JSON style: - separate username field - multiple URLs for the same server (e.g. both UDP & TCP) - Added more explicit logging for ICE Connected since it's useful for debugging - Give focus to the input field on app launch since that's the only useful thing to have focus on, anyway. - Fix minor typos - Cleaned up trailing whitespace and hard tabs BUG=2191 R=wu@webrtc.org Review URL: https://webrtc-codereview.appspot.com/2127004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4687 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/app/webrtc/objc/RTCICEServer.mm | 12 ++- talk/app/webrtc/objc/public/RTCICEServer.h | 9 +- .../examples/ios/AppRTCDemo/APPRTCAppClient.m | 33 +++++--- .../ios/AppRTCDemo/APPRTCAppDelegate.h | 3 +- .../ios/AppRTCDemo/APPRTCAppDelegate.m | 82 ++++++++++++++++++- .../ios/AppRTCDemo/APPRTCViewController.m | 1 + 6 files changed, 116 insertions(+), 24 deletions(-) diff --git a/talk/app/webrtc/objc/RTCICEServer.mm b/talk/app/webrtc/objc/RTCICEServer.mm index cb32673aa..cc5a84a41 100644 --- a/talk/app/webrtc/objc/RTCICEServer.mm +++ b/talk/app/webrtc/objc/RTCICEServer.mm @@ -33,22 +33,25 @@ @implementation RTCICEServer -- (id)initWithURI:(NSURL *)URI password:(NSString *)password { - if (!URI || !password) { +- (id)initWithURI:(NSURL *)URI + username:(NSString *)username + password:(NSString *)password { + if (!URI || !username || !password) { NSAssert(NO, @"nil arguments not allowed"); self = nil; return nil; } if ((self = [super init])) { _URI = URI; + _username = [username copy]; _password = [password copy]; } return self; } - (NSString *)description { - return [NSString stringWithFormat:@"Server: [%@]\nPassword: [%@]", - [self.URI absoluteString], self.password]; + return [NSString stringWithFormat:@"RTCICEServer: [%@:%@:%@]", + [self.URI absoluteString], self.username, self.password]; } @end @@ -58,6 +61,7 @@ - (webrtc::PeerConnectionInterface::IceServer)iceServer { webrtc::PeerConnectionInterface::IceServer iceServer; iceServer.uri = [[self.URI absoluteString] UTF8String]; + iceServer.username = [self.username UTF8String]; iceServer.password = [self.password UTF8String]; return iceServer; } diff --git a/talk/app/webrtc/objc/public/RTCICEServer.h b/talk/app/webrtc/objc/public/RTCICEServer.h index 717d042f7..63c14efaa 100644 --- a/talk/app/webrtc/objc/public/RTCICEServer.h +++ b/talk/app/webrtc/objc/public/RTCICEServer.h @@ -30,14 +30,15 @@ // RTCICEServer allows for the creation of ICEServer structs. @interface RTCICEServer : NSObject -// The server URI. +// The server URI, username, and password. @property(nonatomic, strong, readonly) NSURL* URI; - -// The server password. +@property(nonatomic, copy, readonly) NSString* username; @property(nonatomic, copy, readonly) NSString* password; // Initializer for RTCICEServer taking uri and password. -- (id)initWithURI:(NSString*)URI password:(NSString*)password; +- (id)initWithURI:(NSString*)URI + username:(NSString*)username + password:(NSString*)password; #ifndef DOXYGEN_SHOULD_SKIP_THIS // Disallow init and don't add to documentation diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m index 93f693f45..99f516666 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m +++ b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m @@ -194,14 +194,17 @@ error:&error]; NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); NSString *username = json[@"username"]; - NSString *turnServer = json[@"turn"]; NSString *password = json[@"password"]; - NSString *fullUrl = - [NSString stringWithFormat:@"turn:%@@%@", username, turnServer]; - RTCICEServer *ICEServer = - [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:fullUrl] + NSArray* uris = json[@"uris"]; + for (int i = 0; i < [uris count]; ++i) { + NSString *turnServer = [uris objectAtIndex:i]; + RTCICEServer *ICEServer = + [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer] + username:username password:password]; - [ICEServers addObject:ICEServer]; + NSLog(@"Added ICE Server: %@", ICEServer); + [ICEServers addObject:ICEServer]; + } } else { NSLog(@"Unable to get TURN server. Error: %@", error.description); } @@ -241,9 +244,10 @@ [NSRegularExpression regularExpressionWithPattern:@"room is full" options:0 error:nil]; - if ([fullRegex numberOfMatchesInString:self.roomHtml - options:0 - range:NSMakeRange(0, [self.roomHtml length])]) { + if ([fullRegex + numberOfMatchesInString:self.roomHtml + options:0 + range:NSMakeRange(0, [self.roomHtml length])]) { [self showMessage:@"Room full"]; return; } @@ -252,7 +256,8 @@ NSString *fullUrl = [[[connection originalRequest] URL] absoluteString]; NSRange queryRange = [fullUrl rangeOfString:@"?"]; self.baseURL = [fullUrl substringToIndex:queryRange.location]; - [self maybeLogMessage:[NSString stringWithFormat:@"Base URL: %@", self.baseURL]]; + [self maybeLogMessage: + [NSString stringWithFormat:@"Base URL: %@", self.baseURL]]; self.token = [self findVar:@"channelToken" strippingQuotes:YES]; if (!self.token) @@ -286,11 +291,15 @@ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error]; NSAssert(!error, @"Unable to parse. %@", error.localizedDescription); - NSArray *servers = [json objectForKey:@"ICEServers"]; + NSArray *servers = [json objectForKey:@"iceServers"]; NSMutableArray *ICEServers = [NSMutableArray array]; for (NSDictionary *server in servers) { NSString *url = [server objectForKey:@"url"]; + NSString *username = json[@"username"]; NSString *credential = [server objectForKey:@"credential"]; + if (!username) { + username = @""; + } if (!credential) { credential = @""; } @@ -300,7 +309,9 @@ credential]]; RTCICEServer *ICEServer = [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url] + username:username password:credential]; + NSLog(@"Added ICE Server: %@", ICEServer); [ICEServers addObject:ICEServer]; } [self updateICEServers:ICEServers withTurnServer:turnServerUrl]; diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h index ad1c51276..22754e3ad 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h +++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h @@ -35,7 +35,8 @@ @protocol APPRTCSendMessage - (void)sendData:(NSData *)data; - +// Logging helper. +- (void)displayLogMessage:(NSString *)message; @end @class APPRTCViewController; diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m index 710f4ad5e..34aa7520c 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m +++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m @@ -62,7 +62,7 @@ - (void)peerConnection:(RTCPeerConnection *)peerConnection signalingStateChanged:(RTCSignalingState)stateChanged { - NSLog(@"PCO onSignalingStateChange."); + NSLog(@"PCO onSignalingStateChange: %d", stateChanged); } - (void)peerConnection:(RTCPeerConnection *)peerConnection @@ -119,6 +119,13 @@ - (void)peerConnection:(RTCPeerConnection *)peerConnection iceConnectionChanged:(RTCICEConnectionState)newState { NSLog(@"PCO onIceConnectionChange. %d", newState); + if (newState == RTCICEConnectionConnected) + [self displayLogMessage:@"ICE Connection Connected."]; + NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!"); +} + +- (void)displayLogMessage:(NSString *)message { + [_delegate displayLogMessage:message]; } @end @@ -258,8 +265,8 @@ } else if (([value compare:@"offer"] == NSOrderedSame) || ([value compare:@"answer"] == NSOrderedSame)) { NSString *sdpString = [objects objectForKey:@"sdp"]; - RTCSessionDescription *sdp = - [[RTCSessionDescription alloc] initWithType:value sdp:sdpString]; + RTCSessionDescription *sdp = [[RTCSessionDescription alloc] + initWithType:value sdp:[APPRTCAppDelegate preferISAC:sdpString]]; [self.peerConnection setRemoteDescriptionWithDelegate:self sessionDescription:sdp]; [self displayLogMessage:@"PC - setRemoteDescription."]; @@ -283,8 +290,71 @@ #pragma mark - RTCSessionDescriptonDelegate methods +// Match |pattern| to |string| and return the first group of the first +// match, or nil if no match was found. ++ (NSString *)firstMatch:(NSRegularExpression *)pattern + withString:(NSString *)string { + NSTextCheckingResult* result = + [pattern firstMatchInString:string + options:0 + range:NSMakeRange(0, [string length])]; + if (!result) + return nil; + return [string substringWithRange:[result rangeAtIndex:1]]; +} + +// Mangle |origSDP| to prefer the ISAC/16k audio codec. ++ (NSString *)preferISAC:(NSString *)origSDP { + int mLineIndex = -1; + NSString* isac16kRtpMap = nil; + NSArray* lines = [origSDP componentsSeparatedByString:@"\n"]; + NSRegularExpression* isac16kRegex = [NSRegularExpression + regularExpressionWithPattern:@"^a=rtpmap:(\\d+) ISAC/16000[\r]?$" + options:0 + error:nil]; + for (int i = 0; + (i < [lines count]) && (mLineIndex == -1 || isac16kRtpMap == nil); + ++i) { + NSString* line = [lines objectAtIndex:i]; + if ([line hasPrefix:@"m=audio "]) { + mLineIndex = i; + continue; + } + isac16kRtpMap = [self firstMatch:isac16kRegex withString:line]; + } + if (mLineIndex == -1) { + NSLog(@"No m=audio line, so can't prefer iSAC"); + return origSDP; + } + if (isac16kRtpMap == nil) { + NSLog(@"No ISAC/16000 line, so can't prefer iSAC"); + return origSDP; + } + NSArray* origMLineParts = + [[lines objectAtIndex:mLineIndex] componentsSeparatedByString:@" "]; + NSMutableArray* newMLine = + [NSMutableArray arrayWithCapacity:[origMLineParts count]]; + int origPartIndex = 0; + // Format is: m= ... + [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]]; + [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]]; + [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]]; + [newMLine addObject:isac16kRtpMap]; + for (; origPartIndex < [origMLineParts count]; ++origPartIndex) { + if ([isac16kRtpMap compare:[origMLineParts objectAtIndex:origPartIndex]] + != NSOrderedSame) { + [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex]]; + } + } + NSMutableArray* newLines = [NSMutableArray arrayWithCapacity:[lines count]]; + [newLines addObjectsFromArray:lines]; + [newLines replaceObjectAtIndex:mLineIndex + withObject:[newMLine componentsJoinedByString:@" "]]; + return [newLines componentsJoinedByString:@"\n"]; +} + - (void)peerConnection:(RTCPeerConnection *)peerConnection - didCreateSessionDescription:(RTCSessionDescription *)sdp + didCreateSessionDescription:(RTCSessionDescription *)origSdp error:(NSError *)error { if (error) { [self displayLogMessage:@"SDP onFailure."]; @@ -293,6 +363,10 @@ } [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."]; + RTCSessionDescription* sdp = + [[RTCSessionDescription alloc] + initWithType:origSdp.type + sdp:[APPRTCAppDelegate preferISAC:origSdp.description]]; [self.peerConnection setLocalDescriptionWithDelegate:self sessionDescription:sdp]; [self displayLogMessage:@"PC setLocalDescription."]; diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m index 928686b68..ab84c0932 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m +++ b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m @@ -36,6 +36,7 @@ - (void)viewDidLoad { [super viewDidLoad]; self.textField.delegate = self; + [self.textField becomeFirstResponder]; } - (void)displayText:(NSString *)text {