private void ProcessMediaAnswer(SIP_Call call, SDP_Message offer, SDP_Message answer) { if (call == null) { throw new ArgumentNullException("call"); } if (offer == null) { throw new ArgumentNullException("offer"); } if (answer == null) { throw new ArgumentNullException("answer"); } try { #region SDP basic validation // Version field must exist. if (offer.Version == null) { call.Terminate("Invalid SDP answer: Required 'v'(Protocol Version) field is missing."); return; } // Origin field must exist. if (offer.Origin == null) { call.Terminate("Invalid SDP answer: Required 'o'(Origin) field is missing."); return; } // Session Name field. // Check That global 'c' connection attribute exists or otherwise each enabled media stream must contain one. if (offer.Connection == null) { for (int i = 0; i < offer.MediaDescriptions.Count; i++) { if (offer.MediaDescriptions[i].Connection == null) { call.Terminate("Invalid SDP answer: Global or per media stream no: " + i + " 'c'(Connection) attribute is missing."); return; } } } // Check media streams count. if (offer.MediaDescriptions.Count != answer.MediaDescriptions.Count) { call.Terminate("Invalid SDP answer, media descriptions count in answer must be equal to count in media offer (RFC 3264 6.)."); return; } #endregion // Process media streams info. for (int i = 0; i < offer.MediaDescriptions.Count; i++) { SDP_MediaDescription offerMedia = offer.MediaDescriptions[i]; SDP_MediaDescription answerMedia = answer.MediaDescriptions[i]; // Remote-party disabled this stream. if (answerMedia.Port == 0) { #region Cleanup active RTP stream and it's resources, if it exists // Dispose existing RTP session. if (offerMedia.Tags.ContainsKey("rtp_session")) { ((RTP_Session)offerMedia.Tags["rtp_session"]).Dispose(); offerMedia.Tags.Remove("rtp_session"); } // Release UPnPports if any. if (offerMedia.Tags.ContainsKey("upnp_rtp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)offerMedia.Tags["upnp_rtp_map"]); } catch { } offerMedia.Tags.Remove("upnp_rtp_map"); } if (offerMedia.Tags.ContainsKey("upnp_rtcp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)offerMedia.Tags["upnp_rtcp_map"]); } catch { } offerMedia.Tags.Remove("upnp_rtcp_map"); } #endregion } // Remote-party accepted stream. else { Dictionary<int, AudioCodec> audioCodecs = (Dictionary<int, AudioCodec>)offerMedia.Tags["audio_codecs"]; #region Validate stream-mode disabled,inactive,sendonly,recvonly /* RFC 3264 6.1. If a stream is offered as sendonly, the corresponding stream MUST be marked as recvonly or inactive in the answer. If a media stream is listed as recvonly in the offer, the answer MUST be marked as sendonly or inactive in the answer. If an offered media stream is listed as sendrecv (or if there is no direction attribute at the media or session level, in which case the stream is sendrecv by default), the corresponding stream in the answer MAY be marked as sendonly, recvonly, sendrecv, or inactive. If an offered media stream is listed as inactive, it MUST be marked as inactive in the answer. */ // If we disabled this stream in offer and answer enables it (no allowed), terminate call. if (offerMedia.Port == 0) { call.Terminate("Invalid SDP answer, you may not enable sdp-offer disabled stream no: " + i + " (RFC 3264 6.)."); return; } RTP_StreamMode offerStreamMode = GetRtpStreamMode(offer, offerMedia); RTP_StreamMode answerStreamMode = GetRtpStreamMode(answer, answerMedia); if (offerStreamMode == RTP_StreamMode.Send && answerStreamMode != RTP_StreamMode.Receive) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'recvonly' (RFC 3264 6.)."); return; } if (offerStreamMode == RTP_StreamMode.Receive && answerStreamMode != RTP_StreamMode.Send) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'sendonly' (RFC 3264 6.)."); return; } if (offerStreamMode == RTP_StreamMode.Inactive && answerStreamMode != RTP_StreamMode.Inactive) { call.Terminate("Invalid SDP answer, sdp stream no: " + i + " stream-mode must be 'inactive' (RFC 3264 6.)."); return; } #endregion #region Create/modify RTP session RTP_Session rtpSession = (RTP_Session)offerMedia.Tags["rtp_session"]; rtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]); rtpSession.StreamMode = (answerStreamMode == RTP_StreamMode.Inactive ? RTP_StreamMode.Inactive : offerStreamMode); rtpSession.RemoveTargets(); if (GetSdpHost(answer, answerMedia) != "0.0.0.0") { rtpSession.AddTarget(GetRtpTarget(answer, answerMedia)); } rtpSession.Start(); #endregion #region Create/modify audio-in source if (!offerMedia.Tags.ContainsKey("rtp_audio_in")) { AudioIn_RTP rtpAudioIn = new AudioIn_RTP(m_pAudioInDevice, 20, audioCodecs, rtpSession.CreateSendStream()); rtpAudioIn.Start(); offerMedia.Tags.Add("rtp_audio_in", rtpAudioIn); } #endregion } } call.LocalSDP = offer; call.RemoteSDP = answer; } catch (Exception x) { call.Terminate("Error processing SDP answer: " + x.Message); } }
private void ProcessMediaOffer(SIP_Dialog dialog, SIP_ServerTransaction transaction, RTP_MultimediaSession rtpMultimediaSession, SDP_Message offer, SDP_Message localSDP) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (rtpMultimediaSession == null) { throw new ArgumentNullException("rtpMultimediaSession"); } if (offer == null) { throw new ArgumentNullException("offer"); } if (localSDP == null) { throw new ArgumentNullException("localSDP"); } try { #region SDP basic validation // Version field must exist. if (offer.Version == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'v'(Protocol Version) field is missing.", transaction.Request)); return; } // Origin field must exist. if (offer.Origin == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'o'(Origin) field is missing.", transaction.Request)); return; } // Session Name field. // Check That global 'c' connection attribute exists or otherwise each enabled media stream must contain one. if (offer.Connection == null) { for (int i = 0; i < offer.MediaDescriptions.Count; i++) { if (offer.MediaDescriptions[i].Connection == null) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Global or per media stream no: " + i + " 'c'(Connection) attribute is missing.", transaction.Request)); return; } } } #endregion // Re-INVITE media streams count must be >= current SDP streams. if (localSDP.MediaDescriptions.Count > offer.MediaDescriptions.Count) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": re-INVITE SDP offer media stream count must be >= current session stream count.", transaction.Request)); return; } bool audioAccepted = false; // Process media streams info. for (int i = 0; i < offer.MediaDescriptions.Count; i++) { SDP_MediaDescription offerMedia = offer.MediaDescriptions[i]; SDP_MediaDescription answerMedia = (localSDP.MediaDescriptions.Count > i ? localSDP.MediaDescriptions[i] : null); // Disabled stream. if (offerMedia.Port == 0) { // Remote-party offered new disabled stream. if (answerMedia == null) { // Just copy offer media stream data to answer and set port to zero. localSDP.MediaDescriptions.Add(offerMedia); localSDP.MediaDescriptions[i].Port = 0; } // Existing disabled stream or remote party disabled it. else { answerMedia.Port = 0; #region Cleanup active RTP stream and it's resources, if it exists // Dispose existing RTP session. if (answerMedia.Tags.ContainsKey("rtp_session")) { ((RTP_Session)offerMedia.Tags["rtp_session"]).Dispose(); answerMedia.Tags.Remove("rtp_session"); } // Release UPnPports if any. if (answerMedia.Tags.ContainsKey("upnp_rtp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtp_map"]); } catch { } answerMedia.Tags.Remove("upnp_rtp_map"); } if (answerMedia.Tags.ContainsKey("upnp_rtcp_map")) { try { m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtcp_map"]); } catch { } answerMedia.Tags.Remove("upnp_rtcp_map"); } #endregion } } // Remote-party wants to communicate with this stream. else { // See if we can support this stream. if (!audioAccepted && CanSupportMedia(offerMedia)) { // New stream. if (answerMedia == null) { answerMedia = new SDP_MediaDescription(SDP_MediaTypes.audio, 0, 2, "RTP/AVP", null); localSDP.MediaDescriptions.Add(answerMedia); } #region Build audio codec map with codecs which we support Dictionary<int, AudioCodec> audioCodecs = GetOurSupportedAudioCodecs(offerMedia); answerMedia.MediaFormats.Clear(); answerMedia.Attributes.Clear(); foreach (KeyValuePair<int, AudioCodec> entry in audioCodecs) { answerMedia.Attributes.Add(new SDP_Attribute("rtpmap", entry.Key + " " + entry.Value.Name + "/" + entry.Value.CompressedAudioFormat.SamplesPerSecond)); answerMedia.MediaFormats.Add(entry.Key.ToString()); } answerMedia.Attributes.Add(new SDP_Attribute("ptime", "20")); answerMedia.Tags["audio_codecs"] = audioCodecs; #endregion #region Create/modify RTP session // RTP session doesn't exist, create it. if (!answerMedia.Tags.ContainsKey("rtp_session")) { RTP_Session rtpSess = CreateRtpSession(rtpMultimediaSession); // RTP session creation failed,disable this stream. if (rtpSess == null) { answerMedia.Port = 0; break; } answerMedia.Tags.Add("rtp_session", rtpSess); rtpSess.NewReceiveStream += delegate(object s, RTP_ReceiveStreamEventArgs e) { if (answerMedia.Tags.ContainsKey("rtp_audio_out")) { ((AudioOut_RTP)answerMedia.Tags["rtp_audio_out"]).Dispose(); } AudioOut_RTP audioOut = new AudioOut_RTP(m_pAudioOutDevice, e.Stream, audioCodecs); audioOut.Start(); answerMedia.Tags["rtp_audio_out"] = audioOut; }; // NAT if (!HandleNAT(answerMedia, rtpSess)) { // NAT handling failed,disable this stream. answerMedia.Port = 0; break; } } RTP_StreamMode offerStreamMode = GetRtpStreamMode(offer, offerMedia); if (offerStreamMode == RTP_StreamMode.Inactive) { answerMedia.SetStreamMode("inactive"); } else if (offerStreamMode == RTP_StreamMode.Receive) { answerMedia.SetStreamMode("sendonly"); } else if (offerStreamMode == RTP_StreamMode.Send) { answerMedia.SetStreamMode("recvonly"); } else if (offerStreamMode == RTP_StreamMode.SendReceive) { answerMedia.SetStreamMode("sendrecv"); } RTP_Session rtpSession = (RTP_Session)answerMedia.Tags["rtp_session"]; rtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]); rtpSession.StreamMode = GetRtpStreamMode(localSDP, answerMedia); rtpSession.RemoveTargets(); if (GetSdpHost(offer, offerMedia) != "0.0.0.0") { rtpSession.AddTarget(GetRtpTarget(offer, offerMedia)); } rtpSession.Start(); #endregion #region Create/modify audio-in source if (!answerMedia.Tags.ContainsKey("rtp_audio_in")) { AudioIn_RTP rtpAudioIn = new AudioIn_RTP(m_pAudioInDevice, 20, audioCodecs, rtpSession.CreateSendStream()); rtpAudioIn.Start(); answerMedia.Tags.Add("rtp_audio_in", rtpAudioIn); } else { ((AudioIn_RTP)answerMedia.Tags["rtp_audio_in"]).AudioCodecs = audioCodecs; } #endregion audioAccepted = true; } // We don't accept this stream, so disable it. else { // Just copy offer media stream data to answer and set port to zero. // Delete exisiting media stream. if (answerMedia != null) { localSDP.MediaDescriptions.RemoveAt(i); } localSDP.MediaDescriptions.Add(offerMedia); localSDP.MediaDescriptions[i].Port = 0; } } } #region Create and send 2xx response SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, transaction.Request, transaction.Flow); //response.Contact = SIP stack will allocate it as needed; response.ContentType = "application/sdp"; response.Data = localSDP.ToByte(); transaction.SendResponse(response); // Start retransmitting 2xx response, while ACK receives. Handle2xx(dialog, transaction); // REMOVE ME: 27.11.2010 // Start retransmitting 2xx response, while ACK receives. //m_pInvite2xxMgr.Add(dialog,transaction); #endregion } catch (Exception x) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, transaction.Request)); } }
/// <summary> /// This method is called when RTP session creates new send stream. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pRtpSession_NewSendStream(object sender, RTP_SendStreamEventArgs e) { d_SendStream = Dispatcher.BeginInvoke(new Action(delegate() { var selectedInDevice = cbAudioInDevices.SelectedItem as AudioInDevice; m_pAudioInRTP = new AudioIn_RTP(selectedInDevice, 20, m_pAudioCodecs, m_pSendStream); m_pAudioInRTP.Start(); })); }