// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc
private void Rtsp_MessageReceived(object sender, Rtsp.RtspChunkEventArgs e)
{
Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse;
Console.WriteLine("Received " + message.OriginalRequest.ToString());
// Check if the Message has an Authenticate header and what type it is
if (message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate))
{
String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate];
// Parse www_authenticate
// EG: WWW-Authenticate: Basic realm="xxxxxxx"
// EG: WWW-Authenticate: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE
string[] items = www_authenticate.Split(new char[] { ',', ' ' }); // split on Comma and Space
// Process the first item
if (items.Count() >= 1 && items[0].Equals("Basic"))
{
authentication = AUTHENTICATION.BASIC;
}
else if (items.Count() >= 1 && items[0].Equals("Digest"))
{
authentication = AUTHENTICATION.DIGEST;
}
// Process the remaining items
for (int i = 1; i < items.Count(); i++)
{
string[] parts = items[i].Split(new char[] { '=' }); // Split on Equals
if (parts.Count() >= 2 && parts[0].Trim().Equals("realm"))
{
realm = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes
}
else if (parts.Count() >= 2 && parts[0].Trim().Equals("nonce"))
{
nonce = parts[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes
}
}
}
// If we get a reply to OPTIONS and CSEQ is 1 (which was our first command), then send the DESCRIBE
// If we fer a reply to OPTIONS and CSEQ is not 1, it must have been a keepalive command
if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions)
{
if (message.CSeq == 1)
{
// Start a Timer to send an OPTIONS command (for keepalive) every 20 seconds
keepalive_timer = new System.Timers.Timer();
keepalive_timer.Elapsed += Timer_Elapsed;
keepalive_timer.Interval = 20 * 1000;
keepalive_timer.Enabled = true;
// send the DESCRIBE. First time around we have no WWW-Authorise
Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe();
describe_message.RtspUri = new Uri(url);
rtsp_client.SendMessage(describe_message);
}
else
{
// do nothing
}
}
// If we get a reply to DESCRIBE (which was our second command), then prosess SDP and send the SETUP
if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe)
{
// Got a reply for DESCRIBE
// First time we send DESCRIBE we do not add any authorization (and we could not add it even if we wanted to
// as we will not have the authorization Nonce value required for Digest mode
// So we have to handle the Unauthorized 401 error here and send a new DESCRIBE message
if (message.IsOk == false)
{
Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage);
if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == false))
{
// Error 401 - Unauthorized, but the original request did not use Authorization so try again with Authorization added
if (username == null || password == null)
{
// we do nothave a username or password. Abort
return;
}
// Send a new DESCRIBE with authorization
Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe();
describe_message.RtspUri = new Uri(url);
if (authentication != AUTHENTICATION.NONE)
{
String authorization_string = GenerateAuthorization(username, password, authentication,
realm, nonce, url, "DESCRIBE");
if (authorization_string != null)
{
describe_message.Headers.Add("Authorization", authorization_string);
}
}
rtsp_client.SendMessage(describe_message);
return;
}
else if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true))
{
// Authorization failed
return;
}
else
{
// some other error
return;
}
}
// Examine the SDP
Console.Write(System.Text.Encoding.UTF8.GetString(message.Data));
Rtsp.Sdp.SdpFile sdp_data;
using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data)))
{
sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream);
}
// Process each 'Media' Attribute in the SDP (each sub-stream)
// If the attribute is for Video, then carry out a SETUP and a PLAY
// Only do this for the first Video attribute in case there is more than one in the SDP
for (int x = 0; x < sdp_data.Medias.Count; x++)
{
if (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video)
{
// We only want the first video sub-stream
if (video_payload == -1)
{
video_payload = sdp_data.Medias[x].PayloadType;
// search the attributes for control, fmtp and rtpmap
String control = ""; // the "track" or "stream id"
Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264)
Rtsp.Sdp.AttributRtpMap rtpmap = null; // custom payload (>=96) details
foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs)
{
if (attrib.Key.Equals("control"))
{
String sdp_control = attrib.Value;
if (sdp_control.ToLower().StartsWith("rtsp://"))
{
control = sdp_control; //absolute path
}
else
{
control = url + "/" + sdp_control; // relative path
}
}
if (attrib.Key.Equals("fmtp"))
{
fmtp = attrib as Rtsp.Sdp.AttributFmtp;
}
if (attrib.Key.Equals("rtpmap"))
{
rtpmap = attrib as Rtsp.Sdp.AttributRtpMap;
}
}
// If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64
if (rtpmap != null && rtpmap.Value.Contains("H264") && fmtp != null)
{
video_codec = "H264";
var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter);
var sps_pps = param.SpropParameterSets;
if (sps_pps.Count() >= 2)
{
byte[] sps = sps_pps[0];
byte[] pps = sps_pps[1];
Output_SPS_PPS(sps, pps); // output SPS and PPS
}
}
RtspTransport transport = null;
if (rtp_transport == RTP_TRANSPORT.TCP)
{
// Server interleaves the RTP packets over the RTSP connection
// Example for TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1
video_data_channel = 0; // Used in DataReceived event handler
video_rtcp_channel = 1; // Used in DataReceived event handler
transport = new RtspTransport()
{
LowerTransport = RtspTransport.LowerTransportType.TCP,
Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Channel 0 for video. Channel 1 for RTCP status reports
};
}
if (rtp_transport == RTP_TRANSPORT.UDP)
{
// Server sends the RTP packets to a Pair of UDP Ports (one for data, one for rtcp control messages)
// Example for UDP mode Transport: RTP/AVP;unicast;client_port=8000-8001
video_data_channel = udp_pair.data_port; // Used in DataReceived event handler
video_rtcp_channel = udp_pair.control_port; // Used in DataReceived event handler
transport = new RtspTransport()
{
LowerTransport = RtspTransport.LowerTransportType.UDP,
IsMulticast = false,
ClientPort = new PortCouple(video_data_channel, video_rtcp_channel), // a Channel for video. a Channel for RTCP status reports
};
}
if (rtp_transport == RTP_TRANSPORT.MULTICAST)
{
// Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages)
// using Multicast Address and Ports that are in the reply to the SETUP message
// Example for MULTICAST mode Transport: RTP/AVP;multicast
video_data_channel = 0; // we get this information in the SETUP message reply
video_rtcp_channel = 0; // we get this information in the SETUP message reply
transport = new RtspTransport()
{
LowerTransport = RtspTransport.LowerTransportType.UDP,
IsMulticast = true
};
}
// Send SETUP
Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup();
setup_message.RtspUri = new Uri(control);
setup_message.AddTransport(transport);
if (authentication != AUTHENTICATION.NONE)
{
String authorization_string = GenerateAuthorization(username, password, authentication,
realm, nonce, url, "SETUP");
if (authorization_string != null)
{
setup_message.Headers.Add("Authorization", authorization_string);
}
}
rtsp_client.SendMessage(setup_message);
}
}
}
}
// If we get a reply to SETUP (which was our third command), then process and then send PLAY
if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup)
{
// Got Reply to SETUP
if (message.IsOk == false)
{
Console.WriteLine("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage);
return;
}
Console.WriteLine("Got reply from Setup. Session is " + message.Session);
session = message.Session; // Session value used with Play, Pause, Teardown
// Check the Transport header
if (message.Headers.ContainsKey(RtspHeaderNames.Transport))
{
RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]);
// Check if Transport header includes Multicast
if (transport.IsMulticast)
{
String multicast_address = transport.Destination;
video_data_channel = transport.Port.First;
video_rtcp_channel = transport.Port.Second;
// Create the Pair of UDP Sockets in Multicast mode
udp_pair = new UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel);
udp_pair.DataReceived += Rtp_DataReceived;
udp_pair.Start();
}
}
// Send PLAY
Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay();
play_message.RtspUri = new Uri(url);
play_message.Session = session;
if (authentication != AUTHENTICATION.NONE)
{
String authorization_string = GenerateAuthorization(username, password, authentication,
realm, nonce, url, "PLAY");
if (authorization_string != null)
{
play_message.Headers.Add("Authorization", authorization_string);
}
}
rtsp_client.SendMessage(play_message);
}
// If we get a reply to PLAY (which was our fourth command), then we should have video being received
if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay)
{
// Got Reply to PLAY
if (message.IsOk == false)
{
Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage);
return;
}
Console.WriteLine("Got reply from Play " + message.Command);
}
}