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
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m
index 93f693f..99f5166 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 ad1c512..22754e3 100644
--- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h
@@ -35,7 +35,8 @@
 @protocol APPRTCSendMessage<NSObject>
 
 - (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 710f4ad..34aa752 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=<media> <port> <proto> <fmt> ...
+  [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 928686b..ab84c09 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 {