internal RtspResponse HandlePullSetup(RtspRequestSetup request) { var response = request.CreateResponse(); if (string.IsNullOrEmpty(response.Session)) { // TODO Allocate a real session ID response.Session = sessionGenerator.Next().ToString(); } var pushUri = GetPushUri(request.RtspUri.AbsolutePath); RtspPushDescription description; if (PushDescriptions.TryGetValue(pushUri, out description)) { //TODO get port and multicast address from description. var forwarder = description.GetForwarderFor(pushUri); var transport = new RtspTransport(); RtspTransport newTransport = new RtspTransport() { IsMulticast = true, Destination = forwarder.ForwardHostVideo, Port = new PortCouple(forwarder.ForwardPortVideo, forwarder.ListenCommandPort) }; response.Headers[RtspHeaderNames.Transport] = newTransport.ToString(); } else { response.ReturnCode = 404; } return(response); }
/// <summary> /// Selects the transport based on the configuration of the system.. /// </summary> /// <param name="requestSetup">The request setup message.</param> /// <returns>The selected transport</returns> /// <remarks> /// The transport is selected by taking the first supported transport /// in order of appearence. /// </remarks> private static RtspTransport SelectTransport(RtspRequestSetup requestSetup) { RtspTransport selectedTransport = null; const bool acceptTCP = false; const bool acceptUDPUnicast = true; const bool acceptUDPMulticast = true; foreach (RtspTransport proposedTransport in requestSetup.GetTransports()) { if (acceptTCP && proposedTransport.LowerTransport == RtspTransport.LowerTransportType.TCP) { selectedTransport = proposedTransport; break; } if (acceptUDPMulticast && proposedTransport.LowerTransport == RtspTransport.LowerTransportType.UDP && proposedTransport.IsMulticast) { selectedTransport = proposedTransport; break; } if (acceptUDPUnicast && proposedTransport.LowerTransport == RtspTransport.LowerTransportType.UDP && !proposedTransport.IsMulticast) { selectedTransport = proposedTransport; break; } } return(selectedTransport); }
public void DefaultValue() { RtspTransport testValue = new RtspTransport(); Assert.IsTrue(testValue.IsMulticast); Assert.AreEqual(RtspTransport.LowerTransportType.UDP, testValue.LowerTransport); Assert.AreEqual("PLAY", testValue.Mode); }
private void ProcessSetupRequest(RtspResponse message) { // If we get a reply to SETUP (which was our third command), then process then send PLAY // Got Reply to SETUP Logger.Info("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 multicastAddress = transport.Destination; videoDataChannel = transport.Port.First; videoRTCPChannel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode udpPair = new UDPSocketPair(multicastAddress, videoDataChannel, multicastAddress, videoRTCPChannel); udpPair.DataReceived += RtpDataReceived; udpPair.Start(); } } RtspRequest play_message = new RtspRequestPlay { RtspUri = new Uri(url), Session = session }; rtspListener.SendMessage(play_message); }
public void Parse1() { RtspTransport testValue = RtspTransport.Parse("RTP/AVP/UDP;destination"); Assert.IsTrue(testValue.IsMulticast); Assert.AreEqual(RtspTransport.LowerTransportType.UDP, testValue.LowerTransport); Assert.AreEqual("PLAY", testValue.Mode); }
public void Parse3() { RtspTransport testValue = RtspTransport.Parse("RTP/AVP/TCP;interleaved=3-4"); Assert.IsFalse(testValue.IsMulticast); Assert.AreEqual(RtspTransport.LowerTransportType.TCP, testValue.LowerTransport); Assert.AreEqual(3, testValue.Interleaved.First); Assert.IsTrue(testValue.Interleaved.IsSecondPortPresent); Assert.AreEqual(4, testValue.Interleaved.Second); }
public void ToStringTCP() { RtspTransport transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(0, 1), }; Assert.AreEqual("RTP/AVP/TCP;interleaved=0-1", transport.ToString()); }
public void Parse2() { RtspTransport testValue = RtspTransport.Parse("RTP/AVP/TCP;multicast;destination=test.example.com;ttl=234;ssrc=cd3b20a5"); Assert.IsTrue(testValue.IsMulticast); Assert.AreEqual(RtspTransport.LowerTransportType.TCP, testValue.LowerTransport); Assert.AreEqual("test.example.com", testValue.Destination); Assert.AreEqual("cd3b20a5", testValue.SSrc); Assert.AreEqual("PLAY", testValue.Mode); }
/// <summary> /// Asynchronously plays a stream. /// </summary> /// <param name="url">The url of a stream to play.</param> /// <param name="connectionTimeout">The connection timeout.</param> /// <exception cref="StreamPlayerException">Failed to play the stream.</exception> /// <param name="transport">RTSP transport protocol.</param> /// <param name="flags">RTSP flags.</param> internal void StartPlay(String url, TimeSpan connectionTimeout, RtspTransport transport, RtspFlags flags) { if (_startPlayDelegate(url, Convert.ToInt32(connectionTimeout.TotalMilliseconds), Convert.ToInt32(transport), Convert.ToInt32(flags)) != 0) { throw new StreamPlayerException("Failed to play the stream."); } }
/// <summary> /// Asynchronously plays a stream. /// </summary> /// <param name="uri">The uri of a stream to play.</param> /// <param name="connectionTimeout"></param> /// <exception cref="Win32Exception">Failed to load the FFmpeg facade dll.</exception> /// <exception cref="StreamPlayerException">Failed to play the stream.</exception> /// <param name="transport">RTSP transport protocol.</param> /// <param name="flags">RTSP flags.</param> public void StartPlay(Uri uri, TimeSpan connectionTimeout, RtspTransport transport, RtspFlags flags, FrameDelay framedelay) { if (IsPlaying) { Stop(); } Player.StartPlay(uri.IsFile ? uri.LocalPath : uri.ToString(), connectionTimeout, transport, flags, framedelay); }
public void Parse4() { RtspTransport testValue = RtspTransport.Parse("RTP/AVP;unicast;destination=1.2.3.4;source=3.4.5.6;server_port=5000-5001;client_port=5003-5004"); Assert.IsFalse(testValue.IsMulticast); Assert.AreEqual(RtspTransport.LowerTransportType.UDP, testValue.LowerTransport); Assert.AreEqual("1.2.3.4", testValue.Destination); Assert.AreEqual("3.4.5.6", testValue.Source); Assert.AreEqual(5000, testValue.ServerPort.First); Assert.AreEqual(5001, testValue.ServerPort.Second); Assert.AreEqual(5003, testValue.ClientPort.First); Assert.AreEqual(5004, testValue.ClientPort.Second); }
public void ToStringUDPUnicast() { RtspTransport transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(5000, 5001), ServerPort = new PortCouple(5002, 5003), Destination = "1.2.3.4" }; Assert.AreEqual("RTP/AVP/UDP;unicast;destination=1.2.3.4;client_port=5000-5001;server_port=5002-5003", transport.ToString()); }
public void StartReadStream() { try { LoadIni(); Uri u = new Uri(m_strUri); TimeSpan ts = new TimeSpan(0, 0, 0, 0, m_nTimeOut); RtspTransport tsport = (RtspTransport)m_nTransPort; m_streamControl.StartPlay(u, ts, tsport, RtspFlags.None); m_camStatus = CamStatus.Connecting; } catch (Exception) { } }
public void StartReadStream(string uri, int timeout_ms, int transport = 1) //transport : 1-TCP, 2-UDP, 0-NONE { m_strUri = uri; m_nTimeOut = timeout_ms; m_nTransPort = transport; Uri u = new Uri(uri); TimeSpan ts = new TimeSpan(0, 0, 0, 0, timeout_ms); RtspTransport tsport = (RtspTransport)transport; IniFile ini = new IniFile(); ini.IniWriteValue(INISECT_RTSP, INIKEY_URI, uri, DefPath.VisionSetting); ini.IniWriteValue(INISECT_RTSP, INIKEY_TIMEOUT, timeout_ms.ToString(), DefPath.VisionSetting); ini.IniWriteValue(INISECT_RTSP, INIKEY_TRANSPORT, transport.ToString(), DefPath.VisionSetting); m_streamControl.StartPlay(u, ts, tsport, RtspFlags.None); m_camStatus = CamStatus.Connecting; }
private void ProcessSetupResponse(RtspResponse message) { rtspSession = message.Session; // Session value used with Play, Pause, Teardown Logger.Info($"RTSP session: {rtspSession}"); if (message.Headers.ContainsKey(RtspHeaderNames.Transport)) { RtspTransport transport = RtspTransport.Parse(message.Headers[RtspHeaderNames.Transport]); if (transport.IsMulticast) { string multicastAddress = transport.Destination; videoDataChannel = transport.Port.First; videoRTCPChannel = transport.Port.Second; // Create the Pair of UDP Sockets in Multicast mode udpSocketPair = new UDPSocketPair(multicastAddress, videoDataChannel, multicastAddress, videoRTCPChannel); udpSocketPair.DataReceived += RtpDataReceived; udpSocketPair.Start(); } } if (_suspendTransfer) { // Transfer suspended. Set state to playing but do not issue play request. // Play request will be sent upon resume. _currentState = State.Playing; return; } _currentState = State.Paused; PostRequest(new RtspRequestPlay { RtspUri = new Uri(rtspUrl), Session = rtspSession }); }
// 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. If so we update the 'realm' and 'nonce' if (message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; // Parse www_authenticate // EG: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE string[] items = www_authenticate.Split(new char[] { ',', ' ' }); foreach (string item in items) { // Split on the = symbol and load in string[] parts = item.Split(new char[] { '=' }); 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 } } Console.WriteLine("WWW Authorize parsed for " + realm + " " + nonce); } // 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 will not have the authorization Nonce so we // 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 request did not use Authorizarion. if (username == null || password == null) { // we do nothave a username or password. Abort return; } // Send a new DESCRIBE with authorization String digest_authorization = GenerateDigestAuthorization(username, password, realm, nonce, url, "DESCRIBE"); Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (digest_authorization != null) { describe_message.Headers.Add(RtspHeaderNames.Authorization, digest_authorization); } 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) for (int x = 0; x < sdp_data.Medias.Count; x++) { bool audio = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.audio); bool video = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (audio && audio_payload != -1) { continue; // have already matched an audio payload } if (audio || video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264 video) 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")) { Rtsp.Sdp.AttributRtpMap rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; // Check if the Codec Used (EncodingName) is one we support String[] valid_video_codecs = { "H264" }; String[] valid_audio_codecs = { "PCMA", "PCMU" }; if (video && Array.IndexOf(valid_video_codecs, rtpmap.EncodingName) >= 0) { // found a valid codec video_codec = rtpmap.EncodingName; video_payload = sdp_data.Medias[x].PayloadType; } if (audio && Array.IndexOf(valid_audio_codecs, rtpmap.EncodingName) >= 0) { audio_codec = rtpmap.EncodingName; audio_payload = sdp_data.Medias[x].PayloadType; } } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (video && video_codec.Contains("H264") && fmtp != null) { 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]; if (Received_SPS_PPS != null) { Received_SPS_PPS(sps, pps); } } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } if (audio && audio_payload == -1) { continue; } RtspTransport transport = null; int data_channel = 0; int rtcp_channel = 0; 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 if (video) { video_data_channel = 0; video_rtcp_channel = 1; data_channel = video_data_channel; rtcp_channel = video_rtcp_channel; } if (audio) { audio_data_channel = 2; audio_rtcp_channel = 3; data_channel = audio_data_channel; rtcp_channel = audio_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(data_channel, rtcp_channel), // Eg 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 if (video) { video_data_channel = video_udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = video_udp_pair.control_port; // Used in DataReceived event handler data_channel = video_data_channel; rtcp_channel = video_rtcp_channel; } if (audio) { audio_data_channel = audio_udp_pair.data_port; // Used in DataReceived event handler audio_rtcp_channel = audio_udp_pair.control_port; // Used in DataReceived event handler data_channel = audio_data_channel; rtcp_channel = audio_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(data_channel, rtcp_channel), // a Channel for data (video or audio). 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 if (video) { 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 } if (audio) { audio_data_channel = 0; // we get this information in the SETUP message reply audio_rtcp_channel = 0; // we get this information in the SETUP message reply } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Add authorization (if there is a username and password) String digest_authorization = GenerateDigestAuthorization(username, password, realm, nonce, url, "SETUP"); // Send SETUP Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (digest_authorization != null) { setup_message.Headers.Add("Authorization", digest_authorization); } 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 video_udp_pair = new Rtsp.UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); video_udp_pair.DataReceived += Rtp_DataReceived; video_udp_pair.Start(); // TODO - Need to set audio_udp_pair } } // Send PLAY 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 if (message.IsOk == false) { Console.WriteLine("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } Console.WriteLine("Got reply from Play " + message.Command); } }
private void MessageReceived(object sender, RtspChunkEventArgs e) { RtspResponse message = e.Message as RtspResponse; // If we get a reply to OPTIONS then start the Keepalive Timer and send DESCRIBE if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestOptions) { // Start a Timer to send an Keepalive RTSP command every 20 seconds _keepaliveTimer = new Timer(); _keepaliveTimer.Elapsed += SendKeepalive; _keepaliveTimer.Interval = 20 * 1000; _keepaliveTimer.Enabled = true; // Send DESCRIBE RtspRequest describe_message = new RtspRequestDescribe(); describe_message.RtspUri = new Uri(_url); _client.SendMessage(describe_message); } if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestDescribe) { Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } Uri video_uri = null; foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[0].Attributs) { if (attrib.Key.Equals("control")) { video_uri = new Uri(attrib.Value); } } RtspTransport transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(0, 1), }; // Generate SETUP messages RtspRequestSetup setup_message = new RtspRequestSetup(); setup_message.RtspUri = video_uri; setup_message.AddTransport(transport); _client.SendMessage(setup_message); } if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestSetup) { if (message.Timeout > 0 && message.Timeout > _keepaliveTimer.Interval / 1000) { _keepaliveTimer.Interval = message.Timeout * 1000 / 2; } // Send PLAY RtspRequest play_message = new RtspRequestPlay(); play_message.RtspUri = new Uri(_url); play_message.Session = message.Session; _client.SendMessage(play_message); } }
// RTSP Messages are OPTIONS, DESCRIBE, SETUP, PLAY etc void Rtsp_MessageReceived(object sender, RtspChunkEventArgs e) { var message = e.Message as RtspResponse; //logger.Debug ("Received " + message.OriginalRequest.Method); // If message has a 401 - Unauthorised Error, then we re-send the message with Authorization // using the most recently received 'realm' and 'nonce' if (!message.IsOk) { logger.Warn("Got Error in RTSP Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // the authorization failed. Stop(); return; } // Check if the Reply has an Authenticate header. if (message.ReturnCode == 401 && message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { // Process the WWW-Authenticate header // EG: Basic realm="AProxy" // EG: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE string www_authenticate = message.Headers [RtspHeaderNames.WWWAuthenticate]; string [] items = www_authenticate.Split(new char [] { ',', ' ' }); foreach (string item in items) { if (item.ToLower().Equals("basic")) { auth_type = "Basic"; } else if (item.ToLower().Equals("digest")) { auth_type = "Digest"; } else { // Split on the = symbol and update the realm and nonce string [] parts = item.Split(new char [] { '=' }, 2); // max 2 parts in the results array 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 } } } //logger.Debug ("WWW Authorize parsed for " + auth_type + " " + realm + " " + nonce); } var resend_message = message.OriginalRequest.Clone() as RtspMessage; if (auth_type != null) { AddAuthorization(resend_message, Username, Password, auth_type, realm, nonce, Url); } rtsp_client.SendMessage(resend_message); return; } // If we get a reply to OPTIONS then start the Keepalive Timer and send DESCRIBE if (message.OriginalRequest != null && message.OriginalRequest is RtspRequestOptions) { string public_methods = message.Headers [RtspHeaderNames.Public]; SupportedMethods.Clear(); foreach (string method in public_methods.Split(',')) { SupportedMethods.Add(method.Trim()); } if (keepalive_timer == null) { // 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 = 5 * 1000; keepalive_timer.Enabled = true; // Send DESCRIBE var describe_message = new RtspRequestDescribe(); describe_message.RtspUri = new Uri(Url); describe_message.AddAccept("application/sdp"); if (auth_type != null) { AddAuthorization(describe_message, Username, Password, auth_type, realm, nonce, Url); } rtsp_client.SendMessage(describe_message); } } // 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 Mictlanix.DotNet.Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE if (!message.IsOk) { logger.Warn("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } // Examine the SDP //logger.Debug (Encoding.UTF8.GetString (message.Data)); Sdp.SdpFile sdp_data; using (var sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP (each sub-stream) for (int x = 0; x < sdp_data.Medias.Count; x++) { bool video = (sdp_data.Medias [x].MediaType == Mictlanix.DotNet.Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) string control = ""; // the "track" or "stream id" Sdp.FmtpAttribute fmtp = null; // holds SPS and PPS in base64 (h264 video) foreach (var attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) { string sdp_control = attrib.Value; if (sdp_control.ToLower().StartsWith("rtsp://", StringComparison.Ordinal)) { control = sdp_control; //absolute path } else if (message.Headers.ContainsKey(RtspHeaderNames.ContentBase)) { control = message.Headers [RtspHeaderNames.ContentBase] + sdp_control; // relative path } else { control = Url + "/" + sdp_control; // relative path } } if (attrib.Key.Equals("fmtp")) { fmtp = attrib as Sdp.FmtpAttribute; } if (attrib.Key.Equals("rtpmap")) { var rtpmap = attrib as Sdp.RtpMapAttribute; // Check if the Codec Used (EncodingName) is one we support string [] valid_video_codecs = { "H264" }; if (video && Array.IndexOf(valid_video_codecs, rtpmap.EncodingName) >= 0) { // found a valid codec video_codec = rtpmap.EncodingName; video_payload = sdp_data.Medias [x].PayloadType; } } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (video && video_codec.Contains("H264") && fmtp != null) { var param = 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]; ParameterSetsReceived?.Invoke(sps, pps); } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } // Server interleaves the RTP packets over the RTSP connection // TCP mode (RTP over RTSP) Transport: RTP/AVP/TCP;interleaved=0-1 video_data_channel = 0; video_rtcp_channel = 1; var transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(video_data_channel, video_rtcp_channel), // Eg Channel 0 for video. Channel 1 for RTCP status reports }; // Send SETUP var setup_message = new RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (auth_type != null) { AddAuthorization(setup_message, Username, Password, auth_type, realm, nonce, Url); } 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 RtspRequestSetup) { // Got Reply to SETUP if (!message.IsOk) { logger.Warn("Got Error in SETUP Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } //logger.Debug ("Got reply from SETUP Session=" + message.Session); RtspSession = message.Session; // Session value used with Play, Pause, Teardown // Send PLAY RtspRequest play_message = new RtspRequestPlay(); play_message.RtspUri = new Uri(Url); play_message.Session = RtspSession; if (auth_type != null) { AddAuthorization(play_message, Username, Password, auth_type, realm, nonce, Url); } 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 RtspRequestPlay) { // Got Reply to PLAY if (!message.IsOk) { logger.Warn("Got Error in PLAY Reply " + message.ReturnCode + " " + message.ReturnMessage); return; } //logger.Debug ("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; } } }
// 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 RtspTransport RTSP_ConstructReplyTransport(RtspTransport transport, out UDPSocket udp_pair) { udp_pair = null; RtspTransport transport_reply = new 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) { 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 try { _logger.Trace($"{Id} Start creating UDPSocket"); udp_pair = new Rtsp.UDPSocket(_ipAddress, 50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use udp_pair.DataReceived += UdpPair_DataReceived; udp_pair.ControlReceived += UdpPair_ControlReceived; udp_pair.Start(); // start listening for data on the UDP ports _logger.Trace($"{Id} End creating UDPSocket"); } catch { if (udp_pair != null) { ReleaseUDPSocket(udp_pair); throw; } } // 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._dataPort, udp_pair._controlPort); } 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; } return(transport_reply); }
// 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 message has a 401 - Unauthorised Error, then we re-send the message with Authorization // using the most recently received 'realm' and 'nonce' if (message.IsOk == false) { Console.WriteLine("Got Error in RTSP Reply " + message.ReturnCode + " " + message.ReturnMessage); if (message.ReturnCode == 401 && (message.OriginalRequest.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)) { // the authorization failed. Stop(); return; } // Check if the Reply has an Authenticate header. if (message.ReturnCode == 401 && message.Headers.ContainsKey(RtspHeaderNames.WWWAuthenticate)) { // Process the WWW-Authenticate header // EG: Basic realm="AProxy" // EG: Digest realm="AXIS_WS_ACCC8E3A0A8F", nonce="000057c3Y810622bff50b36005eb5efeae118626a161bf", stale=FALSE String www_authenticate = message.Headers[RtspHeaderNames.WWWAuthenticate]; string[] items = www_authenticate.Split(new char[] { ',', ' ' }); foreach (string item in items) { if (item.ToLower().Equals("basic")) { auth_type = "Basic"; } else if (item.ToLower().Equals("digest")) { auth_type = "Digest"; } else { // Split on the = symbol and update the realm and nonce string[] parts = item.Split(new char[] { '=' }, 2); // max 2 parts in the results array 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 } } } Console.WriteLine("WWW Authorize parsed for " + auth_type + " " + realm + " " + nonce); } RtspMessage resend_message = message.OriginalRequest.Clone() as RtspMessage; if (auth_type != null) { AddAuthorization(resend_message, username, password, auth_type, realm, nonce, url); } rtsp_client.SendMessage(resend_message); return; } // If we get a reply to OPTIONS then start the Keepalive Timer and send DESCRIBE if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { if (keepalive_timer == null) { // 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 DESCRIBE Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); if (auth_type != null) { AddAuthorization(describe_message, username, password, auth_type, realm, nonce, 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 if (message.IsOk == false) { Console.WriteLine("Got Error in DESCRIBE Reply " + message.ReturnCode + " " + message.ReturnMessage); 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); } // RTP and RTCP 'channels' are used in TCP Interleaved mode (RTP over RTSP) int next_free_rtp_channel = 0; int next_free_rtcp_channel = 1; // Process each 'Media' Attribute in the SDP (each sub-stream) for (int x = 0; x < sdp_data.Medias.Count; x++) { bool audio = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.audio); bool video = (sdp_data.Medias[x].MediaType == Rtsp.Sdp.Media.MediaTypes.video); if (video && video_payload != -1) { continue; // have already matched an video payload } if (audio && audio_payload != -1) { continue; // have already matched an audio payload } if (audio || video) { // search the attributes for control, rtpmap and fmtp // (fmtp only applies to video) String control = ""; // the "track" or "stream id" Rtsp.Sdp.AttributFmtp fmtp = null; // holds SPS and PPS in base64 (h264 video) 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")) { Rtsp.Sdp.AttributRtpMap rtpmap = attrib as Rtsp.Sdp.AttributRtpMap; // Check if the Codec Used (EncodingName) is one we support String[] valid_video_codecs = { "H264" }; String[] valid_audio_codecs = { "PCMA", "PCMU", "AMR" }; if (video && Array.IndexOf(valid_video_codecs, rtpmap.EncodingName) >= 0) { // found a valid codec video_codec = rtpmap.EncodingName; video_payload = sdp_data.Medias[x].PayloadType; } if (audio && Array.IndexOf(valid_audio_codecs, rtpmap.EncodingName) >= 0) { audio_codec = rtpmap.EncodingName; audio_payload = sdp_data.Medias[x].PayloadType; } } } // If the rtpmap contains H264 then split the fmtp to get the sprop-parameter-sets which hold the SPS and PPS in base64 if (video && video_codec.Contains("H264") && fmtp != null) { 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]; if (Received_SPS_PPS != null) { Received_SPS_PPS(sps, pps); } } } // Send the SETUP RTSP command if we have a matching Payload Decoder if (video && video_payload == -1) { continue; } if (audio && audio_payload == -1) { continue; } 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 if (video) { video_data_channel = next_free_rtp_channel; video_rtcp_channel = next_free_rtcp_channel; } if (audio) { audio_data_channel = next_free_rtp_channel; audio_rtcp_channel = next_free_rtcp_channel; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(next_free_rtp_channel, next_free_rtcp_channel), // Eg Channel 0 for RTP video data. Channel 1 for RTCP status reports }; next_free_rtp_channel += 2; next_free_rtcp_channel += 2; } if (rtp_transport == RTP_TRANSPORT.UDP) { int rtp_port = 0; int rtcp_port = 0; // 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 if (video) { video_data_channel = video_udp_pair.data_port; // Used in DataReceived event handler video_rtcp_channel = video_udp_pair.control_port; // Used in DataReceived event handler rtp_port = video_udp_pair.data_port; rtcp_port = video_udp_pair.control_port; } if (audio) { audio_data_channel = audio_udp_pair.data_port; // Used in DataReceived event handler audio_rtcp_channel = audio_udp_pair.control_port; // Used in DataReceived event handler rtp_port = audio_udp_pair.data_port; rtcp_port = audio_udp_pair.control_port; } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(rtp_port, rtcp_port), // a UDP Port for data (video or audio). a UDP Port 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 if (video) { 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 } if (audio) { audio_data_channel = 0; // we get this information in the SETUP message reply audio_rtcp_channel = 0; // we get this information in the SETUP message reply } transport = new RtspTransport() { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = true }; } // Generate SETUP messages Rtsp.Messages.RtspRequestSetup setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(control); setup_message.AddTransport(transport); if (auth_type != null) { AddAuthorization(setup_message, username, password, auth_type, realm, nonce, url); } // Add SETUP message to list of mesages to send setup_messages.Add(setup_message); } } // Send the FIRST SETUP message and remove it from the list of Setup Messages rtsp_client.SendMessage(setup_messages[0]); setup_messages.RemoveAt(0); } // If we get a reply to SETUP (which was our third command), then we // (i) check if we have any more SETUP commands to send out (eg if we are doing SETUP for Video and Audio) // (ii) send a PLAY command if all the SETUP command have been sent 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 and and additional Setups // 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 video_udp_pair = new Rtsp.UDPSocket(multicast_address, video_data_channel, multicast_address, video_rtcp_channel); video_udp_pair.DataReceived += Rtp_DataReceived; video_udp_pair.Start(); // TODO - Need to set audio_udp_pair } } // Check if we have another SETUP command to send, then remote it from the list if (setup_messages.Count > 0) { // send the next SETUP message, after adding in the 'session' Rtsp.Messages.RtspRequestSetup next_setup = setup_messages[0]; next_setup.Session = session; rtsp_client.SendMessage(next_setup); setup_messages.RemoveAt(0); } else { // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; if (auth_type != null) { AddAuthorization(play_message, username, password, auth_type, realm, nonce, url); } 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); } }
/// <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 var listener = sender as RtspListener; var message = e.Message as RtspMessage; Console.WriteLine("RTSP message received " + message); // Handle OPTIONS message if (message is RtspRequestOptions) { // Create the reponse to OPTIONS var options_response = (e.Message as RtspRequestOptions).CreateResponse(); listener.SendMessage(options_response); } // Handle DESCRIBE message if (message is RtspRequestDescribe) { var requested_url = (message as 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 var raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header var raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header var sps_str = Convert.ToBase64String(raw_sps); var pps_str = Convert.ToBase64String(raw_pps); var 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"); var sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) var describe_response = (e.Message as 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 RtspRequestSetup) { // var setupMessage = message as 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 var transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client var transport_reply = new RtspTransport(); if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode} transport_reply.LowerTransport = RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } if (transport.LowerTransport == 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 = RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = transport.ClientPort; // FIX // for now until implemented transport_reply = null; } if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP && transport.IsMulticast) { // 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 = RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { var 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++; } var setup_response = setupMessage.CreateResponse(); setup_response.Headers[RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = new_session.session_id; listener.SendMessage(setup_response); } else { var setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } // Handle PLAY message if (message is RtspRequestPlay) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (var 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 var play_response = (e.Message as RtspRequestPlay).CreateResponse(); listener.SendMessage(play_response); } // Handle PLAUSE message if (message is RtspRequestPause) { lock (rtp_list) { // Search for the Session in the Sessions List. Change the state of "PLAY" foreach (var 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 var pause_response = (e.Message as RtspRequestPause).CreateResponse(); listener.SendMessage(pause_response); } // Handle GET_PARAMETER message, often used as a Keep Alive if (message is RtspRequestGetParameter) { // Create the reponse to GET_PARAMETER var getparameter_response = (e.Message as RtspRequestGetParameter).CreateResponse(); listener.SendMessage(getparameter_response); } // Handle TEARDOWN if (message is RtspRequestTeardown) { lock (rtp_list) { // Search for the Session in the Sessions List. foreach (var 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(); requestSetup.Headers[RtspHeaderNames.Transport] = firstNewTransport.ToString();// +", " + secondTransport.ToString(); _setupForwarder.Add(setupKey, forwarder); return(requestSetup); }
/// <summary> /// Constructs a stream. /// </summary> /// <param name="uri">The uri of a stream to play.</param> /// <param name="connectionTimeout">The connection timeout.</param> /// <param name="streamTimeout">The stream timeout.</param> /// <param name="transport">RTSP transport protocol.</param> /// <param name="flags">RTSP flags.</param> public static Stream FromUri(Uri uri, TimeSpan connectionTimeout, TimeSpan streamTimeout, RtspTransport transport, RtspFlags flags) { var type = Load(); var proxy = type.GetMethod("FromUri").Invoke(null, new Object[] { uri, connectionTimeout, streamTimeout, transport, flags }); return(new Stream(proxy)); }
// 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); } }