/// <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); }
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); }
private void ProcessDescribeResponse(RtspResponse message) { // Got a reply for DESCRIBE // Examine the SDP Logger.Info(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdpData; using (StreamReader sdpStream = new StreamReader(new MemoryStream(message.Data))) { sdpData = Rtsp.Sdp.SdpFile.Read(sdpStream); } // 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 < sdpData.Medias.Count; x++) { if (sdpData.Medias[x].MediaType == Media.MediaTypes.video) { // We only want the first video sub-stream if (videoPayloadType != -1) { return; } // search the attributes for control, fmtp and rtpmap ParseAttributes(sdpData, x, out string control, out Rtsp.Sdp.AttributFmtp fmtp, out Rtsp.Sdp.AttributRtpMap rtpmap); // 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); OutputNal(param.SpropParameterSets); // output SPS and PPS } // Split the rtpmap to get the Payload Type videoPayloadType = 0; if (rtpmap != null) { videoPayloadType = rtpmap.PayloadNumber; } RtspRequestSetup setupMessage = new RtspRequestSetup(); setupMessage.RtspUri = new Uri(rtspUrl + "/" + control); var transport = GetRTSPTransport(); setupMessage.AddTransport(transport); PostRequest(setupMessage); } } }
internal RtspResponse HandleSetup(RtspRequestSetup request) { Contract.Requires(request != null); Contract.Ensures(Contract.Result <RtspResponse>() != null); var response = request.CreateResponse(); if (string.IsNullOrEmpty(response.Session)) { // TODO Allocate a real session ID response.Session = sessionGenerator.Next().ToString(); } RtspPushDescription description; if (!PushDescriptions.TryGetValue(request.RtspUri.AbsolutePath, out description)) { response.ReturnCode = 404; return(response); } bool configok = false; foreach (var transport in request.GetTransports()) { if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP && !transport.IsMulticast ) { var forwarder = new UDPForwarder(); description.AddForwarders(response.Session, request.RtspUri.AbsolutePath, forwarder); transport.ServerPort = new PortCouple(forwarder.ListenVideoPort); response.Headers[RtspHeaderNames.Transport] = transport.ToString(); configok = true; } } if (!configok) { response.ReturnCode = 461; } return(response); }
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; } } }
/// <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> /// Handles request message. /// </summary> /// <param name="message">A message, can be rewriten.</param> /// <returns>The destination</returns> private RtspListener HandleRequest(ref RtspMessage message) { Contract.Requires(message != null); Contract.Requires(message is RtspRequest); Contract.Ensures(Contract.Result <RtspListener>() != null); Contract.Ensures(Contract.ValueAtReturn(out message) != null); RtspRequest request = message as RtspRequest; RtspListener destination; // Do not forward, direct respond because we do not know where to send. if (request.RtspUri == null || request.RtspUri.AbsolutePath.Split(new char[] { '/' }, 3).Length < 3) { destination = HandleRequestWithoutUrl(ref message); } else { try { // get the real destination request.RtspUri = RewriteUri(request.RtspUri); destination = GetRtspListenerForDestination(request.RtspUri); // Handle setup RtspRequestSetup requestSetup = request as RtspRequestSetup; if (requestSetup != null) { message = HandleRequestSetup(ref destination, requestSetup); } //Handle Play Reques RtspRequestPlay requestPlay = request as RtspRequestPlay; if (requestPlay != null) { message = HandleRequestPlay(ref destination, requestPlay); } //Update session state and handle special message if (request.Session != null && request.RtspUri != null) { string sessionKey = RtspSession.GetSessionName(request.RtspUri, request.Session); if (_activesSession.ContainsKey(sessionKey)) { _activesSession[sessionKey].Handle(request); switch (request.RequestTyped) { // start here to start early //case RtspRequest.RequestType.PLAY: // _activesSession[sessionKey].Start(request.SourcePort.RemoteAdress); // break; case RtspRequest.RequestType.TEARDOWN: _activesSession[sessionKey].Stop(request.SourcePort.RemoteAdress); if (!_activesSession[sessionKey].IsNeeded) { _activesSession.Remove(sessionKey); } else { // system still need the server to send data do not send him the message. // reponds to client directly. destination = request.SourcePort; message = request.CreateResponse(); } break; } } else { _logger.Warn("Command {0} for session {1} which was not found", request.RequestTyped, sessionKey); } } } catch (Exception error) { _logger.Error("Error during handle of request", error); destination = request.SourcePort; RtspResponse theDirectResponse = request.CreateResponse(); theDirectResponse.ReturnCode = 500; message = theDirectResponse; } } return(destination); }
// 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 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); } }