public void AddTransport(RtspTransport newTransport) { string actualTransport = string.Empty; if(Headers.ContainsKey(RtspHeaderNames.Transport)) actualTransport = Headers[RtspHeaderNames.Transport] + ","; Headers[RtspHeaderNames.Transport] = actualTransport + newTransport.ToString(); }
private static void ReadTransport(RtspTransport returnValue, string[] transportProtocolPart) { TransportType transport; if (!Enum.TryParse <TransportType>(transportProtocolPart[0], out transport)) { throw new ArgumentException("Transport type invalid", "aTransportString"); } returnValue.Transport = transport; }
private static void ReadProfile(RtspTransport returnValue, string[] transportProtocolPart) { ProfileType profile; if (transportProtocolPart.Length < 2 || !Enum.TryParse <ProfileType>(transportProtocolPart[1], out profile)) { throw new ArgumentException("Transport profile type invalid", "aTransportString"); } returnValue.Profile = profile; }
public void AddTransport(RtspTransport newTransport) { string actualTransport = string.Empty; if (Headers.ContainsKey(RtspHeaderNames.Transport)) { actualTransport = Headers[RtspHeaderNames.Transport] + ","; } Headers[RtspHeaderNames.Transport] = actualTransport + newTransport.ToString(); }
/// <summary> /// Gets the transports associate with the request. /// </summary> /// <value>The transport.</value> public RtspTransport[] GetTransports() { if (!Headers.ContainsKey(RtspHeaderNames.Transport)) { return new RtspTransport[] { new RtspTransport() } } ; string[] items = Headers[RtspHeaderNames.Transport].Split(','); return(items.Select(o => RtspTransport.Parse(o)).ToArray()); }
private static void ReadLowerTransport(RtspTransport returnValue, string[] transportProtocolPart) { if (transportProtocolPart.Length == 3) { LowerTransportType lowerTransport; if (!Enum.TryParse <LowerTransportType>(transportProtocolPart[2], out lowerTransport)) { throw new ArgumentException("Lower transport type invalid", "aTransportString"); } returnValue.LowerTransport = lowerTransport; } }
// Process each RTSP message that is received private async System.Threading.Tasks.Task RTSP_Message_ReceivedAsync(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); var deviceId = ""; var streamId = ""; var unixTimestamp = 0; var startTime = new DateTime(1970, 1, 1); if (message is RtspRequest) { var rtspParameters = HttpUtility.ParseQueryString(((RtspRequest)message).RtspUri.Query); deviceId = rtspParameters["deviceId"]; streamId = rtspParameters["streamId"]; int.TryParse(rtspParameters["unixTimestamp"], out unixTimestamp); startTime = startTime.AddSeconds(unixTimestamp); Console.WriteLine($"{rtspParameters["deviceId"]}, {rtspParameters["streamId"]},{rtspParameters["unixTimestamp"]}"); } if (String.IsNullOrEmpty(deviceId)) { _logger.Error("No deviceId"); return; } List <RTSPConnection> rtsp_list = new List <RTSPConnection>(); rtsp_list = _rtspList.GetOrAdd(deviceId, rtsp_list); // Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce) bool authorized = false; Authentication authInfo = null; if (message.Headers.ContainsKey("Authorization") == true) { // The Header contained Authorization // Check the message has the correct Authorization // If it does not have the correct Authorization then close the RTSP connection authInfo = Authentication.GetAuthenticationInfo(message); URLCommand nvrCmd = new URLCommand(_nvrIp, uint.Parse(_nvrPort), authInfo.Username, authInfo.Password); string loginResponse = null; authorized = nvrCmd.Login(ref loginResponse); if (authorized == false) { // Send a 401 Authentication Failed reply, then close the RTSP Socket Rtsp.Messages.RtspResponse authorization_response = (e.Message as Rtsp.Messages.RtspRequest).CreateResponse(); authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); authorization_response.ReturnCode = 401; listener.SendMessage(authorization_response); lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list.ToArray()) { if (connection.listener == listener) { rtsp_list.Remove(connection); } } } listener.Dispose(); return; } else { lock (rtsp_list) { if (!rtsp_list.Any(rtsp => rtsp.listener.RemoteAdress == listener.RemoteAdress)) { RTSPConnection new_connection = new RTSPConnection(); new_connection.listener = listener; new_connection.client_hostname = listener.RemoteAdress.Split(':')[0]; new_connection.ssrc = global_ssrc; new_connection.time_since_last_rtsp_keepalive = DateTime.UtcNow; new_connection.video_time_since_last_rtcp_keepalive = DateTime.UtcNow; rtsp_list.Add(new_connection); } } _logger.Info($"Login NVR success:{loginResponse}"); } } else { Rtsp.Messages.RtspResponse authorization_response = (e.Message as Rtsp.Messages.RtspRequest).CreateResponse(); authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); // 'Basic' or 'Digest' authorization_response.ReturnCode = 401; listener.SendMessage(authorization_response); return; } // Update the RTSP Keepalive Timeout // We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { // found the connection connection.time_since_last_rtsp_keepalive = DateTime.UtcNow; break; } } } // Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); // parse and get deviceId from url if (!_nvrPlayerList.ContainsKey(deviceId)) { URLCommand nvrCmd = new URLCommand(_nvrIp, uint.Parse(_nvrPort), authInfo.Username, authInfo.Password); string deviceConfigXml = null; //DeviceConfig deviceConfig = null; //nvrCmd.GetDeviceConfig(ref deviceConfigXml, deviceId); //XmlDocument xdoc = new XmlDocument(); try { //xdoc.LoadXml(deviceConfigXml); //XmlNodeReader reader = new XmlNodeReader(xdoc.DocumentElement); //XmlSerializer ser = new XmlSerializer(typeof(DeviceConfig)); //deviceConfig = (DeviceConfig)ser.Deserialize(reader); //var resolutionList = deviceConfig.Device.VideoQuality.Quality.Resolution1.Split('x'); //var widthStr = resolutionList[0].Replace("N", ""); //var heightStr = resolutionList[1]; //TODO get framerate instead of hardcode } catch (Exception ex) { _logger.Error(ex.ToString()); } //TODO open wmfplayer by session (device + stream or device + stream + IP&Port + playback time) WmfPlayer wmfPlayer = new WmfPlayer(new IntPtr(Int32.Parse(deviceId))); wmfPlayer.m_SelectID = deviceId; if (_nvrPlayerList.TryAdd(deviceId, wmfPlayer)) { wmfPlayer.ReceivedYUVFrame += video_source_ReceivedYUVFrame; await wmfPlayer.OpenVideoAsync(_nvrIp, uint.Parse(_nvrPort), authInfo.Username, authInfo.Password, startTime, 1, deviceId, int.Parse(streamId)); } } } else if (message is Rtsp.Messages.RtspRequestDescribe) // Handle DESCRIBE message { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL // Make the Base64 SPS and PPS raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header String sps_str = Convert.ToBase64String(raw_sps); String pps_str = Convert.ToBase64String(raw_pps); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append($"o={authInfo.Username} 0 0 IN IP4 0.0.0.0\n"); sdp.Append("s=ACTi NVR\n"); sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); sdp.Append("a=control:*\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=fmtp:96 packetization-mode=1;profile-level-id=4D6028; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } else if (message is Rtsp.Messages.RtspRequestSetup)// Handle SETUP message { // var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } Rtsp.UDPSocket udp_pair = null; if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { Boolean udp_supported = true; if (udp_supported) { // RTP over UDP mode // Create a pair of UDP sockets - One is for the Video, one is for the RTCP udp_pair = new Rtsp.UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => { // RTCP data received Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString()); }; udp_pair.Start(); // start listening for data on the UDP ports // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = new Rtsp.Messages.PortCouple(udp_pair.data_port, udp_pair.control_port); } else { transport_reply = null; } } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { // Update the session with transport information String copy_of_session_id = ""; lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { // ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream // or a SETUP for an Audio Stream. // In the SDP the H264 video track is TrackID 0 // found the connection // Add the transports to the connection connection.video_client_transport = transport; connection.video_transport_reply = transport_reply; // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname connection.video_udp_pair = udp_pair; connection.video_session_id = session_handle.ToString(); session_handle++; // Copy the Session ID copy_of_session_id = connection.video_session_id; break; } } } Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = copy_of_session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } else if (message is Rtsp.Messages.RtspRequestPlay)// Handle PLAY message (Sent with a Session ID) { lock (rtsp_list) { // Search for the Session in the Sessions List. Change the state to "PLAY" bool session_found = false; foreach (RTSPConnection connection in rtsp_list) { if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */ { // found the session session_found = true; connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO) string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end string rtp_info = "url=" + ((Rtsp.Messages.RtspRequestPlay)message).RtspUri + ";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp; // Send the reply Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_response.AddHeader("Range: " + range); play_response.AddHeader("RTP-Info: " + rtp_info); listener.SendMessage(play_response); break; } } if (session_found == false) { // Session ID was not found in the list of Sessions. Send a 454 error Rtsp.Messages.RtspResponse play_failed_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_failed_response.ReturnCode = 454; // Session Not Found listener.SendMessage(play_failed_response); } } } else if (message is Rtsp.Messages.RtspRequestPause) // Handle PAUSE message (Sent with a Session ID) { lock (rtsp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTSPConnection connection in rtsp_list) { if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */) { // found the session connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse pause_response = (e.Message as Rtsp.Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Rtsp.Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Rtsp.Messages.RtspResponse getparameter_response = (e.Message as Rtsp.Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN (sent with a Session ID) if (message is Rtsp.Messages.RtspRequestTeardown) { lock (rtsp_list) { // Search for the Session in the Sessions List. foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list { if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL { // If this is UDP, close the transport // For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection) if (connection.video_udp_pair != null) { connection.video_udp_pair.Stop(); connection.video_udp_pair = null; } rtsp_list.Remove(connection); // Close the RTSP socket listener.Dispose(); } } } } }
/// <summary> /// Parses the specified transport string. /// </summary> /// <param name="aTransportString">A transport string.</param> /// <returns>The transport class.</returns> /// <exception cref="ArgumentNullException"><paramref name="aTransportString"/> is null.</exception> public static RtspTransport Parse(string aTransportString) { if (aTransportString == null) { throw new ArgumentNullException("aTransportString"); } Contract.EndContractBlock(); RtspTransport returnValue = new RtspTransport(); string[] transportPart = aTransportString.Split(';'); string[] transportProtocolPart = transportPart[0].Split('/'); ReadTransport(returnValue, transportProtocolPart); ReadProfile(returnValue, transportProtocolPart); ReadLowerTransport(returnValue, transportProtocolPart); foreach (string part in transportPart) { string[] subPart = part.Split('='); switch (subPart[0].ToUpperInvariant()) { case "UNICAST": returnValue.IsMulticast = false; break; case "MULTICAST": returnValue.IsMulticast = true; break; case "DESTINATION": if (subPart.Length == 2) { returnValue.Destination = subPart[1]; } break; case "SOURCE": if (subPart.Length == 2) { returnValue.Source = subPart[1]; } break; case "INTERLEAVED": returnValue.IsMulticast = false; if (subPart.Length < 2) { throw new ArgumentException("interleaved value invalid", "aTransportString"); } returnValue.Interleaved = PortCouple.Parse(subPart[1]); break; case "APPEND": returnValue.IsAppend = true; break; case "TTL": int ttl = 0; if (subPart.Length < 2 || !int.TryParse(subPart[1], out ttl)) { throw new ArgumentException("TTL value invalid", "aTransportString"); } returnValue.TTL = ttl; break; case "LAYERS": int layers = 0; if (subPart.Length < 2 || !int.TryParse(subPart[1], out layers)) { throw new ArgumentException("Layers value invalid", "aTransportString"); } returnValue.TTL = layers; break; case "PORT": if (subPart.Length < 2) { throw new ArgumentException("Port value invalid", "aTransportString"); } returnValue.Port = PortCouple.Parse(subPart[1]); break; case "CLIENT_PORT": if (subPart.Length < 2) { throw new ArgumentException("client_port value invalid", "aTransportString"); } returnValue.ClientPort = PortCouple.Parse(subPart[1]); break; case "SERVER_PORT": if (subPart.Length < 2) { throw new ArgumentException("server_port value invalid", "aTransportString"); } returnValue.ServerPort = PortCouple.Parse(subPart[1]); break; case "SSRC": if (subPart.Length < 2) { throw new ArgumentException("ssrc value invalid", "aTransportString"); } returnValue.SSrc = subPart[1]; break; case "MODE": if (subPart.Length < 2) { throw new ArgumentException("mode value invalid", "aTransportString"); } returnValue.Mode = subPart[1]; break; default: // TODO log invalid part break; } } return(returnValue); }
/// <summary> /// Parses the specified transport string. /// </summary> /// <param name="aTransportString">A transport string.</param> /// <returns>The transport class.</returns> /// <exception cref="ArgumentNullException"><paramref name="aTransportString"/> is null.</exception> public static RtspTransport Parse(string aTransportString) { if (aTransportString == null) throw new ArgumentNullException("aTransportString"); Contract.EndContractBlock(); RtspTransport returnValue = new RtspTransport(); string[] transportPart = aTransportString.Split(';'); string[] transportProtocolPart = transportPart[0].Split('/'); ReadTransport(returnValue, transportProtocolPart); ReadProfile(returnValue, transportProtocolPart); ReadLowerTransport(returnValue, transportProtocolPart); foreach (string part in transportPart) { string[] subPart = part.Split('='); switch (subPart[0].ToUpperInvariant()) { case "UNICAST": returnValue.IsMulticast = false; break; case "MULTICAST": returnValue.IsMulticast = true; break; case "DESTINATION": if (subPart.Length == 2) returnValue.Destination = subPart[1]; break; case "SOURCE": if (subPart.Length == 2) returnValue.Source = subPart[1]; break; case "INTERLEAVED": if (subPart.Length < 2) throw new ArgumentException("interleaved value invalid", "aTransportString"); returnValue.Interleaved = PortCouple.Parse(subPart[1]); break; case "APPEND": returnValue.IsAppend = true; break; case "TTL": int ttl = 0; if (subPart.Length < 2 || !int.TryParse(subPart[1], out ttl)) throw new ArgumentException("TTL value invalid", "aTransportString"); returnValue.TTL = ttl; break; case "LAYERS": int layers = 0; if (subPart.Length < 2 || !int.TryParse(subPart[1], out layers)) throw new ArgumentException("Layers value invalid", "aTransportString"); returnValue.TTL = layers; break; case "PORT": if (subPart.Length < 2) throw new ArgumentException("Port value invalid", "aTransportString"); returnValue.Port = PortCouple.Parse(subPart[1]); break; case "CLIENT_PORT": if (subPart.Length < 2) throw new ArgumentException("client_port value invalid", "aTransportString"); returnValue.ClientPort = PortCouple.Parse(subPart[1]); break; case "SERVER_PORT": if (subPart.Length < 2) throw new ArgumentException("server_port value invalid", "aTransportString"); returnValue.ServerPort = PortCouple.Parse(subPart[1]); break; case "SSRC": if (subPart.Length < 2) throw new ArgumentException("ssrc value invalid", "aTransportString"); returnValue.SSrc = subPart[1]; break; case "MODE": if (subPart.Length < 2) throw new ArgumentException("mode value invalid", "aTransportString"); returnValue.SSrc = subPart[1]; break; default: // TODO log invalid part break; } } return returnValue; }
/// <summary> /// Configures the transport and forwarder. /// </summary> /// <param name="aMessage">A message.</param> /// <param name="forwarder">The preset forwarder.</param> /// <returns>The configured forwarder.</returns> private static Forwarder ConfigureTransportAndForwarder(RtspMessage aMessage, UDPForwarder forwarder) { RtspTransport transport = RtspTransport.Parse(aMessage.Headers[RtspHeaderNames.Transport]); Forwarder resultForwarder; if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP) { if (transport.ServerPort != null) { forwarder.SourcePortVideo = transport.ServerPort.First; forwarder.ForwardPortCommand = transport.ServerPort.Second; } resultForwarder = forwarder; } else { TCPtoUDPForwader TCPForwarder = new TCPtoUDPForwader(); TCPForwarder.ForwardCommand = aMessage.SourcePort; TCPForwarder.SourceInterleavedVideo = transport.Interleaved.First; TCPForwarder.ForwardInterleavedCommand = transport.Interleaved.Second; // we need to transfer already getted values TCPForwarder.ForwardHostVideo = forwarder.ForwardHostVideo; TCPForwarder.ForwardPortVideo = forwarder.ForwardPortVideo; TCPForwarder.SourcePortCommand = forwarder.SourcePortCommand; TCPForwarder.ToMulticast = forwarder.ToMulticast; resultForwarder = TCPForwarder; } if (resultForwarder.ToMulticast) { // Setup port and destination multicast. resultForwarder.ForwardHostVideo = CreateNextMulticastAddress(); resultForwarder.ForwardPortVideo = forwarder.FromForwardVideoPort; RtspTransport newTransport = new RtspTransport() { IsMulticast = true, Destination = resultForwarder.ForwardHostVideo, Port = new PortCouple(resultForwarder.ForwardPortVideo, resultForwarder.ListenCommandPort) }; if ((resultForwarder is UDPForwarder && forwarder.ForwardPortCommand == 0) || (resultForwarder is TCPtoUDPForwader && (resultForwarder as TCPtoUDPForwader).ForwardInterleavedCommand == 0)) { newTransport.Port = null; } aMessage.Headers[RtspHeaderNames.Transport] = newTransport.ToString(); } else { RtspTransport newTransport = new RtspTransport() { IsMulticast = false, Destination = forwarder.ForwardHostVideo, ClientPort = new PortCouple(resultForwarder.ForwardPortVideo, resultForwarder.SourcePortCommand), ServerPort = new PortCouple(resultForwarder.FromForwardVideoPort, resultForwarder.ListenCommandPort) }; if ((resultForwarder is UDPForwarder && forwarder.ForwardPortCommand == 0) || (resultForwarder is TCPtoUDPForwader && (resultForwarder as TCPtoUDPForwader).ForwardInterleavedCommand == 0)) { newTransport.ServerPort = null; } aMessage.Headers[RtspHeaderNames.Transport] = newTransport.ToString(); } return resultForwarder; }
// Process each RTSP message that is received private void RTSP_Message_Received(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); // Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is Rtsp.Messages.RtspRequestDescribe) { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL // Make the Base64 SPS and PPS byte[] raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header byte[] raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header String sps_str = Convert.ToBase64String(raw_sps); String pps_str = Convert.ToBase64String(raw_pps); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n"); sdp.Append("s=SharpRTSP Test Camera\n"); sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); sdp.Append("a=control:trackID=0\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets="+sps_str+","+pps_str+";\n"); byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } // Handle SETUP message if (message is Rtsp.Messages.RtspRequestSetup) { // var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Create a 'Session' and add it to the Session List // ToDo - Check the Track ID. In the SDP the H264 video track is TrackID 0 RTPSession new_session = new RTPSession(); new_session.session_id = session_count.ToString(); new_session.listener = listener; new_session.sequence_number = (UInt16)rnd.Next(65535); // start with a random 16 bit sequence number new_session.ssrc = 1; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First,transport.Interleaved.Second); } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { // RTP over UDP mode} // Create a pair of UDP sockets // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = transport.ClientPort; // FIX // for now until implemented transport_reply = null; } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000,7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { // Add the transports to the Session new_session.client_transport = transport; new_session.transport_reply = transport_reply; // Add the new session to the Sessions List rtp_list.Add(new_session); session_count++; Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = new_session.session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode =461; listener.SendMessage(setup_response); } } // Handle PLAY message if (message is Rtsp.Messages.RtspRequestPlay) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTPSession session in rtp_list) { if (session.session_id.Equals(message.Session)) { // found the session session.play = true; break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); listener.SendMessage(play_response); } // Handle PLAUSE message if (message is Rtsp.Messages.RtspRequestPause) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTPSession session in rtp_list) { if (session.session_id.Equals(message.Session)) { // found the session session.play = false; break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse pause_response = (e.Message as Rtsp.Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Rtsp.Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Rtsp.Messages.RtspResponse getparameter_response = (e.Message as Rtsp.Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } }
private static void ReadTransport(RtspTransport returnValue, string[] transportProtocolPart) { TransportType transport; if (!Enum.TryParse<TransportType>(transportProtocolPart[0], out transport)) throw new ArgumentException("Transport type invalid", "aTransportString"); returnValue.Transport = transport; }
private static void ReadProfile(RtspTransport returnValue, string[] transportProtocolPart) { ProfileType profile; if (transportProtocolPart.Length < 2 || !Enum.TryParse<ProfileType>(transportProtocolPart[1], out profile)) throw new ArgumentException("Transport profile type invalid", "aTransportString"); returnValue.Profile = profile; }
private static void ReadLowerTransport(RtspTransport returnValue, string[] transportProtocolPart) { if (transportProtocolPart.Length == 3) { LowerTransportType lowerTransport; if (!Enum.TryParse<LowerTransportType>(transportProtocolPart[2], out lowerTransport)) throw new ArgumentException("Lower transport type invalid", "aTransportString"); returnValue.LowerTransport = lowerTransport; } }
// Process each RTSP message that is received private void RTSP_Message_Received(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); // Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce) if (auth != null) { bool authorized = false; if (message.Headers.ContainsKey("Authorization") == true) { // Check the message has the correct Authorization authorized = auth.IsValid(message); } if ((message.Headers.ContainsKey("Authorization") == false) || authorized == false) { // Send a 401 Authentication Required reply Rtsp.Messages.RtspResponse authorization_response = (e.Message as Rtsp.Messages.RtspRequest).CreateResponse(); authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); authorization_response.ReturnCode = 401; listener.SendMessage(authorization_response); return; } } // Update the RTSP Keepalive Timeout // We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { // found the connection connection.time_since_last_rtsp_keepalive = DateTime.UtcNow; break; } } } // Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is Rtsp.Messages.RtspRequestDescribe) { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL // Make the Base64 SPS and PPS byte[] raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header byte[] raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header String sps_str = Convert.ToBase64String(raw_sps); String pps_str = Convert.ToBase64String(raw_pps); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n"); sdp.Append("s=SharpRTSP Test Camera\n"); sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); sdp.Append("a=control:trackID=0\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } // Handle SETUP message if (message is Rtsp.Messages.RtspRequestSetup) { // var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } Rtsp.UDPSocket udp_pair = null; if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { Boolean udp_supported = true; if (udp_supported) { // RTP over UDP mode // Create a pair of UDP sockets - One is for the Video, one is for the RTCP udp_pair = new Rtsp.UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => { // RTCP data received Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString()); }; udp_pair.Start(); // start listening for data on the UDP ports // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = new Rtsp.Messages.PortCouple(udp_pair.data_port, udp_pair.control_port); } else { transport_reply = null; } } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { // Update the session with transport information String copy_of_session_id = ""; lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { // ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream // or a SETUP for an Audio Stream. // In the SDP the H264 video track is TrackID 0 // found the connection // Add the transports to the connection connection.video_client_transport = transport; connection.video_transport_reply = transport_reply; // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname connection.video_udp_pair = udp_pair; connection.video_session_id = session_handle.ToString(); session_handle++; // Copy the Session ID copy_of_session_id = connection.video_session_id; break; } } } Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = copy_of_session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } // Handle PLAY message (Sent with a Session ID) if (message is Rtsp.Messages.RtspRequestPlay) { lock (rtsp_list) { // Search for the Session in the Sessions List. Change the state to "PLAY" bool session_found = false; foreach (RTSPConnection connection in rtsp_list) { if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */ { // found the session session_found = true; connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO) string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end string rtp_info = "url=" + ((Rtsp.Messages.RtspRequestPlay)message).RtspUri + ";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp; // Send the reply Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_response.AddHeader("Range: " + range); play_response.AddHeader("RTP-Info: " + rtp_info); listener.SendMessage(play_response); break; } } if (session_found == false) { // Session ID was not found in the list of Sessions. Send a 454 error Rtsp.Messages.RtspResponse play_failed_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_failed_response.ReturnCode = 454; // Session Not Found listener.SendMessage(play_failed_response); } } } // Handle PAUSE message (Sent with a Session ID) if (message is Rtsp.Messages.RtspRequestPause) { lock (rtsp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTSPConnection connection in rtsp_list) { if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */) { // found the session connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse pause_response = (e.Message as Rtsp.Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Rtsp.Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Rtsp.Messages.RtspResponse getparameter_response = (e.Message as Rtsp.Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN (sent with a Session ID) if (message is Rtsp.Messages.RtspRequestTeardown) { lock (rtsp_list) { // Search for the Session in the Sessions List. foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list { if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL { // If this is UDP, close the transport // For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection) if (connection.video_udp_pair != null) { connection.video_udp_pair.Stop(); connection.video_udp_pair = null; } rtsp_list.Remove(connection); // Close the RTSP socket listener.Dispose(); } } } } }
// 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()); // 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 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 // 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].GetMediaType() == Rtsp.Sdp.Media.MediaType.video) { // We only want the first video sub-stream if (video_payload == -1) { // seach the atributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 Rtsp.Sdp.AttributRtpMap rtpmap = null; // holds Payload format, eg 96 often used with H264 as first dynamic payload value foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) control = attrib.Value; if (attrib.Key.Equals("fmtp")) fmtp = attrib as Rtsp.Sdp.AttributFmtp; if (attrib.Key.Equals("rtpmap")) rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; } // Split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (fmtp != null) { var param = Rtsp.Sdp.H264Parameters.Parse(fmtp.FormatParameter); var sps_pps = param.SpropParameterSets; if (sps_pps.Count > 0) video_sps = sps_pps[0]; if (sps_pps.Count > 1) video_pps = sps_pps[1]; Output_NAL(sps_pps); // output SPS and PPS } // Split the rtpmap to get the Payload Type video_payload = 0; if (rtpmap != null) video_payload = rtpmap.PayloadNumber; Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(url + "/" + control); 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 }; } setup_message.AddTransport(transport); rtsp_client.SendMessage(setup_message); } } } } // If we get a reply to SETUP (which was our third command), then process then send PLAY if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP 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(); } } Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; 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 Console.WriteLine("Got reply from Play " + message.Command); } }
private void RTSP_ProcessSetupRequest(RtspRequestSetup message, RtspListener listener) { // var setupMessage = message; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.UDPSocket udp_pair; RtspTransport transport_reply = RTSP_ConstructReplyTransport(transport, out udp_pair); bool mediaTransportSet = false; if (transport_reply != null) { // Update the session with transport information String copy_of_session_id = ""; // ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream // or a SETUP for an Audio Stream. // In the SDP the H264 video track is TrackID 0 // Add the transports to the connection if (contentBase != null) { string controlTrack = setupMessage.RtspUri.AbsoluteUri.Replace(contentBase, string.Empty); var requestMedia = _sdpFile.Medias.FirstOrDefault(media => media.Attributs.FirstOrDefault(a => a.Key == "control" && (a.Value == controlTrack || "/" + a.Value == controlTrack)) != null); if (requestMedia != null) { if (requestMedia.MediaType == Media.MediaTypes.video) { _videoClientTransport = transport; _videoTransportReply = transport_reply; // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname if (_videoUdpPair != null) { ReleaseUDPSocket(_videoUdpPair); } _videoUdpPair = udp_pair; mediaTransportSet = true; if (setupMessage.Session == null) { _videoSessionId = _sessionHandle.ToString(); _sessionHandle++; } else { _videoSessionId = setupMessage.Session; } // Copy the Session ID copy_of_session_id = _videoSessionId; } if (requestMedia.MediaType == Media.MediaTypes.audio) { _audioClientTransport = transport; _audioTransportReply = transport_reply; // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname if (_audioUdpPair != null) { ReleaseUDPSocket(_audioUdpPair); } _audioUdpPair = udp_pair; mediaTransportSet = true; if (setupMessage.Session == null) { _audioSessionId = _sessionHandle.ToString(); _sessionHandle++; } else { _audioSessionId = setupMessage.Session; } // Copy the Session ID copy_of_session_id = _audioSessionId; } } } if (false == mediaTransportSet) { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(_logger); // unsuported mediatime setup_response.ReturnCode = 415; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(_logger); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = copy_of_session_id; setup_response.Timeout = timeout_in_seconds; listener.SendMessage(setup_response); } } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(_logger); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } if (false == mediaTransportSet) { if (udp_pair != null) { ReleaseUDPSocket(udp_pair); udp_pair = null; } } }
// Process each RTSP message that is received private void RTSP_Message_Received(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); // Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is Rtsp.Messages.RtspRequestDescribe) { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL // Make the Base64 SPS and PPS byte[] raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header byte[] raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header String sps_str = Convert.ToBase64String(raw_sps); String pps_str = Convert.ToBase64String(raw_pps); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n"); sdp.Append("s=SharpRTSP Test Camera\n"); sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); sdp.Append("a=control:trackID=0\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } // Handle SETUP message if (message is Rtsp.Messages.RtspRequestSetup) { // var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { // RTP over UDP mode} // Create a pair of UDP sockets // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = transport.ClientPort; // FIX // for now until implemented transport_reply = null; } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { RTPSession new_session = new RTPSession(); new_session.listener = listener; new_session.sequence_number = (UInt16)rnd.Next(65535); // start with a random 16 bit sequence number new_session.ssrc = 1; // Add the transports to the Session new_session.client_transport = transport; new_session.transport_reply = transport_reply; lock (rtp_list) { // Create a 'Session' and add it to the Session List // ToDo - Check the Track ID. In the SDP the H264 video track is TrackID 0 // Place Lock() here so the Session Count and the addition to the list is locked new_session.session_id = session_count.ToString(); // Add the new session to the Sessions List rtp_list.Add(new_session); session_count++; } Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = new_session.session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } // Handle PLAY message if (message is Rtsp.Messages.RtspRequestPlay) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTPSession session in rtp_list) { if (session.session_id.Equals(message.Session)) { // found the session session.play = true; break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); listener.SendMessage(play_response); } // Handle PLAUSE message if (message is Rtsp.Messages.RtspRequestPause) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTPSession session in rtp_list) { if (session.session_id.Equals(message.Session)) { // found the session session.play = false; break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse pause_response = (e.Message as Rtsp.Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Rtsp.Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Rtsp.Messages.RtspResponse getparameter_response = (e.Message as Rtsp.Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN if (message is Rtsp.Messages.RtspRequestTeardown) { lock (rtp_list) { // Search for the Session in the Sessions List. foreach (RTPSession session in rtp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list { if (session.session_id.Equals(message.Session)) { // TODO - Close UDP or Multicast transport // For TCP there is no transport to close rtp_list.Remove(session); // Close the RTSP socket listener.Dispose(); } } } } }
/// <summary> /// Handles a request setup. /// </summary> /// <param name="destination">The destination.</param> /// <param name="requestSetup">The request setup.</param> /// <returns>The rewritten message</returns> /// <remarks> /// The destination can be modified. /// </remarks> private RtspMessage HandleRequestSetup(ref RtspListener destination, RtspRequestSetup requestSetup) { Contract.Requires(requestSetup != null); Contract.Requires(destination != null); Contract.Ensures(Contract.Result<RtspMessage>() != null); Contract.Ensures(Contract.ValueAtReturn(out destination) != null); // look if we already have a multicast streaming playing for this URI. foreach (var session in _activesSession.Values) { if (session.State == RtspSession.SessionState.Playing && session.ListOfForwader.ContainsKey(requestSetup.RtspUri)) { Forwarder existingForwarder = session.ListOfForwader[requestSetup.RtspUri]; if (existingForwarder != null && existingForwarder.ToMulticast) { RtspResponse returnValue = requestSetup.CreateResponse(); returnValue.Headers[RtspHeaderNames.Transport] = new RtspTransport() { IsMulticast = true, Destination = existingForwarder.ForwardHostVideo, Port = new PortCouple(existingForwarder.ForwardPortVideo, existingForwarder.ListenCommandPort), }.ToString(); returnValue.Session = session.Name; destination = requestSetup.SourcePort; return returnValue; } } } string setupKey = requestSetup.SourcePort.RemoteAdress + "SEQ" + requestSetup.CSeq.ToString(CultureInfo.InvariantCulture); RtspTransport selectedTransport = SelectTransport(requestSetup); // We do not handle asked transport so return directly. if (selectedTransport == null) { _logger.Info("No transport asked are supported, sorry"); RtspResponse returnValue = requestSetup.CreateResponse(); // Unsupported transport; returnValue.ReturnCode = 461; destination = requestSetup.SourcePort; return returnValue; } UDPForwarder forwarder = new UDPForwarder(); forwarder.ToMulticast = selectedTransport.IsMulticast; // this part of config is only valid in unicast. if (!selectedTransport.IsMulticast) { forwarder.ForwardPortVideo = selectedTransport.ClientPort.First; forwarder.SourcePortCommand = selectedTransport.ClientPort.Second; // If the client did not set the destination.. get it from TCP source if (!string.IsNullOrEmpty(selectedTransport.Destination)) { forwarder.ForwardHostVideo = selectedTransport.Destination; } else { forwarder.ForwardHostVideo = requestSetup.SourcePort.RemoteAdress.Split(':')[0]; _logger.Debug("Destination get from TCP port {0}", forwarder.ForwardHostVideo); } } // Configured the transport asked. forwarder.ForwardHostCommand = destination.RemoteAdress.Split(':')[0]; RtspTransport firstNewTransport = new RtspTransport() { IsMulticast = false, ClientPort = new PortCouple(forwarder.ListenVideoPort, forwarder.FromForwardCommandPort), }; RtspTransport secondTransport = new RtspTransport() { IsMulticast = false, LowerTransport = RtspTransport.LowerTransportType.TCP, }; requestSetup.Headers[RtspHeaderNames.Transport] = firstNewTransport.ToString() + ", " + secondTransport.ToString(); _setupForwarder.Add(setupKey, forwarder); return requestSetup; }
// Process each RTSP message that is received private void RTSP_Message_Received(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); // Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce) if (auth != null) { bool authorized = false; if (message.Headers.ContainsKey("Authorization") == true) { // Check the message has the correct Authorization authorized = auth.IsValid(message); } if ((message.Headers.ContainsKey("Authorization") == false) || authorized == false) { // Send a 401 Authentication Required reply Rtsp.Messages.RtspResponse authorization_response = (e.Message as Rtsp.Messages.RtspRequest).CreateResponse(); authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); authorization_response.ReturnCode = 401; listener.SendMessage(authorization_response); return; } } // Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is Rtsp.Messages.RtspRequestDescribe) { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL //// Make the Base64 SPS and PPS //byte[] raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header //byte[] raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header //String sps_str = Convert.ToBase64String(raw_sps); //String pps_str = Convert.ToBase64String(raw_pps); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n"); sdp.Append("s=WPFRtspServer\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); if (mStreams != null) { foreach (var item in mStreams) { switch (item.Item1) { case StreamType.Video: { sdp.Append(string.Format("m=video 0 RTP/AVP {0}\n", (uint)item.Item1)); sdp.Append(string.Format("a=rtpmap:{0} {1}/90000\n", (uint)item.Item1, item.Item3)); sdp.Append(string.Format("a=fmtp:{0}\n", (uint)item.Item1)); sdp.Append(string.Format("a=control:trackID={0}\n", item.Item2)); //sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); } break; case StreamType.Audio: { sdp.Append(string.Format("m=audio 0 RTP/AVP {0}\n", (uint)item.Item1)); sdp.Append(string.Format("a=rtpmap:{0} mpeg4-generic/90000/2\n", (uint)item.Item1)); sdp.Append(string.Format("a=fmtp:{0}\n", (uint)item.Item1)); sdp.Append(string.Format("a=control:trackID={0}\n", item.Item2)); //sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); } break; case StreamType.None: default: break; } } } byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } // Handle SETUP message if (message is Rtsp.Messages.RtspRequestSetup) { // var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { // RTP over UDP mode} // Create a pair of UDP sockets // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = transport.ClientPort; // FIX // for now until implemented transport_reply = null; } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { RTPSession new_session = new RTPSession(); new_session.listener = listener; new_session.sequence_number = (UInt16)rnd.Next(65535); // start with a random 16 bit sequence number new_session.ssrc = global_ssrc; // Add the transports to the Session new_session.client_transport = transport; new_session.transport_reply = transport_reply; lock (rtp_list) { if (setupMessage.RtspUri.Segments != null && setupMessage.RtspUri.Segments.Length == 2) { var split = setupMessage.RtspUri.Segments[1].Split(new char[] { '=' }); if (split != null && split.Length == 2) { int.TryParse(split[1], out new_session.trackID); } } //setupMessage.SourcePort.RemoteAdress // Create a 'Session' and add it to the Session List // ToDo - Check the Track ID. In the SDP the H264 video track is TrackID 0 // Place Lock() here so the Session Count and the addition to the list is locked new_session.session_id = session_count.ToString(); // Add the new session to the Sessions List rtp_list.Add(new_session); session_count++; } Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = new_session.session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } // Handle PLAY message if (message is Rtsp.Messages.RtspRequestPlay) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" bool session_found = false; foreach (RTPSession session in rtp_list) { //if (session.session_id.Equals(message.Session)) { // found the session session_found = true; session.play = true; string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end string rtp_info = "url=" + ((Rtsp.Messages.RtspRequestPlay)message).RtspUri + ";seq=" + session.sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp; // Send the reply Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_response.AddHeader("Range: " + range); play_response.AddHeader("RTP-Info: " + rtp_info); listener.SendMessage(play_response); } } if (session_found == false) { // Session ID was not found in the list of Sessions. Send a 454 error Rtsp.Messages.RtspResponse play_failed_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_failed_response.ReturnCode = 454; // Session Not Found listener.SendMessage(play_failed_response); } } } // Handle PLAUSE message if (message is Rtsp.Messages.RtspRequestPause) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (RTPSession session in rtp_list) { if (session.session_id.Equals(message.Session)) { // found the session session.play = false; break; } } } // ToDo - only send back the OK response if the Session in the RTSP message was found Rtsp.Messages.RtspResponse pause_response = (e.Message as Rtsp.Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Rtsp.Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Rtsp.Messages.RtspResponse getparameter_response = (e.Message as Rtsp.Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN if (message is Rtsp.Messages.RtspRequestTeardown) { lock (rtp_list) { // Search for the Session in the Sessions List. foreach (RTPSession session in rtp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list { if (session.session_id.Equals(message.Session)) { // TODO - Close UDP or Multicast transport // For TCP there is no transport to close rtp_list.Remove(session); // Close the RTSP socket listener.Dispose(); } } } } }