internal RtspMessage ProcessPlay(RtspMessage playRequest, RtpSource source) { //Determine if the request can be processed. bool playAllowed = false; //Indicate why play would not be allowed. string information = string.Empty; //Check the there is an underlying transport which has not already been disposed. if (m_RtpClient == null || true == m_RtpClient.IsDisposed) { //Indicate the SETUP needs to occur again. information = "Session Transport closed. Perform SETUP."; //REDIRECT TO SETUP? } else playAllowed = true; //Get the contexts which are available if play is allowed. IEnumerable<RtpClient.TransportContext> sourceAvailable = Enumerable.Empty<RtpClient.TransportContext>(); //If there is a tranport which can communicate the media then determine if there is an applicable source. if (playAllowed) { //Query the source's transport for a context which has been attached to the session via SETUP. sourceAvailable = source.RtpClient.GetTransportContexts().Where(sc => GetSourceContext(sc.MediaDescription) != null); //If any context is available then this PLAY request can be honored. playAllowed = sourceAvailable.Any(); //Information information = "No Source Transport. Perform SETUP."; //REDIRECT TO SETUP? } //13.4.16 464 Data Transport Not Ready Yet //The data transmission channel to the media destination is not yet ready for carrying data. if (false == playAllowed) { return CreateRtspResponse(playRequest, RtspStatusCode.DataTransportNotReadyYet, information); } //bool allowIncomingRtp = false; //Attach the packet events if not already attached (E.g. paused) if (false == Playing.Contains(source.Id)) { //Attach events based on how the source will raise them. if (source.RtpClient.FrameChangedEventsEnabled) source.RtpClient.RtpFrameChanged += OnSourceFrameChanged; else source.RtpClient.RtpPacketReceieved += OnSourceRtpPacketRecieved; //Ensure playing Playing.Add(source.Id); } m_RtpClient.FrameChangedEventsEnabled = false; //m_RtpClient.IncomingPacketEventsEnabled = allowIncomingRtp; //Prepare a place to hold the response RtspMessage playResponse = CreateRtspResponse(playRequest); //Get the Range header string rangeHeader = playRequest[RtspHeaders.Range]; TimeSpan? startRange = null, endRange = null; /* A PLAY request without a Range header is legal. It starts playing a stream from the beginning unless the stream has been paused. If a stream has been paused via PAUSE, stream delivery resumes at the pause point. If a stream is playing, such a PLAY request causes no further action and can be used by the client to test server liveness. */ //Determine if the client wants to start playing from a specific point in time or until a specific point if (false == string.IsNullOrWhiteSpace(rangeHeader)) { //TODO //If the source does not support seeking then a 456 must be returned. //Will require a property or convention in the SessionDescripton e.g. a=broadcast. //return CreateRtspResponse(playRequest, RtspStatusCode.HeaderFieldNotValidForResource); string type; TimeSpan start, end; //If parsing of the range header was successful if (RtspHeaders.TryParseRange(rangeHeader, out type, out start, out end)) { //Determine the max start time TimeSpan max = sourceAvailable.Max(tc => tc.MediaEndTime); //Start playing from here startRange = start; //End playing after this time if given and not unspecified endRange = end; //http://stackoverflow.com/questions/4672359/why-does-timespan-fromsecondsdouble-round-to-milliseconds if(end != Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan && (end += Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan) > max) return CreateRtspResponse(playRequest, RtspStatusCode.InvalidRange, "Invalid End Range"); //If the given time to start at is > zero if (start > TimeSpan.Zero) { //If the maximum is not infinite and the start exceeds the max indicate this. if (max != Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan && start > max) return CreateRtspResponse(playRequest, RtspStatusCode.InvalidRange, "Invalid Start Range"); } //If the end time is infinite and the max is not infinite then the end is the max time. if (end == Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan && max != Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan) endRange = end = max; //If the start time is 0 and the end time is not infinite then start the start time to the uptime of the stream (how long it has been playing) if (start == TimeSpan.Zero && end != Media.Common.Extensions.TimeSpan.TimeSpanExtensions.InfiniteTimeSpan) startRange = start = source.RtpClient.Uptime; else startRange = null; } } //Todo Process Scale, Speed, Bandwidth, Blocksize //Set Seek-Style to indicate if Seeking is supported. //Prepare the RtpInfo header //Iterate the source's TransportContext's to Augment the RtpInfo header for the current request List<string> rtpInfos = new List<string>(); string lastSegment = playRequest.Location.Segments.Last(); Sdp.MediaType mediaType; //If the mediaType was specified if (Enum.TryParse(lastSegment, out mediaType)) { var sourceContext = sourceAvailable.FirstOrDefault(tc => tc.MediaDescription.MediaType == mediaType); //AggreateOperationNotAllowed? if (sourceContext == null) return CreateRtspResponse(playRequest, RtspStatusCode.BadRequest, "Source Not Setup"); var context = m_RtpClient.GetContextForMediaDescription(sourceContext.MediaDescription); //Create the RtpInfo header for this context. //There should be a better way to get the Uri for the stream //E.g. ServerLocation should be used. //UriEnecode? bool hasAnyState = sourceContext.RtpPacketsReceived > 0 || sourceContext.RtpPacketsSent > 0 && false == context.InDiscovery; rtpInfos.Add(RtspHeaders.RtpInfoHeader(new Uri("rtsp://" + ((IPEndPoint)(m_RtspSocket.LocalEndPoint)).Address + "/live/" + source.Id + '/' + context.MediaDescription.MediaType.ToString()), hasAnyState ? sourceContext.SequenceNumber : (int?)null, hasAnyState ? sourceContext.RtpTimestamp : (int?)null, hasAnyState ? (int?)null : context.SynchronizationSourceIdentifier)); //Identify now to emulate GStreamer :P m_RtpClient.SendSendersReport(context); //Done with context. context = null; } else { foreach (RtpClient.TransportContext sourceContext in sourceAvailable) { var context = m_RtpClient.GetContextForMediaDescription(sourceContext.MediaDescription); if (context == null) continue; //Create the RtpInfo header for this context. //UriEnecode? //There should be a better way to get the Uri for the stream //E.g. ServerLocation should be used. bool hasAnyState = sourceContext.RtpPacketsReceived > 0 || sourceContext.RtpPacketsSent > 0 && false == context.InDiscovery; rtpInfos.Add(RtspHeaders.RtpInfoHeader(new Uri("rtsp://" + ((IPEndPoint)(m_RtspSocket.LocalEndPoint)).Address + "/live/" + source.Id + '/' + context.MediaDescription.MediaType.ToString()), hasAnyState ? sourceContext.SequenceNumber : (int?)null, hasAnyState ? sourceContext.RtpTimestamp : (int?)null, hasAnyState ? (int?)null : context.SynchronizationSourceIdentifier)); //Done with context. context = null; } //Send all reports m_RtpClient.SendSendersReports(); } //Indicate the range of the play response. (`Range` will be 'now-' if no start or end was given) playResponse.SetHeader(RtspHeaders.Range, RtspHeaders.RangeHeader(startRange, endRange)); //Sent the rtpInfo playResponse.AppendOrSetHeader(RtspHeaders.RtpInfo, string.Join(", ", rtpInfos.ToArray())); //Todo //Set the MediaProperties header. //Ensure RtpClient is now connected connected so packets will begin to go out when enqued if (false == m_RtpClient.IsActive) { m_RtpClient.Activate(); //m_RtpClient.m_WorkerThread.Priority = ThreadPriority.Highest; } //Return the response return playResponse; }
internal RtspMessage ProcessPause(RtspMessage request, RtpSource source) { //If the source is attached if (Attached.ContainsValue(source)) { //Iterate the source transport contexts foreach (RtpClient.TransportContext sourceContext in source.RtpClient.GetTransportContexts()) { //Adding the id will stop the packets from being enqueued into the RtpClient PacketBuffer.Add((int)sourceContext.SynchronizationSourceIdentifier); } //Return the response return CreateRtspResponse(request); } //The source is not attached return CreateRtspResponse(request, RtspStatusCode.MethodNotValidInThisState); }
/* 10.7 TEARDOWN The TEARDOWN request stops the stream delivery for the given URI, freeing the resources associated with it. If the URI is the presentation URI for this presentation, any RTSP session identifier associated with the session is no longer valid. Unless all transport parameters are defined by the session description, a SETUP request has to be issued before the session can be played again. */ internal RtspMessage ProcessTeardown(RtspMessage request, RtpSource source) { //Determine if this is for only a single track or the entire shebang if (false == Attached.ContainsValue(source)) return CreateRtspResponse(request, RtspStatusCode.BadRequest); //Determine if we have the track string track = request.Location.Segments.Last().Replace("/", string.Empty); Sdp.MediaType mediaType; //For a single track if (Enum.TryParse <Sdp.MediaType>(track, true, out mediaType)) { //bool GetContextBySdpControlLine... out mediaDescription RtpClient.TransportContext sourceContext = source.RtpClient.GetTransportContexts().FirstOrDefault(sc => sc.MediaDescription.MediaType == mediaType); //Cannot teardown media because we can't find the track they are asking to tear down if (sourceContext == null) { return CreateRtspResponse(request, RtspStatusCode.NotFound); } else { RemoveMedia(sourceContext.MediaDescription); } } else //Tear down all streams { RemoveSource(source); } //Return the response return CreateRtspResponse(request); }
/// <summary> /// Removes all packets from the PacketBuffer related to the given source and enqueues them on the RtpClient of this ClientSession /// </summary> /// <param name="source">The RtpSource to check for packets in the PacketBuffer</param> internal void ProcessPacketBuffer(RtpSource source) { //Process packets from the PacketBuffer relevent to the Range Header IEnumerable<RtpPacket> packets; //Iterate all TransportContext's in the Source foreach (RtpClient.TransportContext sourceContext in source.RtpClient.GetTransportContexts()) { if (sourceContext == null) continue; //If the PacketBuffer has any packets related remove packets from the PacketBuffer if (PacketBuffer.Remove((int)sourceContext.SynchronizationSourceIdentifier, out packets)) { //Send them out m_RtpClient.m_OutgoingRtpPackets.AddRange(packets.SkipWhile(rtp => rtp.SequenceNumber < sourceContext.SequenceNumber)); } } }
/// <summary> /// /// </summary> /// <param name="request"></param> /// <param name="sourceContext"></param> /// <returns></returns> /// //TODO Should be SourceMedia and SourceContext. internal RtspMessage ProcessSetup(RtspMessage request, RtpSource sourceStream, RtpClient.TransportContext sourceContext) { //Assign the sessionId now if it has not been assigned before. if (SessionId == null) SessionId = m_Id.GetHashCode().ToString(); //We also have to send one back string returnTransportHeader = null; //Create a response RtspMessage response = CreateRtspResponse(request); Sdp.MediaDescription mediaDescription = sourceContext.MediaDescription; bool rtcpDisabled = sourceStream.m_DisableQOS; //Values in the header we need int clientRtpPort = -1, clientRtcpPort = -1, serverRtpPort = -1, serverRtcpPort = -1, localSsrc = 0, remoteSsrc = 0; //Cache this to prevent having to go to get it every time down the line IPAddress sourceIp = IPAddress.Any; string mode; bool unicast, multicast, interleaved, multiplexing; byte dataChannel = 0, controlChannel = 1; //Get the transport header string transportHeader = request[RtspHeaders.Transport]; //If that is not present we cannot determine what transport the client wants if (string.IsNullOrWhiteSpace(transportHeader) || false == (transportHeader.Contains("RTP")) || false == RtspHeaders.TryParseTransportHeader(transportHeader, out localSsrc, out sourceIp, out serverRtpPort, out serverRtcpPort, out clientRtpPort, out clientRtcpPort, out interleaved, out dataChannel, out controlChannel, out mode, out unicast, out multicast)) { return CreateRtspResponse(request, RtspStatusCode.BadRequest, "Invalid Transport Header"); } //RTCP-mux: when RTSP 2.0 is official... (Along with Server Sent Messages) //Check if the ssrc was 0 which indicates any id if (localSsrc == 0) localSsrc = RFC3550.Random32((int)sourceContext.MediaDescription.MediaType); //Could also randomize the setupContext sequenceNumber here. //We need to make an TransportContext in response to a setup RtpClient.TransportContext setupContext = null; //Check for already setup stream and determine if the stream needs to be setup again or just updated if (Attached.ContainsKey(sourceContext)) { //The contex may already existm should look first by ssrc. setupContext = m_RtpClient.GetContextForMediaDescription(sourceContext.MediaDescription); //If the context exists if (setupContext != null) { //Update the ssrc if it doesn't match. if (localSsrc != 0 && setupContext.SynchronizationSourceIdentifier != localSsrc) { setupContext.SynchronizationSourceIdentifier = localSsrc; if (remoteSsrc != 0 && setupContext.RemoteSynchronizationSourceIdentifier != remoteSsrc) setupContext.RemoteSynchronizationSourceIdentifier = remoteSsrc; } multicast = Media.Common.Extensions.IPAddress.IPAddressExtensions.IsMulticast(((IPEndPoint)setupContext.RemoteRtp).Address); interleaved = setupContext.RtpSocket.ProtocolType == ProtocolType.Tcp && SharesSocket; //Then indicate the information for that context in the return transport header. returnTransportHeader = RtspHeaders.TransportHeader(setupContext.MediaDescription.MediaProtocol, setupContext.SynchronizationSourceIdentifier, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, ((IPEndPoint)setupContext.RemoteRtp).Port, ((IPEndPoint)setupContext.RemoteRtcp).Port, ((IPEndPoint)setupContext.LocalRtp).Port, ((IPEndPoint)setupContext.LocalRtcp).Port, false == multicast, multicast, null, interleaved, setupContext.DataChannel, setupContext.ControlChannel); setupContext.LeaveOpen = interleaved; //Attach logger (have option?) m_RtpClient.Logger = m_Server.Logger; goto UpdateContext; } } //Should determine intervals here for Rtcp from SessionDescription //Should determine if aggregate operation is allowed //Maybe setting up both udp and tcp at the same time? clientRtpPort needs to be nullable. //Maybe better to just give tokens from the function .. //Without explicitly checking for !interleaved VLC will recieve what it thinks are RTSP responses unless RTSP Interleaved is Forced. //Was trying to Quicktime to pickup RTSP Interleaved by default on the first response but it doesn't seem that easy (quick time tries to switch but fails?) //If the source does not force TCP and interleaved was not given and this is a unicast or multicast connection if (false == interleaved && (unicast || multicast)) { //Check requested transport is allowed by server if (sourceStream.ForceTCP)//The client wanted Udp and Tcp was forced { //Return the result var result = CreateRtspResponse(request, RtspStatusCode.UnsupportedTransport); //Indicate interleaved is required. result.SetHeader(RtspHeaders.Transport, RtspHeaders.TransportHeader(RtpClient.RtpAvpProfileIdentifier + "/TCP", localSsrc, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, null, null, null, null, null, false, null, true, dataChannel, controlChannel)); return result; } //QuickTime debug if (clientRtpPort == 0) clientRtpPort = Media.Common.Extensions.Socket.SocketExtensions.FindOpenPort(ProtocolType.Udp, 30000, true); if (clientRtcpPort == 0) clientRtcpPort = clientRtpPort + 1; if (serverRtpPort == 0) serverRtpPort = Media.Common.Extensions.Socket.SocketExtensions.FindOpenPort(ProtocolType.Udp, 30000, true); if (serverRtcpPort == 0) serverRtcpPort = serverRtpPort + 1; //Ensure the ports are allowed to be used. if (m_Server.MaximumUdpPort.HasValue && (clientRtpPort > m_Server.MaximumUdpPort || clientRtcpPort > m_Server.MaximumUdpPort)) { //Handle port out of range return CreateRtspResponse(request, RtspStatusCode.BadRequest, "Requested Udp Ports were out of range. Maximum Port = " + m_Server.MaximumUdpPort); } //Create sockets to reserve the ports. var localAddress = ((IPEndPoint)m_RtspSocket.LocalEndPoint).Address; Socket tempRtp = Media.Common.Extensions.Socket.SocketExtensions.ReservePort(SocketType.Dgram, ProtocolType.Udp, localAddress, clientRtpPort); Socket tempRtcp = Media.Common.Extensions.Socket.SocketExtensions.ReservePort(SocketType.Dgram, ProtocolType.Udp, localAddress, clientRtcpPort); //Check if the client was already created. if (m_RtpClient == null || m_RtpClient.IsDisposed) { //Create a sender using a new segment on the existing buffer. m_RtpClient = new RtpClient(new Common.MemorySegment(m_Buffer)); //Dont handle frame changed events from the client m_RtpClient.FrameChangedEventsEnabled = false; //Dont handle packets from the client m_RtpClient.HandleIncomingRtpPackets = false; //Attach the Interleaved data event m_RtpClient.InterleavedData += m_RtpClient_InterleavedData; //Attach logger (have option?) m_RtpClient.Logger = m_Server.Logger; //Use default data and control channel (Should be option?) setupContext = new RtpClient.TransportContext(0, 1, localSsrc, mediaDescription, false == rtcpDisabled, remoteSsrc, 0); } else //The client was already created. { //Have to calculate next data and control channel RtpClient.TransportContext lastContext = m_RtpClient.GetTransportContexts().LastOrDefault(); if (lastContext != null) setupContext = new RtpClient.TransportContext((byte)(lastContext.DataChannel + 2), (byte)(lastContext.ControlChannel + 2), localSsrc, mediaDescription, false == rtcpDisabled, remoteSsrc, 0); else setupContext = new RtpClient.TransportContext(dataChannel, controlChannel, localSsrc, mediaDescription, false == rtcpDisabled, remoteSsrc, 0); } //Initialize the Udp sockets setupContext.Initialize(localAddress, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, serverRtpPort, serverRtcpPort, clientRtpPort, clientRtcpPort); //Ensure the receive buffer size is updated for that context. Media.Common.ISocketReferenceExtensions.SetReceiveBufferSize(((Media.Common.ISocketReference)setupContext), m_Buffer.Count); ////Check if the punch packets made it out. //if ((setupContext.IsRtpEnabled && ((IPEndPoint)setupContext.RemoteRtp).Port == 0) // || // (setupContext.IsRtcpEnabled && ((IPEndPoint)setupContext.RemoteRtcp).Port == 0)) //{ // //Response should be a 461 or we should indicate the remote party is not yet listening in the response // //Could also use StatusCode (100) with a reason phrase or header //} //Add the transportChannel m_RtpClient.TryAddContext(setupContext); //Create the returnTransportHeader (Should be setupContext.SynchronizationSourceIdentifier) returnTransportHeader = RtspHeaders.TransportHeader(RtpClient.RtpAvpProfileIdentifier, localSsrc, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, clientRtpPort, clientRtcpPort, serverRtpPort, serverRtcpPort, true, false, null, false, 0, 0); tempRtp.Dispose(); tempRtp = null; tempRtcp.Dispose(); tempRtcp = null; } //Check for 'interleaved' token or TCP being forced if (sourceStream.ForceTCP || interleaved) { //Check if the client was already created. if (m_RtpClient == null || m_RtpClient.IsDisposed) { //Create a sender using a new segment on the existing buffer. m_RtpClient = new RtpClient(new Common.MemorySegment(m_Buffer)); m_RtpClient.InterleavedData += m_RtpClient_InterleavedData; //Attach logger (have option?) m_RtpClient.Logger = m_Server.Logger; #region Unused [Helps with debugging] //m_RtpClient.RtcpPacketReceieved += m_RtpClient_RecievedRtcp; //m_RtpClient.RtpPacketReceieved += m_RtpClient_RecievedRtp; //m_RtpClient.RtcpPacketSent += m_RtpClient_SentRtcp; #endregion //Dont handle frame changed events from the client m_RtpClient.FrameChangedEventsEnabled = false; //Dont handle packets from the client m_RtpClient.HandleIncomingRtpPackets = false; //Create a new Interleave (don't use what was given as data or control channels) setupContext = new RtpClient.TransportContext((byte)(dataChannel = 0), (byte)(controlChannel = 1), localSsrc, mediaDescription, m_RtspSocket, false == rtcpDisabled, remoteSsrc, 0); //Add the transportChannel the client requested m_RtpClient.TryAddContext(setupContext); //Initialize the Interleaved Socket setupContext.Initialize(m_RtspSocket, m_RtspSocket); } else //The client was already created { //Have to calculate next data and control channel RtpClient.TransportContext lastContext = m_RtpClient.GetTransportContexts().LastOrDefault(); //Don't use what was given as data or control channels if (lastContext != null) setupContext = new RtpClient.TransportContext(dataChannel = (byte)(lastContext.DataChannel + 2), controlChannel = (byte)(lastContext.ControlChannel + 2), localSsrc, mediaDescription, false == rtcpDisabled, remoteSsrc, 0); else setupContext = new RtpClient.TransportContext(dataChannel, controlChannel, localSsrc, mediaDescription, false == rtcpDisabled, remoteSsrc, 0); //Add the transportChannel the client requested m_RtpClient.TryAddContext(setupContext); //Initialize the current TransportChannel with the interleaved Socket setupContext.Initialize(m_RtspSocket, m_RtspSocket); } //Create the returnTransportHeader //returnTransportHeader = RtspHeaders.TransportHeader(RtpClient.RtpAvpProfileIdentifier + "/TCP", setupContext.SynchronizationSourceIdentifier, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, LocalEndPoint.Port, LocalEndPoint.Port, ((IPEndPoint)RemoteEndPoint).Port, ((IPEndPoint)RemoteEndPoint).Port, true, false, null, true, dataChannel, controlChannel); //Leave the socket open when disposing the RtpClient setupContext.LeaveOpen = true; returnTransportHeader = RtspHeaders.TransportHeader(RtpClient.RtpAvpProfileIdentifier + "/TCP", localSsrc, ((IPEndPoint)m_RtspSocket.RemoteEndPoint).Address, null, null, null, null, null, false, null, true, dataChannel, controlChannel); } //Add the new source Attached.Add(sourceContext, sourceStream); UpdateContext: //Synchronize the context sequence numbers setupContext.SequenceNumber = sourceContext.SequenceNumber; //Start and end times are always equal. setupContext.MediaStartTime = sourceContext.MediaStartTime; setupContext.MediaEndTime = sourceContext.MediaEndTime; //Set the returnTransportHeader to the value above response.SetHeader(RtspHeaders.Transport, returnTransportHeader); //Give the sessionid for the transport setup response.SetHeader(RtspHeaders.Session, SessionId); return response; }