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(); }
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; } }
/// <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); }
// 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 RtspListener listener = sender as RtspListener; Messages.RtspMessage message = e.Message as Messages.RtspMessage; //Debug.Log("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) { // The Header contained Authorization // Check the message has the correct Authorization // If it does not have the correct Authorization then close the RTSP connection authorized = auth.IsValid(message); if (authorized == false) { // Send a 401 Authentication Failed reply, then close the RTSP Socket Messages.RtspResponse authorization_response = (e.Message as 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; } } if ((message.Headers.ContainsKey("Authorization") == false)) { // Send a 401 Authentication Failed with extra info in WWW-Authenticate // to tell the Client if we are using Basic or Digest Authentication Messages.RtspResponse authorization_response = (e.Message as 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 Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Messages.RtspResponse options_response = (e.Message as Messages.RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is Messages.RtspRequestDescribe) { String requested_url = (message as Messages.RtspRequestDescribe).RtspUri.ToString(); //Debug.Log("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 String sps_str = Convert.ToBase64String(h264_encoder.GetRawSPS()); String pps_str = Convert.ToBase64String(h264_encoder.GetRawPPS()); //Debug.LogFormat("sps: {0}, pps: {1}", sps_str, pps_str); 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) Messages.RtspResponse describe_response = (e.Message as 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 Messages.RtspRequestSetup) { // var setupMessage = message as 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 Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Messages.RtspTransport transport_reply = new Messages.RtspTransport(); transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters if (transport.LowerTransport == Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } UDPSocket udp_pair = null; if (transport.LowerTransport == 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 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 = Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ServerPort = new Messages.PortCouple(udp_pair.data_port, udp_pair.control_port); } else { transport_reply = null; } } if (transport.LowerTransport == 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 = Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new 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; } } } Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = copy_of_session_id; listener.SendMessage(setup_response); } else { 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 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=" + ((Messages.RtspRequestPlay)message).RtspUri + ";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp; // Send the reply Messages.RtspResponse play_response = (e.Message as 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 Messages.RtspResponse play_failed_response = (e.Message as 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 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 Messages.RtspResponse pause_response = (e.Message as Messages.RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is Messages.RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER Messages.RtspResponse getparameter_response = (e.Message as Messages.RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN (sent with a Session ID) if (message is 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(); } } } } }