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/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."];