Привет всем, у меня есть IP-камера, которая поддерживает SIP. То, что я хочу сделать, это позвонить в камеру, используя протокол SIP от моего c# клиента. Я использовал библиотеку стека sum lumisoft для этого, но я получаю 401 несанкционированную ошибку, когда я вызываю метод приглашения в полученном ответе. Мой код, который я пробовал до сих пор -
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using LumiSoft.Net;
namespace VMSServer_Interface.TwoWayAudio.Sip
{
sealed class SipSendAudio
{
private SIP_Stack stack = null;
private int sipPort = 5060;
private int rtpBasePort = 21240;
private Dictionary<int, AudioCodec> m_pAudioCodecs = null;
private SIP_Call m_pCall = null;
private AudioOutDevice m_pAudioOutDevice = null;
private AudioInDevice m_pAudioInDevice = null;
private IPAddress rtpIP = null;
public SipSendAudio(Camera camera ): base(camera)
{
}
private void Call_Click(ref string error)
{
try
{
m_pAudioOutDevice = AudioOut.Devices[0];
m_pAudioInDevice = AudioIn.Devices[0];
m_pAudioCodecs = new Dictionary<int, AudioCodec>();
m_pAudioCodecs.Add(0, new PCMU());// We are using mu law encoding technique
// for sip call we have to bind sip stack with ip address
stack = new SIP_Stack();
stack.UserAgent = "I2V";
GetBindIP();//Get the required ip to bind with and ssame ip is used to create rtp session
if (rtpIP == null)
{
throw new Exception("Unable to bind to Any IP Address");
}
stack.BindInfo = new IPBindInfo[] { new IPBindInfo("", BindInfoProtocol.UDP,rtpIP , sipPort) };//IPAddress.Any
stack.Error += new EventHandler<ExceptionEventArgs>(m_pStack_Error);// handle stack error
stack.Start();
SIP_t_NameAddress to = null;
SIP_t_NameAddress from = null;
string ip = "sip:camera@" + this.Camera.IP_LAN +":5060"; // sip:user@ip:port
to = new SIP_t_NameAddress(ip);
if (!to.IsSipOrSipsUri)// Checks if it is valid sip uri or not
{
throw new ArgumentException();
}
string pc_ip = "sip:client@127.0.0.1";
from = new SIP_t_NameAddress(pc_ip);
Call(from, to);// starts calling
}
catch (Exception x)
{
error = x.Message;
ExceptionHandler.handleException(x);
}
}
/// <summary>
/// Is called when SIp stack has unhandled error.
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="e">Event data.</param>
private void m_pStack_Error(object sender, ExceptionEventArgs e)
{
ExceptionHandler.handleException(" SipSendAudio Stack Error: " + e.Exception.Message);
this.SendingAudio = false;//
}
private void Call_StateChanged(object sender, EventArgs e)
{
if(m_pCall.State == SIP_Call.SIP_CallState.Active)
{
this.SendingAudio = true;
}
else if (m_pCall.State == SIP_Call.SIP_CallState.Terminated)
{
this.SendingAudio = false;
SDP_Message localSDP = m_pCall.LocalSDP;
foreach (SDP_MediaDescription media in localSDP.MediaDescriptions)
{
if (media.Tags.ContainsKey("rtp_audio_in"))
{
((AudioIn_RTP)media.Tags["rtp_audio_in"]).Dispose();//disposing tags in media description
}
if (media.Tags.ContainsKey("rtp_audio_out"))
{
((AudioOut_RTP)media.Tags["rtp_audio_out"]).Dispose();
}
}
if (m_pCall.RtpMultimediaSession != null)
{
m_pCall.RtpMultimediaSession.Dispose();//disposing rtpmultimedia session
}
if (m_pCall.Dialog != null && m_pCall.Dialog.IsTerminatedByRemoteParty)
{
//
}
}
else if (m_pCall.State == SIP_Call.SIP_CallState.Disposed)
{
m_pCall = null;
}
}
private void Call(SIP_t_NameAddress from, SIP_t_NameAddress to)
{
if (from == null)
{
throw new ArgumentNullException("from");
}
if (to == null)
{
throw new ArgumentNullException("to");
}
RTP_MultimediaSession rtpMultimediaSession = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());
RTP_Session rtpSession = CreateRtpSession(rtpMultimediaSession);// Create RTP multimedia session beteen sender and receiver
// Port search failed.
if (rtpSession == null)
{
throw new Exception();
}
SDP_Message sdpOffer = new SDP_Message();//creating sdp message that is sent to the remote
sdpOffer.Version = "0";
sdpOffer.Origin = new SDP_Origin("-", sdpOffer.GetHashCode(), 1, "IN", "IP4",rtpSession.LocalEP.IP.ToString());// rtpSession.LocalEP.IP.ToString());
sdpOffer.SessionName = "SIP Call";
sdpOffer.Times.Add(new SDP_Time(0, 0));
SDP_MediaDescription mediaStream = new SDP_MediaDescription(SDP_MediaTypes.audio, 0, 1, "RTP/AVP", null);
rtpSession.NewReceiveStream += delegate(object s, RTP_ReceiveStreamEventArgs e)//gets called whenever new stream is received from remote object
{
AudioOut_RTP audioOut = new AudioOut_RTP(m_pAudioOutDevice, e.Stream, m_pAudioCodecs);
audioOut.Start();// starts playing received audio stream
mediaStream.Tags["rtp_audio_out"] = audioOut;
};
foreach (KeyValuePair<int, AudioCodec> entry in m_pAudioCodecs)// adding available encoding technique
{
mediaStream.Attributes.Add(new SDP_Attribute("rtpmap", entry.Key + " " + entry.Value.Name + "/" + entry.Value.CompressedAudioFormat.SamplesPerSecond));
mediaStream.MediaFormats.Add(entry.Key.ToString());
}
mediaStream.Attributes.Add(new SDP_Attribute("ptime", "20"));
mediaStream.Attributes.Add(new SDP_Attribute("sendrecv", ""));
mediaStream.Attributes.Add(new SDP_Attribute("sendonly", ""));
mediaStream.Tags["rtp_session"] = rtpSession;
mediaStream.Tags["audio_codecs"] = m_pAudioCodecs;
sdpOffer.MediaDescriptions.Add(mediaStream);//adding media description to the sdp
IPEndPoint rtpPublicEP = rtpSession.LocalEP.RtpEP;
IPEndPoint rtcpPublicEP = rtpSession.LocalEP.RtcpEP;
mediaStream.Port = rtpPublicEP.Port;
mediaStream.Connection = new SDP_Connection("IN", "IP4", rtpPublicEP.Address.ToString());
// Create INVITE request.
SIP_Request invite = stack.CreateRequest(SIP_Methods.INVITE, to, from);
invite.ContentType = "application/sdp";
invite.Data = sdpOffer.ToByte();// setting sdp into invite data
SIP_RequestSender sender = stack.CreateRequestSender(invite);
// Create call.
m_pCall = new SIP_Call(stack, sender, rtpMultimediaSession);
m_pCall.LocalSDP = sdpOffer;
m_pCall.StateChanged += new EventHandler(Call_StateChanged);
m_pCall.onError += new EventHandler<SipCallErrorEventArgs>(CallTerminatedOnErrorHandler);
bool finalResponseSeen = false;
List<SIP_Dialog_Invite> earlyDialogs = new List<SIP_Dialog_Invite>();
sender.Credentials.Add(new NetworkCredential("root", "root1234"));
//sender.Request.Authorization = GetCredentials();
sender.ResponseReceived += delegate(object s, SIP_ResponseReceivedEventArgs e)// gets called when we receive the invite response from the remote
{
// Skip 2xx retransmited response.
if (finalResponseSeen)
{
return;
}
else
{
if(e.Response.StatusCode >= 200)
{
finalResponseSeen = true;
}
try
{
if (e.Response.StatusCodeType == SIP_StatusCodeType.Provisional)
{
if (e.Response.StatusCode > 100 && e.Response.To.Tag != null)
{
earlyDialogs.Add((SIP_Dialog_Invite)e.GetOrCreateDialog);
}
// 180_Ringing.
if (e.Response.StatusCode == 180)
{
//
}
}
else if (e.Response.StatusCodeType == SIP_StatusCodeType.Success)
{
SIP_Dialog dialog = e.GetOrCreateDialog;
foreach (SIP_Dialog_Invite d in earlyDialogs.ToArray())
{
if (!d.Equals(dialog))
{
d.Terminate("Another forking leg accepted.", true);
}
}
m_pCall.InitCalling(dialog, sdpOffer);
// Remote-party provided SDP.
if (e.Response.ContentType != null && e.Response.ContentType.ToLower().IndexOf("application/sdp") > -1)
{
try
{
// SDP offer. We sent offerless INVITE, we need to send SDP answer in ACK request.'
if (e.ClientTransaction.Request.ContentType == null || e.ClientTransaction.Request.ContentType.ToLower().IndexOf("application/sdp") == -1)
{
// Currently we never do it, so it never happens. This is place holder, if we ever support it.
}
// SDP answer to our offer.
else
{
// This method takes care of ACK sending and 2xx response retransmission ACK sending.
HandleAck(m_pCall.Dialog, e.ClientTransaction);
//ProcessMediaAnswer(m_pCall, m_pCall.LocalSDP, SDP_Message.Parse(Encoding.UTF8.GetString(e.Response.Data)));
}
}
catch (Exception x)
{
m_pCall.Terminate("SDP answer parsing/processing failed.");
ExceptionHandler.handleException(x);
}
}
else
{
// If we provided SDP offer, there must be SDP answer.
if (e.ClientTransaction.Request.ContentType != null && e.ClientTransaction.Request.ContentType.ToLower().IndexOf("application/sdp") > -1)
{
m_pCall.Terminate("Invalid 2xx response, required SDP answer is missing.");//"Invalid 2xx response, required SDP answer is missing."
}
}
}
else
{
foreach (SIP_Dialog_Invite dialog in earlyDialogs.ToArray())
{
dialog.Terminate("All early dialogs are considered terminated upon reception of the non-2xx final response. (RFC 3261 13.2.2.3)", false);
}
if (m_pCall.State != SIP_Call.SIP_CallState.Terminating)
{
}
// Terminate call.
m_pCall.Terminate("Remote party rejected a call.", sendbye: false);
}
}
catch (Exception x)
{
ExceptionHandler.handleException(x);
}
}
};
sender.Start();
}
private void CallTerminatedOnErrorHandler(object sender, SipCallErrorEventArgs e)
{
OnDisconnected(e.Error);
}
private RTP_Session CreateRtpSession(RTP_MultimediaSession rtpMultimediaSession)
{
if (rtpMultimediaSession == null)
{
throw new ArgumentNullException();
}
//--- Search RTP IP -------------------------------------------------------//
// rtpIP is used to create rtp session
//------------------------------------------------------------------------//
// Search free ports for RTP session.
for (int i = 0; i < 100; i += 2)
{
try
{
return rtpMultimediaSession.CreateSession(new RTP_Address(rtpIP, rtpBasePort, rtpBasePort + 1), new RTP_Clock(1, 8000));
}
catch
{
rtpBasePort += 2;
}
}
return null;
}
private RTP_StreamMode GetRtpStreamMode(SDP_Message sdp, SDP_MediaDescription media)
{
if (sdp == null)
{
throw new ArgumentNullException("sdp");
}
if (media == null)
{
throw new ArgumentNullException("media");
}
// Try to get per media stream mode.
foreach (SDP_Attribute a in media.Attributes)
{
if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.SendReceive;
}
else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Send;
}
else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Receive;
}
else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Inactive;
}
}
// No per media stream mode, try to get per session stream mode.
foreach (SDP_Attribute a in sdp.Attributes)
{
if (string.Equals(a.Name, "sendrecv", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.SendReceive;
}
else if (string.Equals(a.Name, "sendonly", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Send;
}
else if (string.Equals(a.Name, "recvonly", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Receive;
}
else if (string.Equals(a.Name, "inactive", StringComparison.InvariantCultureIgnoreCase))
{
return RTP_StreamMode.Inactive;
}
}
return RTP_StreamMode.SendReceive;
}
private string GetSdpHost(SDP_Message sdp, SDP_MediaDescription mediaStream)
{
if (sdp == null)
{
throw new ArgumentNullException("sdp");
}
if (mediaStream == null)
{
throw new ArgumentNullException("mediaStream");
}
// We must have SDP global or per media connection info.
string host = mediaStream.Connection != null ? mediaStream.Connection.Address : null;
if (host == null)
{
host = sdp.Connection.Address != null ? sdp.Connection.Address : null;
if (host == null)
{
throw new ArgumentException("Invalid SDP message, global or per media 'c'(Connection) attribute is missing.");
}
}
return host;
}
/// <summary>
/// Gets RTP target for SDP media stream.
/// </summary>
/// <param name="sdp">SDP message.</param>
/// <param name="mediaStream">SDP media stream.</param>
/// <returns>Return RTP target.</returns>
/// <exception cref="ArgumentNullException">Is raised when <b>sdp</b> or <b>mediaStream</b> is null reference.</exception>
private RTP_Address GetRtpTarget(SDP_Message sdp, SDP_MediaDescription mediaStream)
{
if (sdp == null)
{
throw new ArgumentNullException("sdp");
}
if (mediaStream == null)
{
throw new ArgumentNullException("mediaStream");
}
// We must have SDP global or per media connection info.
string host = mediaStream.Connection != null ? mediaStream.Connection.Address : null;
if (host == null)
{
host = sdp.Connection.Address != null ? sdp.Connection.Address : null;
if (host == null)
{
throw new ArgumentException("Invalid SDP message, global or per media 'c'(Connection) attribute is missing.");
}
}
int remoteRtcpPort = mediaStream.Port + 1;
// Use specified RTCP port, if specified.
foreach (SDP_Attribute attribute in mediaStream.Attributes)
{
if (string.Equals(attribute.Name, "rtcp", StringComparison.InvariantCultureIgnoreCase))
{
remoteRtcpPort = Convert.ToInt32(attribute.Value);
break;
}
}
return new RTP_Address(System.Net.Dns.GetHostAddresses(host)[0], mediaStream.Port, remoteRtcpPort);
}
/// <summary>
/// This method takes care of ACK sending and 2xx response retransmission ACK sending.
/// </summary>
/// <param name="dialog">SIP dialog.</param>
/// <param name="transaction">SIP client transaction.</param>
private void HandleAck(SIP_Dialog dialog, SIP_ClientTransaction transaction)
{
if (dialog == null)
{
throw new ArgumentNullException("dialog");
}
if (transaction == null)
{
throw new ArgumentNullException("transaction");
}
SIP_t_ViaParm via = new SIP_t_ViaParm();
via.ProtocolName = "SIP";
via.ProtocolVersion = "2.0";
via.ProtocolTransport = transaction.Flow.Transport;
via.SentBy = new HostEndPoint(transaction.Flow.LocalEP);
via.Branch = SIP_t_ViaParm.CreateBranch();
via.RPort = 0;
SIP_Request ackRequest = dialog.CreateRequest(SIP_Methods.ACK);
ackRequest.Via.AddToTop(via.ToStringValue());
ackRequest.CSeq = new SIP_t_CSeq(transaction.Request.CSeq.SequenceNumber, SIP_Methods.ACK);
// Authorization
foreach (SIP_HeaderField h in transaction.Request.Authorization.HeaderFields)
{
ackRequest.Authorization.Add(h.Value);
}
// Proxy-Authorization
foreach (SIP_HeaderField h in transaction.Request.ProxyAuthorization.HeaderFields)
{
ackRequest.Authorization.Add(h.Value);
}
// Send ACK.
SendAck(dialog, ackRequest);
// Start receive 2xx retransmissions.
transaction.ResponseReceived += delegate(object sender, SIP_ResponseReceivedEventArgs e)
{
if (dialog.State == SIP_DialogState.Disposed || dialog.State == SIP_DialogState.Terminated)
{
return;
}
// Don't send ACK for forked 2xx, our sent BYE(to all early dialogs) or their early timer will kill these dialogs.
// Send ACK only to our accepted dialog 2xx response retransmission.
if (e.Response.From.Tag == ackRequest.From.Tag && e.Response.To.Tag == ackRequest.To.Tag)
{
SendAck(dialog, ackRequest);
}
};
}
/// <summary>
/// Sends ACK to remote-party.
/// </summary>
/// <param name="dialog">SIP dialog.</param>
/// <param name="ack">SIP ACK request.</param>
private void SendAck(SIP_Dialog dialog, SIP_Request ack)
{
if (dialog == null)
{
throw new ArgumentNullException("dialog");
}
if (ack == null)
{
throw new ArgumentNullException("ack");
}
try
{
// Try existing flow.
dialog.Flow.Send(ack);
// Log
if (dialog.Stack.Logger != null)
{
byte[] ackBytes = ack.ToByteData();
dialog.Stack.Logger.AddWrite(
dialog.ID,
null,
ackBytes.Length,
"Request [DialogID='" + dialog.ID + "';" + "method='" + ack.RequestLine.Method + "'; cseq='" + ack.CSeq.SequenceNumber + "'; " +
"transport='" + dialog.Flow.Transport + "'; size='" + ackBytes.Length + "'] sent '" + dialog.Flow.LocalEP + "' -> '" + dialog.Flow.RemoteEP + "'.",
dialog.Flow.LocalEP,
dialog.Flow.RemoteEP,
ackBytes
);
}
}
catch
{
try
{
dialog.Stack.TransportLayer.SendRequest(ack);
}
catch (Exception x)
{
ExceptionHandler.handleException(x);
}
}
}
/// <summary>
/// Check whether both(firstIP and secondIP) ip belong to same network or not
/// </summary>
/// <param name="firstIP">string firstIP.</param>
/// <param name="subNet">string subNet.</param>
/// <param name="secondIP">string secondIP</param>
/// <return> true if both are from same network, otherwise false</return>
private bool CheckWhetherInSameNetwork(string firstIP, string subNet, string secondIP)
{
uint subnetmaskInInt = ConvertIPToUint(subNet);//Converting subnet to unsigned int
uint firstIPInInt = ConvertIPToUint(firstIP);// converting firstip to unsigned int
uint secondIPInInt = ConvertIPToUint(secondIP);// converting secondip to unsigned int
uint networkPortionofFirstIP = firstIPInInt & subnetmaskInInt;//network portion of firstip by performing AND operation between firstip and subnet
uint networkPortionofSecondIP = secondIPInInt & subnetmaskInInt;
if (networkPortionofFirstIP == networkPortionofSecondIP)
return true;
else
return false;
}
/// <summary>
/// Convert ip address to unsigned integer
/// </summary>
/// <param name="ipAddress">string ipaddress</param>
/// <returns> ip address in unsigned form</returns>
private uint ConvertIPToUint(string ipAddress)
{
System.Net.IPAddress iPAddress = System.Net.IPAddress.Parse(ipAddress);// striing to ip address
byte[] byteIP = iPAddress.GetAddressBytes();//ip address to byte
uint ipInUint = (uint)byteIP[3] << 24;
ipInUint += (uint)byteIP[2] << 16;
ipInUint += (uint)byteIP[1] << 8;
ipInUint += (uint)byteIP[0];
return ipInUint;
}
/// <summary>
/// Get subnet mask of the given ip
/// </summary>
/// <param name="address"> IPAddress address</param>
/// <returns> subnetmask</returns>
/// <exception> if unable to find subnet mask of the given ip</exception>
public static IPAddress GetSubnetMask(IPAddress address)
{
foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces())
{
foreach (UnicastIPAddressInformation unicastIPAddressInformation in adapter.GetIPProperties().UnicastAddresses)
{
if (unicastIPAddressInformation.Address.AddressFamily == AddressFamily.InterNetwork)
{
if (address.Equals(unicastIPAddressInformation.Address))
{
return unicastIPAddressInformation.IPv4Mask;
}
}
}
}
return IPAddress.Parse("255.255.255.0");
}
/// <summary>
/// Get the IP to bind and create rtp session
/// </summary>
private void GetBindIP()
{
string snet = null;
//IPAddress rtpIP = null;
string cameraIP =Camera.IP_LAN.ToString();
foreach (IPAddress ip in Dns.GetHostAddresses(""))
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
string second = ip.ToString();//gets ip of computer
snet = GetSubnetMask(ip).ToString();//gets subnet of above ip
bool result = CheckWhetherInSameNetwork(cameraIP, snet, second);//check whether this is the common network or not
if (result)
{
rtpIP = ip;
break;
}
}
}
}
}
}
Это приложение winforms, которое имеет ссылку https://github.com/pruiz/LumiSoft.Net dll. Пожалуйста, перейдите к методу Call в файле CS в методе call, когда приглашение отправлено, и я получил некоторый ответ в событии ResponseReceived. Обратный вызов 401 не авторизован. Пожалуйста, скажите мне, как я могу решить неавторизованную ошибку или явно указать авторизацию в приглашении.
Также, если это хорошая реализация SIP с открытым исходным кодом, которая работает нормально, пожалуйста, дайте мне знаю. Я также читал этот вопрос о stackoverflow sip, совершающем вызов - 401 Unauthorized , но они не объясняют решение в деталях.
Моя трассировка пакетов Wireshar - ![enter image description here](https://i.stack.imgur.com/E4aqh.png)
Пожалуйста, помогите мне, я застрял на SIP несанкционированной проблеме. Если какие-либо подробности относительно моего SIP звонка, дайте мне знать, я отредактирую вопрос. Thankyou!