// Mangle SDP to prefer ISAC/16000 over any other audio codec.
        private string preferISAC(string sdpDescription)
        {
            string[] lines         = sdpDescription.Split("\n", true);
            int      mLineIndex    = -1;
            string   isac16kRtpMap = null;

            Java.Util.Regex.Pattern isac16kPattern = Java.Util.Regex.Pattern.Compile("^a=rtpmap:(\\d+) ISAC/16000[\r]?$");
            for (int i = 0; (i < lines.Length) && (mLineIndex == -1 || isac16kRtpMap == null); ++i)
            {
                if (lines[i].StartsWith("m=audio "))
                {
                    mLineIndex = i;
                    continue;
                }
                Matcher isac16kMatcher = isac16kPattern.Matcher(lines[i]);
                if (isac16kMatcher.Matches())
                {
                    isac16kRtpMap = isac16kMatcher.Group(1);
                    continue;
                }
            }
            if (mLineIndex == -1)
            {
                Log.Debug(TAG, "No m=audio line, so can't prefer iSAC");
                return(sdpDescription);
            }
            if (isac16kRtpMap == null)
            {
                Log.Debug(TAG, "No ISAC/16000 line, so can't prefer iSAC");
                return(sdpDescription);
            }
            string[]      origMLineParts = lines[mLineIndex].Split(" ", true);
            StringBuilder newMLine       = new StringBuilder();
            int           origPartIndex  = 0;

            // Format is: m=<media> <port> <proto> <fmt> ...
            newMLine.Append(origMLineParts[origPartIndex++]).Append(" ");
            newMLine.Append(origMLineParts[origPartIndex++]).Append(" ");
            newMLine.Append(origMLineParts[origPartIndex++]).Append(" ");
            newMLine.Append(isac16kRtpMap).Append(" ");
            for (; origPartIndex < origMLineParts.Length; ++origPartIndex)
            {
                if (!origMLineParts[origPartIndex].Equals(isac16kRtpMap))
                {
                    newMLine.Append(origMLineParts[origPartIndex]).Append(" ");
                }
            }
            lines[mLineIndex] = newMLine.ToString();
            StringBuilder newSdpDescription = new StringBuilder();

            foreach (string line in lines)
            {
                newSdpDescription.Append(line).Append("\n");
            }
            return(newSdpDescription.ToString());
        }
            // Fetches |url| and fishes the signaling parameters out of the HTML via
            // regular expressions.
            //
            // TODO(fischman): replace this hackery with a dedicated JSON-serving URL in
            // apprtc so that this isn't necessary (here and in other future apps that
            // want to interop with apprtc).
            private AppRTCSignalingParameters getParametersForRoomUrl(string url)
            {
                Java.Util.Regex.Pattern fullRoomPattern = Java.Util.Regex.Pattern.Compile(".*\n *Sorry, this room is full\\..*");

                string roomHtml = drainStream((new URL(url)).OpenConnection().InputStream);

                Matcher fullRoomMatcher = fullRoomPattern.Matcher(roomHtml);

                if (fullRoomMatcher.Find())
                {
                    throw new IOException("Room is full!");
                }

                string gaeBaseHref    = url.Substring(0, url.IndexOf('?'));
                string token          = getVarValue(roomHtml, "channelToken", true);
                string postMessageUrl = "/message?r=" + getVarValue(roomHtml, "roomKey", true) + "&u=" + getVarValue(roomHtml, "me", true);
                bool   initiator      = getVarValue(roomHtml, "initiator", false).Equals("1");
                List <PeerConnection.IceServer> iceServers = outerInstance.iceServersFromPCConfigJSON(getVarValue(roomHtml, "pcConfig", false));

                bool isTurnPresent = false;

                foreach (PeerConnection.IceServer server in iceServers)
                {
                    if (server.Uri.StartsWith("turn:"))
                    {
                        isTurnPresent = true;
                        break;
                    }
                }
                if (!isTurnPresent)
                {
                    iceServers.Add(requestTurnServer(getVarValue(roomHtml, "turnUrl", true)));
                }

                MediaConstraints pcConstraints = constraintsFromJSON(getVarValue(roomHtml, "pcConstraints", false));

                Log.Debug(TAG, "pcConstraints: " + pcConstraints);

                MediaConstraints videoConstraints = constraintsFromJSON(getVideoConstraints(getVarValue(roomHtml, "mediaConstraints", false)));

                Log.Debug(TAG, "videoConstraints: " + videoConstraints);

                return(new AppRTCSignalingParameters(outerInstance, iceServers, gaeBaseHref, token, postMessageUrl, initiator, pcConstraints, videoConstraints));
            }
            // Scan |roomHtml| for declaration & assignment of |varName| and return its
            // value, optionally stripping outside quotes if |stripQuotes| requests it.
            private string getVarValue(string roomHtml, string varName, bool stripQuotes)
            {
                Java.Util.Regex.Pattern pattern = Java.Util.Regex.Pattern.Compile(".*\n *var " + varName + " = ([^\n]*);\n.*");
                Matcher matcher = pattern.Matcher(roomHtml);

                if (!matcher.Find())
                {
                    throw new IOException("Missing " + varName + " in HTML: " + roomHtml);
                }
                string varValue = matcher.Group(1);

                if (matcher.Find())
                {
                    throw new IOException("Too many " + varName + " in HTML: " + roomHtml);
                }
                if (stripQuotes)
                {
                    varValue = varValue.Substring(1, varValue.Length - 1 - 1);
                }
                return(varValue);
            }