public SDPMediaAnnouncement(SDPMediaTypesEnum mediaType, SDPConnectionInformation connection, int port, SDPMessageMediaFormat messageMediaFormat) { Media = mediaType; Port = port; Connection = connection; MessageMediaFormat = messageMediaFormat; }
public static SDPConnectionInformation ParseConnectionInformation(string connectionLine) { SDPConnectionInformation connectionInfo = new SDPConnectionInformation(); string[] connectionFields = connectionLine.Substring(2).Trim().Split(' '); connectionInfo.ConnectionNetworkType = connectionFields[0].Trim(); connectionInfo.ConnectionAddressType = connectionFields[1].Trim(); connectionInfo.ConnectionAddress = connectionFields[2].Trim(); return connectionInfo; }
public static SDPConnectionInformation ParseConnectionInformation(string connectionLine) { SDPConnectionInformation connectionInfo = new SDPConnectionInformation(); string[] connectionFields = connectionLine.Substring(2).Trim().Split(' '); connectionInfo.ConnectionNetworkType = connectionFields[0].Trim(); connectionInfo.ConnectionAddressType = connectionFields[1].Trim(); connectionInfo.ConnectionAddress = connectionFields[2].Trim(); return(connectionInfo); }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); sdp.m_rawSdp = sdpDescription; SDPMediaAnnouncement activeAnnouncement = null; string[] sdpLines = Regex.Split(sdpDescription, CRLF); foreach (string sdpLine in sdpLines) { string sdpLineTrimmed = sdpLine.Trim(); if (sdpLineTrimmed.StartsWith("v=")) { if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version)) { logger.LogWarning( "The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + "."); } } else if (sdpLineTrimmed.StartsWith("o=")) { string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' '); sdp.Username = ownerFields[0]; sdp.SessionId = ownerFields[1]; Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion); sdp.NetworkType = ownerFields[3]; sdp.AddressType = ownerFields[4]; sdp.AddressOrHost = ownerFields[5]; } else if (sdpLineTrimmed.StartsWith("s=")) { sdp.SessionName = sdpLineTrimmed.Substring(2); } else if (sdpLineTrimmed.StartsWith("c=")) { if (sdp.Connection == null) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } if (activeAnnouncement != null) { activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } } else if (sdpLineTrimmed.StartsWith("b=")) { if (activeAnnouncement != null) { activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } else { sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } } else if (sdpLineTrimmed.StartsWith("t=")) { sdp.Timing = sdpLineTrimmed.Substring(2); } else if (sdpLineTrimmed.StartsWith("m=")) { Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$"); if (mediaMatch.Success) { SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port); announcement.Transport = mediaMatch.Result("${transport}"); announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); sdp.Media.Add(announcement); activeAnnouncement = announcement; } else { logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + "."); } } else if (sdpLineTrimmed.StartsWith("a=" + GROUP_ATRIBUTE_PREFIX)) { sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else if (sdpLineTrimmed.StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX)) { if (activeAnnouncement != null) { activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } } else if (sdpLineTrimmed.StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX)) { if (activeAnnouncement != null) { activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } } else if (sdpLineTrimmed.StartsWith("a=" + DTLS_FINGERPRINT_ATTRIBUTE_PREFIX)) { if (activeAnnouncement != null) { activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } } else if (sdpLineTrimmed.StartsWith($"a={END_ICE_CANDIDATES_ATTRIBUTE}")) { // Do nothing. } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning( "There was no active media announcement for a media format attribute, ignoring."); } } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX) ) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatParameterAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning( "There was no active media announcement for a media format parameter attribute, ignoring."); } } else if (sdpLineTrimmed.StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX)) { if (activeAnnouncement != null) { if (activeAnnouncement.IceCandidates == null) { activeAnnouncement.IceCandidates = new List <string>(); } activeAnnouncement.IceCandidates.Add( sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } else { if (sdp.IceCandidates == null) { sdp.IceCandidates = new List <string>(); } sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } } //2018-12-21 rj2: add a=crypto else if (sdpLineTrimmed.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { try { activeAnnouncement.AddCryptoLine(sdpLineTrimmed); } catch (FormatException fex) { logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex); } } } else if (MediaStreamStatusType.IsMediaStreamStatusAttribute(sdpLine.Trim(), out var mediaStreamStatus)) { if (activeAnnouncement != null) { activeAnnouncement.MediaStreamStatus = mediaStreamStatus; } else { sdp.SessionMediaStreamStatus = mediaStreamStatus; } } else if (sdpLineTrimmed.StartsWith("a=" + MEDIA_ID_ATTRIBUTE_PREFIX)) { if (activeAnnouncement != null) { activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { logger.LogWarning("A media ID can only be set on a media announcement."); } } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX) ) { if (activeAnnouncement != null) { string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); // Set the ID. if (fields.Length > 0) { activeAnnouncement.SsrcGroupID = fields[0]; } // Add attributes for each of the SSRC values. for (int i = 1; i < fields.Length; i++) { if (uint.TryParse(fields[i], out var ssrc)) { activeAnnouncement.SsrcAttributes.Add( new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID)); } } } else { logger.LogWarning("A ssrc-group ID can only be set on a media announcement."); } } else if (sdpLineTrimmed.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1) .Split(' '); if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc)) { var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc); if (ssrcAttribute == null) { ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null); activeAnnouncement.SsrcAttributes.Add(ssrcAttribute); } if (ssrcFields.Length > 1) { if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX)) { ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1); } } } } else { logger.LogWarning("A ssrc attribute can only be set on a media announcement."); } } else { if (activeAnnouncement != null) { activeAnnouncement.AddExtra(sdpLineTrimmed); } else { sdp.AddExtra(sdpLineTrimmed); } } } return(sdp); } else { return(null); } } catch (Exception excp) { logger.LogError("Exception ParseSDPDescription. " + excp.Message); throw excp; } }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); sdp.m_rawSdp = sdpDescription; int mLineIndex = 0; SDPMediaAnnouncement activeAnnouncement = null; // If a media announcement fmtp atribute is found before the rtpmap it will be stored // in this dictionary. A dynamic media format type cannot be created without an rtpmap. Dictionary <int, string> _pendingFmtp = new Dictionary <int, string>(); //string[] sdpLines = Regex.Split(sdpDescription, CRLF); string[] sdpLines = sdpDescription.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string sdpLine in sdpLines) { string sdpLineTrimmed = sdpLine.Trim(); switch (sdpLineTrimmed) { case var l when l.StartsWith("v="): if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version)) { logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + "."); } break; case var l when l.StartsWith("o="): string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' '); sdp.Username = ownerFields[0]; sdp.SessionId = ownerFields[1]; Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion); sdp.NetworkType = ownerFields[3]; sdp.AddressType = ownerFields[4]; sdp.AddressOrHost = ownerFields[5]; break; case var l when l.StartsWith("s="): sdp.SessionName = sdpLineTrimmed.Substring(2); break; case var l when l.StartsWith("i="): if (activeAnnouncement != null) { activeAnnouncement.MediaDescription = sdpLineTrimmed.Substring(2); } else { sdp.SessionDescription = sdpLineTrimmed.Substring(2); } break; case var l when l.StartsWith("c="): if (activeAnnouncement != null) { activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } else if (sdp.Connection == null) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } else { logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored."); } break; case var l when l.StartsWith("b="): if (activeAnnouncement != null) { if (l.StartsWith(SDPMediaAnnouncement.TIAS_BANDWIDTH_ATTRIBUE_PREFIX)) { if (uint.TryParse(l.Substring(l.IndexOf(':') + 1), out uint tias)) { activeAnnouncement.TIASBandwidth = tias; } } else { activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } } else { sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } break; case var l when l.StartsWith("t="): sdp.Timing = sdpLineTrimmed.Substring(2); break; case var l when l.StartsWith("m="): Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$"); if (mediaMatch.Success) { SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); announcement.MLineIndex = mLineIndex; announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port); announcement.Transport = mediaMatch.Result("${transport}"); announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); if (announcement.Media == SDPMediaTypesEnum.audio || announcement.Media == SDPMediaTypesEnum.video) { announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value : MediaStreamStatusEnum.SendRecv; } sdp.Media.Add(announcement); activeAnnouncement = announcement; } else { logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + "."); } mLineIndex++; break; case var x when x.StartsWith($"a={GROUP_ATRIBUTE_PREFIX}"): sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); break; case var x when x.StartsWith($"a={ICE_UFRAG_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={ICE_PWD_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={ICE_SETUP_ATTRIBUTE_PREFIX}"): int colonIndex = sdpLineTrimmed.IndexOf(':'); if (colonIndex != -1 && sdpLineTrimmed.Length > colonIndex) { string iceRoleStr = sdpLineTrimmed.Substring(colonIndex + 1).Trim(); if (Enum.TryParse <IceRolesEnum>(iceRoleStr, true, out var iceRole)) { if (activeAnnouncement != null) { activeAnnouncement.IceRole = iceRole; } else { sdp.IceRole = iceRole; } } else { logger.LogWarning($"ICE role was not recognised from SDP attribute: {sdpLineTrimmed}."); } } else { logger.LogWarning($"ICE role SDP attribute was missing the mandatory colon: {sdpLineTrimmed}."); } break; case var x when x.StartsWith($"a={DTLS_FINGERPRINT_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={ICE_CANDIDATE_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { if (activeAnnouncement.IceCandidates == null) { activeAnnouncement.IceCandidates = new List <string>(); } activeAnnouncement.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } else { if (sdp.IceCandidates == null) { sdp.IceCandidates = new List <string>(); } sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } break; case var x when x == $"a={END_ICE_CANDIDATES_ATTRIBUTE}": // TODO: Set a flag. break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video) { // Parse the rtpmap attribute for audio/video announcements. Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string formatID = formatAttributeMatch.Result("${id}"); string rtpmap = formatAttributeMatch.Result("${attribute}"); if (Int32.TryParse(formatID, out int id)) { if (activeAnnouncement.MediaFormats.ContainsKey(id)) { activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedRtpmap(rtpmap, activeAnnouncement.MediaFormats[id]); } else { string fmtp = _pendingFmtp.ContainsKey(id) ? _pendingFmtp[id] : null; activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, rtpmap, fmtp)); } } else { logger.LogWarning("Non-numeric audio/video media format attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { // Parse the rtpmap attribute for NON audio/video announcements. Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\S+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string formatID = formatAttributeMatch.Result("${id}"); string rtpmap = formatAttributeMatch.Result("${attribute}"); if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) { activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedRtpmap(rtpmap); } else { activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, rtpmap, null)); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } } else { logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { if (activeAnnouncement.Media == SDPMediaTypesEnum.audio || activeAnnouncement.Media == SDPMediaTypesEnum.video) { // Parse the fmtp attribute for audio/video announcements. Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string avFormatID = formatAttributeMatch.Result("${id}"); string fmtp = formatAttributeMatch.Result("${attribute}"); if (Int32.TryParse(avFormatID, out int id)) { if (activeAnnouncement.MediaFormats.ContainsKey(id)) { activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedFmtp(fmtp, activeAnnouncement.MediaFormats[id]); } else { // Store the fmtp attribute for use when the rtpmap attribute turns up. if (_pendingFmtp.ContainsKey(id)) { _pendingFmtp.Remove(id); } _pendingFmtp.Add(id, fmtp); } } else { logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { // Parse the fmtp attribute for NON audio/video announcements. Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\S+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string formatID = formatAttributeMatch.Result("${id}"); string fmtp = formatAttributeMatch.Result("${attribute}"); if (activeAnnouncement.ApplicationMediaFormats.ContainsKey(formatID)) { activeAnnouncement.ApplicationMediaFormats[formatID] = activeAnnouncement.ApplicationMediaFormats[formatID].WithUpdatedFmtp(fmtp); } else { activeAnnouncement.ApplicationMediaFormats.Add(formatID, new SDPApplicationMediaFormat(formatID, null, fmtp)); } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } } else { logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); } break; case var l when l.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX): //2018-12-21 rj2: add a=crypto if (activeAnnouncement != null) { try { activeAnnouncement.AddCryptoLine(sdpLineTrimmed); } catch (FormatException fex) { logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex); } } break; case var x when x.StartsWith($"a={MEDIA_ID_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { logger.LogWarning("A media ID can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); // Set the ID. if (fields.Length > 0) { activeAnnouncement.SsrcGroupID = fields[0]; } // Add attributes for each of the SSRC values. for (int i = 1; i < fields.Length; i++) { if (uint.TryParse(fields[i], out var ssrc)) { activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID)); } } } else { logger.LogWarning("A ssrc-group ID can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc)) { var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc); if (ssrcAttribute == null) { ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null); activeAnnouncement.SsrcAttributes.Add(ssrcAttribute); } if (ssrcFields.Length > 1) { if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX)) { ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1); } } } } else { logger.LogWarning("An ssrc attribute can only be set on a media announcement."); } break; case var x when MediaStreamStatusType.IsMediaStreamStatusAttribute(x, out var mediaStreamStatus): if (activeAnnouncement != null) { activeAnnouncement.MediaStreamStatus = mediaStreamStatus; } else { sdp.SessionMediaStreamStatus = mediaStreamStatus; } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { activeAnnouncement.SctpMap = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); (var sctpPortStr, _, var maxMessageSizeStr) = activeAnnouncement.SctpMap.Split(' '); if (ushort.TryParse(sctpPortStr, out var sctpPort)) { activeAnnouncement.SctpPort = sctpPort; } else { logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port."); } if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) { logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long."); } } else { logger.LogWarning("An sctpmap attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string sctpPortStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); if (ushort.TryParse(sctpPortStr, out var sctpPort)) { activeAnnouncement.SctpPort = sctpPort; } else { logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port."); } } else { logger.LogWarning("An sctp-port attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string maxMessageSizeStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) { logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long."); } } else { logger.LogWarning("A max-message-size attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_ACCEPT_TYPES_PREFIX): if (activeAnnouncement != null) { string acceptTypesStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); var acceptTypesList = acceptTypesStr.Trim().Split(' ').ToList(); activeAnnouncement.MessageMediaFormat.AcceptTypes = acceptTypesList; } else { logger.LogWarning("A accept-types attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PATH_MSRP_PREFIX): if (activeAnnouncement != null) { string pathStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); string pathTrimmedStr = pathStr.Substring(pathStr.IndexOf(':') + 3); activeAnnouncement.MessageMediaFormat.IP = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf(':')); pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf(':') + 1); activeAnnouncement.MessageMediaFormat.Port = pathTrimmedStr.Substring(0, pathTrimmedStr.IndexOf('/')); pathTrimmedStr = pathTrimmedStr.Substring(pathTrimmedStr.IndexOf('/') + 1); activeAnnouncement.MessageMediaFormat.Endpoint = pathTrimmedStr; } else { logger.LogWarning("A path attribute can only be set on a media announcement."); } break; default: if (activeAnnouncement != null) { activeAnnouncement.AddExtra(sdpLineTrimmed); } else { sdp.AddExtra(sdpLineTrimmed); } break; } } return(sdp); } else { return(null); } } catch (Exception excp) { logger.LogError("Exception ParseSDPDescription. " + excp.Message); throw; } }
public SDPMediaAnnouncement(SDPConnectionInformation connection) { Connection = connection; }
public SDPMediaAnnouncement(SDPConnectionInformation connection) { Connection = connection; }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); sdp.m_rawSdp = sdpDescription; int mLineIndex = 0; SDPMediaAnnouncement activeAnnouncement = null; string[] sdpLines = Regex.Split(sdpDescription, CRLF); foreach (string sdpLine in sdpLines) { string sdpLineTrimmed = sdpLine.Trim(); switch (sdpLineTrimmed) { case var l when l.StartsWith("v="): if (!Decimal.TryParse(sdpLineTrimmed.Substring(2), out sdp.Version)) { logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + "."); } break; case var l when l.StartsWith("o="): string[] ownerFields = sdpLineTrimmed.Substring(2).Split(' '); sdp.Username = ownerFields[0]; sdp.SessionId = ownerFields[1]; Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion); sdp.NetworkType = ownerFields[3]; sdp.AddressType = ownerFields[4]; sdp.AddressOrHost = ownerFields[5]; break; case var l when l.StartsWith("s="): sdp.SessionName = sdpLineTrimmed.Substring(2); break; case var l when l.StartsWith("c="): if (activeAnnouncement != null) { activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } else if (sdp.Connection == null) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLineTrimmed); } else { logger.LogWarning("The SDP message had a duplicate connection attribute which was ignored."); } break; case var l when l.StartsWith("b="): if (activeAnnouncement != null) { activeAnnouncement.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } else { sdp.BandwidthAttributes.Add(sdpLineTrimmed.Substring(2)); } break; case var l when l.StartsWith("t="): sdp.Timing = sdpLineTrimmed.Substring(2); break; case var l when l.StartsWith("m="): Match mediaMatch = Regex.Match(sdpLineTrimmed.Substring(2), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$"); if (mediaMatch.Success) { SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); announcement.MLineIndex = mLineIndex; announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port); announcement.Transport = mediaMatch.Result("${transport}"); announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); announcement.MediaStreamStatus = sdp.SessionMediaStreamStatus != null ? sdp.SessionMediaStreamStatus.Value : MediaStreamStatusEnum.SendRecv; sdp.Media.Add(announcement); activeAnnouncement = announcement; } else { logger.LogWarning("A media line in SDP was invalid: " + sdpLineTrimmed.Substring(2) + "."); } mLineIndex++; break; case var x when x.StartsWith($"a={GROUP_ATRIBUTE_PREFIX}"): sdp.Group = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); break; case var x when x.StartsWith($"a={ICE_UFRAG_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IceUfrag = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={ICE_PWD_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.IcePwd = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={DTLS_FINGERPRINT_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { sdp.DtlsFingerprint = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } break; case var x when x.StartsWith($"a={ICE_CANDIDATE_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { if (activeAnnouncement.IceCandidates == null) { activeAnnouncement.IceCandidates = new List <string>(); } activeAnnouncement.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } else { if (sdp.IceCandidates == null) { sdp.IceCandidates = new List <string>(); } sdp.IceCandidates.Add(sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1)); } break; case var x when x == $"a={END_ICE_CANDIDATES_ATTRIBUTE}": // TODO: Set a flag. break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string formatID = formatAttributeMatch.Result("${id}"); string rtpmap = formatAttributeMatch.Result("${attribute}"); if (activeAnnouncement.Media == SDPMediaTypesEnum.application) { var appMediaFormat = activeAnnouncement.ApplicationMediaFormats.Where(x => x.ID == formatID).FirstOrDefault(); if (appMediaFormat.ID != null) { appMediaFormat.Rtpmap = rtpmap; } } else { if (Int32.TryParse(formatID, out int id)) { if (activeAnnouncement.MediaFormats.ContainsKey(id)) { activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedRtpmap(rtpmap, activeAnnouncement.MediaFormats[id]); } else { activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, rtpmap, null)); } } else { logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine); } } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLineTrimmed, SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { string formatID = formatAttributeMatch.Result("${id}"); string fmtp = formatAttributeMatch.Result("${attribute}"); if (activeAnnouncement.Media == SDPMediaTypesEnum.application) { var appMediaFormat = activeAnnouncement.ApplicationMediaFormats.Where(x => x.ID == formatID).FirstOrDefault(); if (appMediaFormat.ID != null) { appMediaFormat.Fmtmp = fmtp; } } else { if (Int32.TryParse(formatID, out int id)) { if (activeAnnouncement.MediaFormats.ContainsKey(id)) { activeAnnouncement.MediaFormats[id] = activeAnnouncement.MediaFormats[id].WithUpdatedFmtp(fmtp, activeAnnouncement.MediaFormats[id]); } else { activeAnnouncement.MediaFormats.Add(id, new SDPAudioVideoMediaFormat(activeAnnouncement.Media, id, null, fmtp)); } } else { logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine); } } } else { activeAnnouncement.AddExtra(sdpLineTrimmed); } } else { logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); } break; case var l when l.StartsWith(SDPSecurityDescription.CRYPTO_ATTRIBUE_PREFIX): //2018-12-21 rj2: add a=crypto if (activeAnnouncement != null) { try { activeAnnouncement.AddCryptoLine(sdpLineTrimmed); } catch (FormatException fex) { logger.LogWarning("Error Parsing SDP-Line(a=crypto) " + fex); } } break; case var x when x.StartsWith($"a={MEDIA_ID_ATTRIBUTE_PREFIX}"): if (activeAnnouncement != null) { activeAnnouncement.MediaID = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); } else { logger.LogWarning("A media ID can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_GROUP_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string[] fields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); // Set the ID. if (fields.Length > 0) { activeAnnouncement.SsrcGroupID = fields[0]; } // Add attributes for each of the SSRC values. for (int i = 1; i < fields.Length; i++) { if (uint.TryParse(fields[i], out var ssrc)) { activeAnnouncement.SsrcAttributes.Add(new SDPSsrcAttribute(ssrc, null, activeAnnouncement.SsrcGroupID)); } } } else { logger.LogWarning("A ssrc-group ID can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SSRC_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string[] ssrcFields = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1).Split(' '); if (ssrcFields.Length > 0 && uint.TryParse(ssrcFields[0], out var ssrc)) { var ssrcAttribute = activeAnnouncement.SsrcAttributes.FirstOrDefault(x => x.SSRC == ssrc); if (ssrcAttribute == null) { ssrcAttribute = new SDPSsrcAttribute(ssrc, null, null); activeAnnouncement.SsrcAttributes.Add(ssrcAttribute); } if (ssrcFields.Length > 1) { if (ssrcFields[1].StartsWith(SDPSsrcAttribute.MEDIA_CNAME_ATTRIBUE_PREFIX)) { ssrcAttribute.Cname = ssrcFields[1].Substring(ssrcFields[1].IndexOf(':') + 1); } } } } else { logger.LogWarning("An ssrc attribute can only be set on a media announcement."); } break; case var x when MediaStreamStatusType.IsMediaStreamStatusAttribute(x, out var mediaStreamStatus): if (activeAnnouncement != null) { activeAnnouncement.MediaStreamStatus = mediaStreamStatus; } else { sdp.SessionMediaStreamStatus = mediaStreamStatus; } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_MAP_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { activeAnnouncement.SctpMap = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); (var sctpPortStr, _, var maxMessageSizeStr) = activeAnnouncement.SctpMap.Split(' '); if (ushort.TryParse(sctpPortStr, out var sctpPort)) { activeAnnouncement.SctpPort = sctpPort; } else { logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port."); } if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) { logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long."); } } else { logger.LogWarning("An sctpmap attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_SCTP_PORT_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string sctpPortStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); if (ushort.TryParse(sctpPortStr, out var sctpPort)) { activeAnnouncement.SctpPort = sctpPort; } else { logger.LogWarning($"An sctp-port value of {sctpPortStr} was not recognised as a valid port."); } } else { logger.LogWarning("An sctp-port attribute can only be set on a media announcement."); } break; case var l when l.StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_MAX_MESSAGE_SIZE_ATTRIBUE_PREFIX): if (activeAnnouncement != null) { string maxMessageSizeStr = sdpLineTrimmed.Substring(sdpLineTrimmed.IndexOf(':') + 1); if (!long.TryParse(maxMessageSizeStr, out activeAnnouncement.MaxMessageSize)) { logger.LogWarning($"A max-message-size value of {maxMessageSizeStr} was not recognised as a valid long."); } } else { logger.LogWarning("A max-message-size attribute can only be set on a media announcement."); } break; default: if (activeAnnouncement != null) { activeAnnouncement.AddExtra(sdpLineTrimmed); } else { sdp.AddExtra(sdpLineTrimmed); } break; } } return(sdp); } else { return(null); } } catch (Exception excp) { logger.LogError("Exception ParseSDPDescription. " + excp.Message); throw; } }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); SDPMediaAnnouncement activeAnnouncement = null; string[] sdpLines = Regex.Split(sdpDescription, CRLF); foreach (string sdpLine in sdpLines) { if (sdpLine.Trim().StartsWith("v=")) { if (!Decimal.TryParse(sdpLine.Substring(2), out sdp.Version)) { logger.LogWarning("The Version value in an SDP description could not be parsed as a decimal: " + sdpLine + "."); } } else if (sdpLine.Trim().StartsWith("o=")) { string[] ownerFields = sdpLine.Substring(2).Split(' '); sdp.Username = ownerFields[0]; sdp.SessionId = ownerFields[1]; Int32.TryParse(ownerFields[2], out sdp.AnnouncementVersion); sdp.NetworkType = ownerFields[3]; sdp.AddressType = ownerFields[4]; sdp.Address = ownerFields[5]; } else if (sdpLine.Trim().StartsWith("s=")) { sdp.SessionName = sdpLine.Substring(2); } else if (sdpLine.Trim().StartsWith("c=")) { if (sdp.Connection == null) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine); } if (activeAnnouncement != null) { activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine); } } else if (sdpLine.Trim().StartsWith("b=")) { if (activeAnnouncement != null) { activeAnnouncement.BandwidthAttributes.Add(sdpLine.Substring(2)); } else { sdp.BandwidthAttributes.Add(sdpLine.Substring(2)); } } else if (sdpLine.Trim().StartsWith("t=")) { sdp.Timing = sdpLine.Substring(2); } else if (sdpLine.Trim().StartsWith("m=")) { Match mediaMatch = Regex.Match(sdpLine.Substring(2).Trim(), @"(?<type>\w+)\s+(?<port>\d+)\s+(?<transport>\S+)(\s*)(?<formats>.*)$"); if (mediaMatch.Success) { SDPMediaAnnouncement announcement = new SDPMediaAnnouncement(); announcement.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out announcement.Port); announcement.Transport = mediaMatch.Result("${transport}"); announcement.ParseMediaFormats(mediaMatch.Result("${formats}")); sdp.Media.Add(announcement); activeAnnouncement = announcement; } else { logger.LogWarning("A media line in SDP was invalid: " + sdpLine.Substring(2) + "."); } } else if (sdpLine.Trim().StartsWith("a=" + ICE_UFRAG_ATTRIBUTE_PREFIX)) { sdp.IceUfrag = sdpLine.Substring(sdpLine.IndexOf(':') + 1); } else if (sdpLine.Trim().StartsWith("a=" + ICE_PWD_ATTRIBUTE_PREFIX)) { sdp.IcePwd = sdpLine.Substring(sdpLine.IndexOf(':') + 1); } else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLine); } } else { logger.LogWarning("There was no active media announcement for a media format attribute, ignoring."); } } else if (sdpLine.Trim().StartsWith(SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX)) { if (activeAnnouncement != null) { Match formatAttributeMatch = Regex.Match(sdpLine.Trim(), SDPMediaAnnouncement.MEDIA_FORMAT_PARAMETERS_ATTRIBUE_PREFIX + @"(?<id>\d+)\s+(?<attribute>.*)$"); if (formatAttributeMatch.Success) { int formatID; if (Int32.TryParse(formatAttributeMatch.Result("${id}"), out formatID)) { activeAnnouncement.AddFormatParameterAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.LogWarning("Invalid media format parameter attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLine); } } else { logger.LogWarning("There was no active media announcement for a media format parameter attribute, ignoring."); } } else if (sdpLine.Trim().StartsWith("a=" + ICE_CANDIDATE_ATTRIBUTE_PREFIX)) { if (sdp.IceCandidates == null) { sdp.IceCandidates = new List <IceCandidate>(); } sdp.IceCandidates.Add(IceCandidate.Parse(sdpLine.Substring(sdpLine.IndexOf(':') + 1))); } else { if (activeAnnouncement != null) { activeAnnouncement.AddExtra(sdpLine); } else { sdp.AddExtra(sdpLine); } } } return(sdp); } else { return(null); } } catch (Exception excp) { logger.LogError("Exception ParseSDPDescription. " + excp.Message); throw excp; } }