public void PlaceCall(string destination, SDP sdp) { m_destination = destination; m_sdp = sdp; XElement descriptionElement = SDPToJingle.GetDescription(m_sdp); m_descriptionID = Crypto.GetRandomString(6); XElement callElement = new XElement(JabberClientNS + "iq", new XAttribute("from", m_jid), new XAttribute("id", m_descriptionID), new XAttribute("to", m_destination), new XAttribute("type", "set"), new XElement(m_sessionNS + "session", new XAttribute("type", "initiate"), new XAttribute("id", m_sessionID), new XAttribute("initiator", m_jid), descriptionElement //new XElement(m_transportNS + "transport") )); logger.Debug("XMPPPhoneSession sending iq with session description."); m_xmppStream.WriteElement(callElement, OnIQResponse); }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="remoteSdp">The answer/offer SDP from the remote party.</param> public void OnSdpAnswer(SDP remoteSdp) { // Check remote party audio is acceptable. if (_supportedAudioFormats?.Count() > 0) { var remoteAudioOffer = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (remoteAudioOffer?.MediaFormats.Count() == 0) { logger.LogWarning("No audio formats were available in the remote party's SDP."); Close("No audio codecs offered."); } else if (remoteAudioOffer.MediaFormats.Select(x => x.FormatCodec).Union(_supportedAudioFormats.Select(y => y.FormatCodec)).Count() == 0) { logger.LogWarning("No matching audio codec was available."); Close("No matching audio codec."); } } // Check remote party video is acceptable. if (_supportedVideoFormats?.Count() > 0) { var remoteVideoOffer = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (remoteVideoOffer?.MediaFormats.Count() == 0) { logger.LogWarning("No video formats were available in the remote party's SDP."); Close("No video codecs offered."); } else if (remoteVideoOffer.MediaFormats.Select(x => x.FormatCodec).Union(_supportedVideoFormats.Select(y => y.FormatCodec)).Count() == 0) { logger.LogWarning("No matching video codec was available."); Close("No matching video codec."); } // Since we only currently support VP8 there's only a single remote payload ID that can be // associated with the video stream. var remoteVP8MediaFormat = remoteVideoOffer.MediaFormats.Where(x => x.FormatCodec == SDPMediaFormatsEnum.VP8).Single(); RtpSession.AddStream(SDPMediaTypesEnum.video, VP8_PAYLOAD_TYPE_ID, new List <int> { Convert.ToInt32(remoteVP8MediaFormat.FormatID) }); } SdpSessionID = remoteSdp.SessionId; RemoteIceUser = remoteSdp.IceUfrag; RemoteIcePassword = remoteSdp.IcePwd; // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { AppendRemoteIceCandidate(iceCandidate); } } }
/// <summary> /// Convenience overload to suit SIP/VoIP callers. /// TODO: Consolidate with createAnswer. /// </summary> /// <param name="connectionAddress">Not used.</param> /// <returns>An SDP payload to answer an offer from the remote party.</returns> public override SDP CreateAnswer(IPAddress connectionAddress) { var result = createAnswer(null); if (result?.sdp != null) { return(SDP.ParseSDPDescription(result.sdp)); } return(null); }
/// <summary> /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending /// on whether it's being used in an offer or an answer. /// </summary> /// <param name="audioCapabilities">Optional. The audio formats to support in the SDP. This list can differ from /// the local audio track if an answer is being generated and only mutually supported formats are being /// used.</param> /// <param name="videoCapabilities">Optional. The video formats to support in the SDP. This list can differ from /// the local video track if an answer is being generated and only mutually supported formats are being /// used.</param> /// <remarks> /// From https://tools.ietf.org/html/draft-ietf-mmusic-ice-sip-sdp-39#section-4.2.5: /// "The transport address from the peer for the default destination /// is set to IPv4/IPv6 address values "0.0.0.0"/"::" and port value /// of "9". This MUST NOT be considered as a ICE failure by the peer /// agent and the ICE processing MUST continue as usual." /// </remarks> private SDP createBaseSdp(List <MediaStreamTrack> tracks, List <SDPMediaFormat> audioCapabilities, List <SDPMediaFormat> videoCapabilities) { SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = LocalSdpSessionID; bool iceCandidatesAdded = false; // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. offerSdp.Group = BUNDLE_ATTRIBUTE; offerSdp.DtlsFingerprint = _currentCertificate.getFingerprints().First().ToString(); // Media announcements must be in the same order in the offer and answer. foreach (var track in tracks.OrderBy(x => x.MLineIndex)) { offerSdp.Group += $" {track.MID}"; SDPMediaAnnouncement announcement = new SDPMediaAnnouncement( track.Kind, SDP.IGNORE_RTP_PORT_NUMBER, (track.Kind == SDPMediaTypesEnum.video) ? videoCapabilities : audioCapabilities); announcement.Transport = RTP_MEDIA_PROFILE; announcement.Connection = new SDPConnectionInformation(IPAddress.Any); announcement.AddExtra(RTCP_MUX_ATTRIBUTE); announcement.AddExtra(RTCP_ATTRIBUTE); announcement.MediaStreamStatus = track.StreamStatus; announcement.MediaID = track.MID; announcement.IceUfrag = _rtpIceChannel.LocalIceUser; announcement.IcePwd = _rtpIceChannel.LocalIcePassword; announcement.IceOptions = ICE_OPTIONS; announcement.DtlsFingerprint = offerSdp.DtlsFingerprint; if (iceCandidatesAdded == false && _rtpIceChannel.Candidates?.Count > 0) { announcement.IceCandidates = new List <string>(); // Add ICE candidates. foreach (var iceCandidate in _rtpIceChannel.Candidates) { announcement.IceCandidates.Add(iceCandidate.ToString()); } iceCandidatesAdded = true; } offerSdp.Media.Add(announcement); } return(offerSdp); }
/// <summary> /// Gets the a basic Session Description Protocol object that describes this RTP session. /// </summary> /// <param name="localAddress">The RTP socket we will be sending from. Note this can't be IPAddress.Any as /// it's getting sent to the callee. An IP address of 0.0.0.0 or [::0] will typically be interpreted as /// "don't send me any RTP".</param> /// <returns>An Session Description Protocol object that can be sent to a remote callee.</returns> public SDP GetSDP(IPAddress localAddress) { var sdp = new SDP(localAddress) { SessionId = Crypto.GetRandomInt(5).ToString(), SessionName = "sipsorcery", Timing = "0 0", Connection = new SDPConnectionInformation(localAddress), }; sdp.Media.Add(MediaAnnouncement); return(sdp); }
private void Answered(SDP sdp) { Console.WriteLine("XMPP client call answered."); IsUACAnswered = true; SIPResponse okResponse = new SIPResponse(SIPResponseStatusCodesEnum.Ok, "Ok", new SIPEndPoint(new IPEndPoint(IPAddress.Loopback, 0))); okResponse.Header.ContentType = SDP.SDP_MIME_CONTENTTYPE; okResponse.Body = sdp.ToString(); SIPDialogue = new SIPDialogue(null, null, null, null, -1, null, null, null, Guid.NewGuid(), Owner, AdminMemberId, null, sdp.ToString()); SIPDialogue.CallDurationLimit = CallDescriptor.CallDurationLimit; CallAnswered(this, okResponse); }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sdpType">Whether the remote SDP is an offer or an answer.</param> /// <param name="remoteSdp">The answer/offer SDP from the remote party.</param> public void setRemoteDescription(SdpType sdpType, SDP remoteSdp) { var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { RtpSession.AddTrack(audioAnnounce.MediaID, SDPMediaTypesEnum.audio, true, audioAnnounce.MediaFormats); } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { RtpSession.AddTrack(videoAnnounce.MediaID, SDPMediaTypesEnum.video, true, videoAnnounce.MediaFormats); } SdpSessionID = remoteSdp.SessionId; RemoteIceUser = remoteSdp.IceUfrag ?? remoteSdp.Media.First().IceUfrag; RemoteIcePassword = remoteSdp.IcePwd ?? remoteSdp.Media.First().IcePwd; // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { AppendRemoteIceCandidate(iceCandidate); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { AppendRemoteIceCandidate(iceCandidate); } } } m_remoteSDP = remoteSdp; }
public static XElement GetDescription(SDP sdp) { XElement descriptionElement = new XElement(m_phoneNS + "description"); foreach (SDPMediaFormat mediaFormat in sdp.Media[0].MediaFormats) { XElement payloadElement = new XElement(m_phoneNS + "payload-type", new XAttribute("id", mediaFormat.FormatID), new XAttribute("name", mediaFormat.Name)); if (mediaFormat.ClockRate != 0) { payloadElement.Add(new XAttribute("clockrate", mediaFormat.ClockRate)); } descriptionElement.Add(payloadElement); } return descriptionElement; }
/// <summary> /// Sets the local SDP. /// </summary> /// <remarks> /// As specified in https://www.w3.org/TR/webrtc/#dom-peerconnection-setlocaldescription. /// </remarks> /// <param name="description">Optional. The session description to set as /// local description. If not supplied then an offer or answer will be created as required. /// </param> public Task setLocalDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; localDescription = description; if (init.type == RTCSdpType.offer) { _rtpIceChannel.IsController = true; } // This is the point the ICE session potentially starts contacting STUN and TURN servers. //IceSession.StartGathering(); signalingState = RTCSignalingState.have_local_offer; onsignalingstatechange?.Invoke(); return(Task.CompletedTask); }
/// <param name="rtpListenAddress">The local IP address to establish the RTP listener socket on.</param> /// <param name="sdpAdvertiseAddress">The public IP address to put into the SDP sent back to the caller.</param> /// <param name="request">The INVITE request that instigated the RTP diagnostics job.</param> public RTPDiagnosticsJob(IPAddress rtpListenAddress, IPAddress sdpAdvertiseAddress, SIPServerUserAgent uas, SIPRequest request) { m_request = request; m_remoteSDP = SDP.ParseSDPDescription(request.Body); RemoteRTPEndPoint = new IPEndPoint(IPAddress.Parse(m_remoteSDP.Connection.ConnectionAddress), m_remoteSDP.Media[0].Port); UAS = uas; //m_rawSourceStream = new RawSourceWaveStream(m_outStream, WaveFormat.CreateMuLawFormat(8000, 1)); //m_waveFileWriter = new WaveFileWriter("out.wav", new WaveFormat(8000, 16, 1)); m_waveFileWriter = new WaveFileWriter("out.wav", new WaveFormat(8000, 16, 1)); //m_outPCMStream = WaveFormatConversionStream.CreatePcmStream(m_rawSourceStream); //m_rawRTPPayloadWriter = new StreamWriter("out.rtp"); //m_rawRTPPayloadReader = new StreamReader("in.rtp"); //IPEndPoint rtpListenEndPoint = null; IPEndPoint rtpListenEndPoint = null; NetServices.CreateRandomUDPListener(rtpListenAddress, RTP_PORTRANGE_START, RTP_PORTRANGE_END, m_inUsePorts, out rtpListenEndPoint); RTPListenEndPoint = rtpListenEndPoint; m_inUsePorts.Add(rtpListenEndPoint.Port); //RTPListenEndPoint = new IPEndPoint(rtpListenAddress, RTP_PORTRANGE_START); m_rtpChannel = new RTPChannel(RTPListenEndPoint); m_rtpChannel.SampleReceived += SampleReceived; ThreadPool.QueueUserWorkItem(delegate { GetAudioSamples(); }); LocalSDP = new SDP() { SessionId = Crypto.GetRandomString(6), Address = sdpAdvertiseAddress.ToString(), SessionName = "sipsorcery", Timing = "0 0", Connection = new SDPConnectionInformation(sdpAdvertiseAddress.ToString()), Media = new List<SDPMediaAnnouncement>() { new SDPMediaAnnouncement() { Media = SDPMediaTypesEnum.audio, Port = RTPListenEndPoint.Port, MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU) } } } }; }
/// <summary> /// Sets the remote SDP offer for this RTP session. It contains required information about payload ID's /// for media formats and RTP events. /// </summary> /// <param name="sdp">The SDP from the remote call party.</param> public void SetRemoteSDP(SDP sdp) { RemoteSDP = sdp; foreach (var announcement in sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio)) { foreach (var mediaFormat in announcement.MediaFormats) { if (mediaFormat.FormatAttribute?.StartsWith(TELEPHONE_EVENT_ATTRIBUTE) == true) { if (!int.TryParse(mediaFormat.FormatID, out m_remoteRtpEventPayloadID)) { logger.LogWarning("The media format on the telpehone event attribute was not a valid integer."); } break; } } } DestinationEndPoint = sdp.GetSDPRTPEndPoint(); RtcpSession.ControlDestinationEndPoint = new IPEndPoint(DestinationEndPoint.Address, DestinationEndPoint.Port + 1); }
public static XElement GetCandidates(SDP sdp) { string iceUfrag = (sdp.IceUfrag != null) ? sdp.IceUfrag : String.Empty; string icePwd = (sdp.IcePwd != null) ? sdp.IcePwd : String.Empty; string sdpIPAddress = sdp.Connection.ConnectionAddress; int sdpPort = sdp.Media[0].Port; string candidateID = Crypto.GetRandomString(6); XElement candidateElement = new XElement(m_sessionNS + "candidate", new XAttribute("name", "rtp"), new XAttribute("address", sdpIPAddress), new XAttribute("port", sdpPort), new XAttribute("username", iceUfrag), new XAttribute("password", icePwd), new XAttribute("preference", "1.0"), new XAttribute("protocol", "udp"), new XAttribute("type", "local"), new XAttribute("network", "0"), new XAttribute("generation", "0")); return candidateElement; }
/// <summary> /// A convenience method to get the RTP end point for single audio offer SDP payloads. /// </summary> /// <param name="sdpMessage">A string representing the SDP payload.</param> /// <returns>The RTP end point for the first media end point.</returns> public static IPEndPoint GetSDPRTPEndPoint(string sdpMessage) { // Process the SDP payload. //Match portMatch = Regex.Match(sdpMessage, @"m=audio (?<port>\d+)", RegexOptions.Singleline); //if (portMatch.Success) //{ // int rtpServerPort = Convert.ToInt32(portMatch.Result("${port}")); // Match serverMatch = Regex.Match(sdpMessage, @"c=IN IP4 (?<ipaddress>(\d+\.){3}\d+)", RegexOptions.Singleline); // if (serverMatch.Success) // { // string rtpServerAddress = serverMatch.Result("${ipaddress}"); // if(IPAddress.TryParse(rtpServerAddress, out var ipAddress)) // { // IPEndPoint serverEndPoint = new IPEndPoint(ipAddress, rtpServerPort); // return serverEndPoint; // } // } //} SDP sdp = SDP.ParseSDPDescription(sdpMessage); // Find first media offer. var sessionConnection = sdp?.Connection; var firstMediaOffer = sdp?.Media.FirstOrDefault(); if (sessionConnection != null && firstMediaOffer != null) { return(new IPEndPoint(IPAddress.Parse(sessionConnection.ConnectionAddress), firstMediaOffer.Port)); } else { return(null); } }
/// <summary> /// Gets an SDP packet that can be used by VoIP clients to negotiate an audio connection. The SDP will only /// offer PCMU since that's all I've gotten around to handling. /// </summary> /// <param name="usePublicIP">If true and the public IP address is available from the STUN client then /// the public IP address will be used in the SDP otherwise the host machine's default IPv4 address will /// be used.</param> /// <returns>An SDP packet that can be used by a VoIP client when initiating a call.</returns> public SDP GetSDP(bool usePublicIP) { IPAddress rtpIPAddress = (usePublicIP && SoftphoneSTUNClient.PublicIPAddress != null) ? SoftphoneSTUNClient.PublicIPAddress : _defaultLocalAddress; var sdp = new SDP() { SessionId = Crypto.GetRandomInt(5).ToString(), Address = rtpIPAddress.ToString(), SessionName = "sipsorcery", Timing = "0 0", Connection = new SDPConnectionInformation(rtpIPAddress.ToString()), }; if (_rtpAudioChannel != null) { var audioAnnouncement = new SDPMediaAnnouncement() { Media = SDPMediaTypesEnum.audio, MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU, "PCMU", 8000) } }; audioAnnouncement.Port = _rtpAudioChannel.RTPPort; sdp.Media.Add(audioAnnouncement); } if (_rtpVideoChannel != null) { var videoAnnouncement = new SDPMediaAnnouncement() { Media = SDPMediaTypesEnum.video, MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat(96, "VP8", 90000) } }; videoAnnouncement.Port = _rtpVideoChannel.RTPPort; sdp.Media.Add(videoAnnouncement); } return sdp; }
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; } }
public void SetRemoteSDP(SDP remoteSDP) { _rtpManager.SetRemoteSDP(remoteSDP); }
/// <summary> /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session /// is ready for to exchange encrypted RTP and RTCP packets. /// </summary> /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN /// server in this session's ICE candidate gathering.</param> public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint) { try { _doDtlsHandshake = doDtlsHandshake; _turnServerEndPoint = turnServerEndPoint; DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync(); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { logger.LogWarning("No local socket candidates were found for WebRTC call closing."); Close("No local ICE candidates available."); } else { bool includeAudioOffer = _supportedAudioFormats?.Count() > 0; bool includeVideoOffer = _supportedVideoFormats?.Count() > 0; bool haveIceCandidatesBeenAdded = false; bool isMediaBundle = includeAudioOffer && includeVideoOffer; // Is this SDP offer bundling audio and video on the same RTP connection. string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } LocalIceUser = LocalIceUser ?? Crypto.GetRandomString(20); LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20); SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = Crypto.GetRandomInt(5).ToString(); // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (isMediaBundle) { offerSdp.Group = MEDIA_GROUPING; } if (includeAudioOffer) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, _rtpChannel.RTPPort, _supportedAudioFormats); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.AddExtra(SETUP_ATTRIBUTE); audioAnnouncement.MediaStreamStatus = AudioStreamStatus; if (isMediaBundle) { audioAnnouncement.MediaID = AUDIO_MEDIA_ID; } offerSdp.Media.Add(audioAnnouncement); } if (includeVideoOffer) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, _rtpChannel.RTPPort, _supportedVideoFormats); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.AddExtra(SETUP_ATTRIBUTE); videoAnnouncement.MediaStreamStatus = VideoStreamStatus; if (isMediaBundle) { videoAnnouncement.MediaID = VIDEO_MEDIA_ID; } offerSdp.Media.Add(videoAnnouncement); } SDP = offerSdp; OnSdpOfferReady?.Invoke(SDP); } // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check. // If there are no remote candidates this call will end up being a NOP. SendStunConnectivityChecks(null); if (_doDtlsHandshake != null) { _ = Task.Run(() => { int result = _doDtlsHandshake(this); IsDtlsNegotiationComplete = (result == 0); }); } } catch (Exception excp) { logger.LogError("Exception WebRtcPeer.Initialise. " + excp); Close(excp.Message); } }
/// <summary> /// This set remote description overload is a convenience method for SIP/VoIP callers /// instead of WebRTC callers. The method signature better matches what the SIP /// user agent is expecting. /// TODO: Using two very similar overloads could cause confusion. Possibly /// consolidate. /// </summary> /// <param name="sdpType">Whether the remote SDP is an offer or answer.</param> /// <param name="sessionDescription">The SDP from the remote party.</param> /// <returns>The result of attempting to set the remote description.</returns> public override SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SDP sessionDescription) { RTCSessionDescriptionInit init = new RTCSessionDescriptionInit { sdp = sessionDescription.ToString(), type = (sdpType == SdpType.answer) ? RTCSdpType.answer : RTCSdpType.offer }; return(setRemoteDescription(init)); }
private void Answered(SDP xmppSDP) { //Console.WriteLine("Yay call answered."); //Console.WriteLine(sdp.ToString()); m_xmppServerEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString()); logger.Debug("Sending STUN binding request to " + m_xmppServerEndPoint + "."); STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest); initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag); byte[] stunMessageBytes = initMessage.ToByteBuffer(); m_xmppMediaSocket.Send(stunMessageBytes, stunMessageBytes.Length, m_xmppServerEndPoint); m_uas.Answer("application/sdp", GetSDPForSIPResponse().ToString(), null, SIPDialogueTransferModesEnum.NotAllowed); }
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(); 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; } }
private SDP GetSDPForXMPPRequest() { m_localSTUNUFrag = Crypto.GetRandomString(8); SDP sdp = new SDP() { IcePwd = Crypto.GetRandomString(12), IceUfrag = m_localSTUNUFrag, Connection = new SDPConnectionInformation(m_localXMPPEndPoint.Address.ToString()), Media = new List<SDPMediaAnnouncement>() { new SDPMediaAnnouncement(m_localXMPPEndPoint.Port) { MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat(0, "PCMU", 8000) } } }, }; return sdp; }
/// <summary> /// Event handler for an answer on an outgoing Google Voice call. /// </summary> /// <param name="xmppSDP">The SDP packet received from the Google Voice gateway.</param> private void XMPPAnswered(SDP xmppSDP) { StatusMessage("Google Voice call answered."); IPEndPoint remoteSDPEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString()); _rtpManager.SetRemoteRTPEndPoints(remoteSDPEndPoint, null); // Google Voice require that a STUN exchange occurs on the RTP socket before the RTP packet can flow. // This code block sends a STUN binding request to the Google Voice gateway. STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest); initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag); byte[] stunMessageBytes = initMessage.ToByteBuffer(); _rtpManager.SendRTPRaw(stunMessageBytes, stunMessageBytes.Length); }
public static SDP GetSDP(string ipAddress, int port, string username, string password, List<XElement> payloads) { string iceUsername = (username.IsNullOrBlank()) ? Crypto.GetRandomString(6) : username; string icePwd = (password.IsNullOrBlank()) ? Crypto.GetRandomString(6) : password; SDP sdp = new SDP() { Address = ipAddress, Username = "******", SessionId = Crypto.GetRandomString(5), AnnouncementVersion = Crypto.GetRandomInt(5), Connection = new SDPConnectionInformation(ipAddress), Timing = "0 0", IceUfrag = iceUsername, IcePwd = icePwd, Media = new List<SDPMediaAnnouncement>() { new SDPMediaAnnouncement(port) { //BandwidthAttributes = new List<string>(){"RS:0", "RR:0"} // Indicate that RTCP is not being used. } } }; sdp.ExtraAttributes.Add("a=candidate:1 1 UDP " + Crypto.GetRandomString(10) + " " + ipAddress + " " + port + " typ host"); sdp.ExtraAttributes.Add("a=candidate:1 2 UDP " + Crypto.GetRandomString(10) + " " + ipAddress + " " + (port + 1) + " typ host"); foreach (XElement payload in payloads) { int formatID; Int32.TryParse(payload.Attribute("id").Value, out formatID); string name = payload.Attribute("name").Value; int clockRate = 0; if (payload.Attribute("clockrate") != null) { Int32.TryParse(payload.Attribute("clockrate").Value, out clockRate); } if(clockRate == 0) { sdp.Media[0].MediaFormats.Add(new SDPMediaFormat(formatID, name)); } else { sdp.Media[0].MediaFormats.Add(new SDPMediaFormat(formatID, name, clockRate)); } } //Console.WriteLine("SDPToJingle SDP=> " + sdp.ToString()); return sdp; }
public static SDP ParseSDPDescription(string sdpDescription) { try { if (sdpDescription != null && sdpDescription.Trim().Length > 0) { SDP sdp = new SDP(); SDPMediaAnnouncement media = new SDPMediaAnnouncement(); string[] sdpLines = Regex.Split(sdpDescription, CRLF); //added WebRTC handling -> dont parse SDP especially sdp.isWebRTC = sdpDescription.Contains("a=source:webrtc"); if (sdp.isWebRTC) { foreach (string sdpLine in sdpLines) { sdp.ExtraAttributes.Add(sdpLine); } } else { foreach (string sdpLine in sdpLines) { if (sdpLine.Trim().StartsWith("v=")) { if (!Decimal.TryParse(sdpLine.Substring(2), out sdp.Version)) { logger.Warn("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=")) { sdp.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine); } 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) { media.Media = SDPMediaTypes.GetSDPMediaType(mediaMatch.Result("${type}")); Int32.TryParse(mediaMatch.Result("${port}"), out media.Port); media.Transport = mediaMatch.Result("${transport}"); media.ParseMediaFormats(mediaMatch.Result("${formats}")); } else { logger.Warn("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)) { 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)) { media.AddFormatAttribute(formatID, formatAttributeMatch.Result("${attribute}")); } else { logger.Warn("Invalid media format attribute in SDP: " + sdpLine); } } else { sdp.ExtraAttributes.Add(sdpLine); } } else { sdp.ExtraAttributes.Add(sdpLine); } } sdp.Media.Add(media); } return sdp; } else { return null; } } catch (Exception excp) { logger.Error("Exception ParseSDPDescription. " + excp.Message); throw excp; } }
/// <summary> /// Gets an SDP packet that can be used by VoIP clients to negotiate an audio connection. The SDP will only /// offer PCMU since that's all I've gotten around to handling. /// </summary> /// <param name="usePublicIP">If true and the public IP address is available from the STUN client then /// the public IP address will be used in the SDP otherwise the hsot machine's default IPv4 address will /// be used.</param> /// <returns>An SDP packet that can be used by a VoIP client when initiating a call.</returns> public SDP GetSDP(bool usePublicIP) { IPAddress rtpIPAddress = (usePublicIP) ? SoftphoneSTUNClient.PublicIPAddress : _rtpEndPoint.Address; int rtpPort = _rtpEndPoint.Port; var sdp = new SDP() { SessionId = Crypto.GetRandomInt(6).ToString(), Address = rtpIPAddress.ToString(), SessionName = "sipsorcery", Timing = "0 0", Connection = new SDPConnectionInformation(rtpIPAddress.ToString()), Media = new List<SDPMediaAnnouncement>() { new SDPMediaAnnouncement() { Media = SDPMediaTypesEnum.audio, Port = rtpPort, MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat((int)SDPMediaFormatsEnum.PCMU) } } } }; return sdp; }
/// <summary> /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending /// on whether it's being used in an offer or an answer. /// </summary> private async Task <SDP> createBaseSdp() { DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync().ConfigureAwait(false); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { //logger.LogWarning("No local socket candidates were found for WebRTC call closing."); //Close("No local ICE candidates available."); throw new ApplicationException("No local ICE candidates available."); } else { SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = LocalSdpSessionID; bool haveIceCandidatesBeenAdded = false; string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (AudioLocalTrack != null && VideoLocalTrack != null) { offerSdp.Group = MEDIA_GROUPING; } // The media is being multiplexed so the audio and video RTP channel is the same. var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio); // --- Audio announcement --- if (AudioLocalTrack != null) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, rtpChannel.RTPPort, AudioLocalTrack.Capabilties); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.MediaStreamStatus = AudioLocalTrack.Transceiver.Direction; audioAnnouncement.MediaID = AudioLocalTrack.Transceiver.MID; offerSdp.Media.Add(audioAnnouncement); } // --- Video announcement --- if (VideoLocalTrack != null) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, rtpChannel.RTPPort, VideoLocalTrack.Capabilties); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.MediaStreamStatus = VideoLocalTrack.Transceiver.Direction; videoAnnouncement.MediaID = VideoLocalTrack.Transceiver.MID; offerSdp.Media.Add(videoAnnouncement); } return(offerSdp); } }
private SDP GetSDPForSIPResponse() { SDP sdp = new SDP() { Address = m_localSIPEndPoint.Address.ToString(), Username = "******", SessionId = Crypto.GetRandomString(5), AnnouncementVersion = Crypto.GetRandomInt(5), Timing = "0 0", Connection = new SDPConnectionInformation(m_localSIPEndPoint.Address.ToString()), Media = new List<SDPMediaAnnouncement>() { new SDPMediaAnnouncement(m_localSIPEndPoint.Port) { MediaFormats = new List<SDPMediaFormat>() { new SDPMediaFormat(0, "PCMU", 8000) } } }, }; return sdp; }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param> public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; remoteDescription = description; SDP remoteSdp = SDP.ParseSDPDescription(init.sdp); SdpType sdpType = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer; var setResult = base.SetRemoteDescription(sdpType, remoteSdp); if (setResult == SetDescriptionResultEnum.OK) { string remoteIceUser = remoteSdp.IceUfrag; string remoteIcePassword = remoteSdp.IcePwd; string dtlsFingerprint = remoteSdp.DtlsFingerprint; var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { remoteIceUser = remoteIceUser ?? audioAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint; } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null) { remoteIceUser = remoteIceUser ?? videoAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint; } } SdpSessionID = remoteSdp.SessionId; if (init.type == RTCSdpType.answer) { _rtpIceChannel.IsController = true; // Set DTLS role to be server. IceRole = IceRolesEnum.passive; } else { // Set DTLS role to be server. // As of 20 Jun 2020 the DTLS handshake logic is based on OpenSSL and the mechanism // used hands over the socket handle to the C++ class. This logic works a lot better // when acting as the server in the handshake. IceRole = IceRolesEnum.passive; } if (remoteIceUser != null && remoteIcePassword != null) { _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword); } if (!string.IsNullOrWhiteSpace(dtlsFingerprint)) { dtlsFingerprint = dtlsFingerprint.Trim().ToLower(); if (dtlsFingerprint.Length < DTLS_FINGERPRINT_DIGEST.Length + 1) { logger.LogWarning($"The DTLS fingerprint was too short."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else if (!dtlsFingerprint.StartsWith(DTLS_FINGERPRINT_DIGEST)) { logger.LogWarning($"The DTLS fingerprint was supplied with an unsupported digest function (supported one is {DTLS_FINGERPRINT_DIGEST}): {dtlsFingerprint}."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else { dtlsFingerprint = dtlsFingerprint.Substring(DTLS_FINGERPRINT_DIGEST.Length + 1).Trim().Replace(":", "").Replace(" ", ""); RemotePeerDtlsFingerprint = ByteBufferInfo.ParseHexStr(dtlsFingerprint); logger.LogDebug($"The DTLS fingerprint for the remote peer's SDP {ByteBufferInfo.HexStr(RemotePeerDtlsFingerprint)}."); } } else { logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description."); return(SetDescriptionResultEnum.DtlsFingerprintMissing); } // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } } signalingState = RTCSignalingState.have_remote_offer; onsignalingstatechange?.Invoke(); } return(setResult); }
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.Warn("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(activeAnnouncement != null) activeAnnouncement.Connection = SDPConnectionInformation.ParseConnectionInformation(sdpLine); else sdp.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.Warn("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.Warn("Invalid media format attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLine); } } else { logger.Warn("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.Warn("Invalid media format parameter attribute in SDP: " + sdpLine); } } else { activeAnnouncement.AddExtra(sdpLine); } } else { logger.Warn("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.Error("Exception ParseSDPDescription. " + excp.Message); throw excp; } }
/// <summary> /// Sets the local SDP. /// </summary> /// <param name="sdp">The SDP to set.</param> public void setLocalDescription(SDP sdp) { SDP = sdp; }
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; } }
/// <summary> /// Generates the SDP for an offer or answer. /// </summary> private async Task <SDP> getSdp(string setupAttribute) { try { DateTime startGatheringTime = DateTime.Now; IceConnectionState = IceConnectionStatesEnum.Gathering; await GetIceCandidatesAsync(); logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}."); IceConnectionState = IceConnectionStatesEnum.GatheringComplete; if (LocalIceCandidates.Count == 0) { //logger.LogWarning("No local socket candidates were found for WebRTC call closing."); //Close("No local ICE candidates available."); throw new ApplicationException("No local ICE candidates available."); } else { bool haveIceCandidatesBeenAdded = false; string localIceCandidateString = null; foreach (var iceCandidate in LocalIceCandidates) { localIceCandidateString += iceCandidate.ToString(); } LocalIceUser = LocalIceUser ?? Crypto.GetRandomString(20); LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20); SDP offerSdp = new SDP(IPAddress.Loopback); offerSdp.SessionId = Crypto.GetRandomInt(5).ToString(); // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed // on a single RTP socket. if (RtpSession.AudioTrack != null && RtpSession.VideoTrack != null) { offerSdp.Group = MEDIA_GROUPING; } // --- Audio announcement --- if (RtpSession.AudioTrack != null) { SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.audio, _rtpChannel.RTPPort, RtpSession.AudioTrack.Capabilties); audioAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { audioAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } audioAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); audioAnnouncement.IceUfrag = LocalIceUser; audioAnnouncement.IcePwd = LocalIcePassword; audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); audioAnnouncement.AddExtra(setupAttribute); audioAnnouncement.MediaStreamStatus = RtpSession.AudioTrack.Transceiver.Direction; audioAnnouncement.MediaID = RtpSession.AudioTrack.Transceiver.MID; offerSdp.Media.Add(audioAnnouncement); } // --- Video announcement --- if (RtpSession.VideoTrack != null) { SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement( SDPMediaTypesEnum.video, _rtpChannel.RTPPort, RtpSession.VideoTrack.Capabilties); videoAnnouncement.Transport = RTP_MEDIA_PROFILE; if (!haveIceCandidatesBeenAdded) { videoAnnouncement.IceCandidates = LocalIceCandidates; haveIceCandidatesBeenAdded = true; } videoAnnouncement.Connection = new SDPConnectionInformation(IPAddress.Any); videoAnnouncement.IceUfrag = LocalIceUser; videoAnnouncement.IcePwd = LocalIcePassword; videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint; videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE); videoAnnouncement.AddExtra(setupAttribute); videoAnnouncement.MediaStreamStatus = RtpSession.VideoTrack.Transceiver.Direction; videoAnnouncement.MediaID = RtpSession.VideoTrack.Transceiver.MID;; offerSdp.Media.Add(videoAnnouncement); } OnSdpOfferReady?.Invoke(offerSdp); return(offerSdp); } // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check. // If there are no remote candidates this call will end up being a NOP. //SendStunConnectivityChecks(null); //if (_doDtlsHandshake != null) //{ // _ = Task.Run(() => // { // int result = _doDtlsHandshake(this); // IsDtlsNegotiationComplete = (result == 0); // }); //} } catch (Exception excp) { logger.LogError("Exception getSdp. " + excp); Close(excp.Message); throw; } }
public void SetRemoteSDP(SDP sdp) { _remoteSDP = sdp; IPAddress remoteRTPIPAddress = IPAddress.Parse(sdp.Connection.ConnectionAddress); int remoteAudioPort = 0; int remoteVideoPort = 0; var audioAnnouncement = sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnouncement != null) { remoteAudioPort = audioAnnouncement.Port; } var videoAnnouncement = sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnouncement != null) { remoteVideoPort = videoAnnouncement.Port; } if (remoteAudioPort != 0) { _remoteAudioEP = new IPEndPoint(remoteRTPIPAddress, remoteAudioPort); logger.Debug("RTP channel remote end point set to audio socket of " + _remoteAudioEP + "."); _rtpAudioChannel.RemoteEndPoint = _remoteAudioEP; _rtpAudioChannel.Start(); } if (remoteVideoPort != 0) { _remoteVideoEP = new IPEndPoint(remoteRTPIPAddress, remoteVideoPort); logger.Debug("RTP channel remote end point set to video socket of " + _remoteVideoEP + "."); _rtpVideoChannel.RemoteEndPoint = new IPEndPoint(remoteRTPIPAddress, remoteVideoPort); _rtpVideoChannel.Start(); } if (remoteAudioPort == 0 && remoteVideoPort == 0) { logger.Warn("No audio or video end point could be extracted from the remote SDP."); } }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param> public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; remoteDescription = description; SDP remoteSdp = SDP.ParseSDPDescription(init.sdp); SdpType sdpType = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer; var setResult = base.SetRemoteDescription(sdpType, remoteSdp); if (setResult == SetDescriptionResultEnum.OK) { string remoteIceUser = remoteSdp.IceUfrag; string remoteIcePassword = remoteSdp.IcePwd; string dtlsFingerprint = remoteSdp.DtlsFingerprint; var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { remoteIceUser = remoteIceUser ?? audioAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint; } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null) { remoteIceUser = remoteIceUser ?? videoAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint; } } SdpSessionID = remoteSdp.SessionId; if (init.type == RTCSdpType.answer) { _rtpIceChannel.IsController = true; // Set DTLS role to be server. IceRole = IceRolesEnum.passive; } else { // Set DTLS role as client. IceRole = IceRolesEnum.active; } if (remoteIceUser != null && remoteIcePassword != null) { _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword); } if (!string.IsNullOrWhiteSpace(dtlsFingerprint)) { dtlsFingerprint = dtlsFingerprint.Trim().ToLower(); if (RTCDtlsFingerprint.TryParse(dtlsFingerprint, out var remoteFingerprint)) { RemotePeerDtlsFingerprint = remoteFingerprint; } else { logger.LogWarning($"The DTLS fingerprint was invalid or not supported."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } } else { logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description."); return(SetDescriptionResultEnum.DtlsFingerprintMissing); } // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } } signalingState = RTCSignalingState.have_remote_offer; onsignalingstatechange?.Invoke(); } return(setResult); }