//Todo, cleanup and allow existing Rtp and Rtcp socket. /// <summary> /// Will create a <see cref="RtpClient"/> based on the given parameters /// </summary> /// <param name="sessionDescription"></param> /// <param name="sharedMemory"></param> /// <param name="incomingEvents"></param> /// <param name="rtcpEnabled"></param> /// <returns></returns> public static RtpClient FromSessionDescription(Sdp.SessionDescription sessionDescription, Common.MemorySegment sharedMemory = null, bool incomingEvents = true, bool rtcpEnabled = true, System.Net.Sockets.Socket existingSocket = null, int?rtpPort = null, int?rtcpPort = null, int remoteSsrc = 0, int minimumSequentialRtpPackets = 2, bool connect = true, System.Action <System.Net.Sockets.Socket> configure = null) { if (Common.IDisposedExtensions.IsNullOrDisposed(sessionDescription)) { throw new System.ArgumentNullException("sessionDescription"); } Sdp.Lines.SessionConnectionLine connectionLine = new Sdp.Lines.SessionConnectionLine(sessionDescription.ConnectionLine); System.Net.IPAddress remoteIp = System.Net.IPAddress.Parse(connectionLine.Host), localIp; System.Net.NetworkInformation.NetworkInterface localInterface; //If the socket is NOT null and IS BOUND use the localIp of the same address family if (object.ReferenceEquals(existingSocket, null).Equals(false) && existingSocket.IsBound) { //If the socket is IP based if (existingSocket.LocalEndPoint is System.Net.IPEndPoint) { //Take the localIp from the LocalEndPoint localIp = (existingSocket.LocalEndPoint as System.Net.IPEndPoint).Address; } else { throw new System.NotSupportedException("Please create an issue for your use case."); } } else // There is no socket existing. { //If the remote address is the broadcast address or the remote address is multicast if (System.Net.IPAddress.Broadcast.Equals(remoteIp) || IPAddressExtensions.IsMulticast(remoteIp)) { //This interface should be the interface you plan on using for the Rtp communication localIp = SocketExtensions.GetFirstMulticastIPAddress(remoteIp.AddressFamily, out localInterface); } else { //This interface should be the interface you plan on using for the Rtp communication localIp = SocketExtensions.GetFirstUnicastIPAddress(remoteIp.AddressFamily, out localInterface); } } RtpClient client = new RtpClient(sharedMemory, incomingEvents); byte lastChannel = 0; //Todo, check for session level ssrc //if (remoteSsrc.Equals(0)) //{ // //Sdp.SessionDescriptionLine ssrcLine = sessionDescription.SsrcGroupLine; // SsrcLine @ the session level could imply Group //} //For each MediaDescription in the SessionDescription foreach (Media.Sdp.MediaDescription md in sessionDescription.MediaDescriptions) { //Make a RtpClient.TransportContext from the MediaDescription being parsed. TransportContext tc = TransportContext.FromMediaDescription(sessionDescription, lastChannel++, lastChannel++, md, rtcpEnabled, remoteSsrc, minimumSequentialRtpPackets, localIp, remoteIp, //The localIp and remoteIp rtpPort, rtcpPort, //The remote ports to receive data from connect, existingSocket, configure); //Try to add the context try { client.AddContext(tc); } catch (System.Exception ex) { TaggedExceptionExtensions.RaiseTaggedException(tc, "See Tag, Could not add the created TransportContext.", ex); } } //Return the participant return(client); }
internal void ReleaseUnusedResources() { //Enumerate each context 'SETUP' in the session if (m_RtpClient != null) { //Iterate each context in the client foreach (var context in m_RtpClient.GetTransportContexts()) { //Could be a property of the transport.. (especially the rtpclient.) //E.g. IsInactive //If the context does not have any activity if (false == context.HasAnyRecentActivity) { //Session level logger //Context has no activity //See if there is still a source for the context RtpClient.TransportContext sourceContext = GetSourceContext(context.MediaDescription); //If there was a source context with activity if (sourceContext != null && sourceContext.HasAnyRecentActivity) { //Remove the attachment from the source context to the session context RemoveSource(Attached[sourceContext]); //Session level logger //Removed Attachment for sourceContext.Id //Remove the reference to the sourceContext sourceContext = null; } } } } //Get rid of any attachment this ClientSession had which no longer have a context. foreach (IMediaSource source in Attached.Keys.ToList().Where(s=> GetSourceContext(s.MediaDescription) == null)) { //Remove the attached media RemoveSource(source); } //Remove rtp theads if (Playing.Count == 0) { if (m_RtpClient != null) { m_RtpClient.Dispose(); m_RtpClient = null; } } }
/// <summary> /// Sends the Rtcp Goodbye and detaches all sources /// </summary> public override void Dispose() { if (IsDisposed) return; base.Dispose(); RemoveAllAttachmentsAndClearPlaying(); //Mark as disconnected IsDisconnected = true; //Disconnect the RtpClient so it's not hanging around wasting resources for nothing if (m_RtpClient != null) { try { m_RtpClient.InterleavedData -= m_RtpClient_InterleavedData; m_RtpClient.Dispose(); m_RtpClient = null; } catch { } } if (m_Buffer != null) { try { m_Buffer.Dispose(); m_Buffer = null; } catch { } } if (m_RtspSocket != null) { try { if (false == LeaveOpen) m_RtspSocket.Dispose(); m_RtspSocket = null; } catch { } } if (LastRequest != null) { try { LastRequest.Dispose(); LastRequest = null; } catch { } } if (LastResponse != null) { try { LastResponse.Dispose(); LastResponse = null; } catch { } } m_Server = m_Contained = null; }
/// <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; }
/// <summary> /// Will create a <see cref="RtpClient"/> based on the given parameters /// </summary> /// <param name="sessionDescription"></param> /// <param name="sharedMemory"></param> /// <param name="incomingEvents"></param> /// <param name="rtcpEnabled"></param> /// <returns></returns> public static RtpClient FromSessionDescription(Sdp.SessionDescription sessionDescription, Common.MemorySegment sharedMemory = null, bool incomingEvents = true, bool rtcpEnabled = true, Socket existingSocket = null, int? rtpPort = null, int? rtcpPort = null, int remoteSsrc = 0, int minimumSequentialRtpPackets = 2) { if (sessionDescription == null) throw new ArgumentNullException("sessionDescription"); Sdp.Lines.SessionConnectionLine connectionLine = new Sdp.Lines.SessionConnectionLine(sessionDescription.ConnectionLine); IPAddress remoteIp = IPAddress.Parse(connectionLine.IPAddress), localIp = Media.Common.Extensions.Socket.SocketExtensions.GetFirstIPAddress(remoteIp.AddressFamily); RtpClient participant = new RtpClient(sharedMemory, incomingEvents); byte lastChannel = 0; bool hasSocket = existingSocket != null; foreach (Media.Sdp.MediaDescription md in sessionDescription.MediaDescriptions) { TransportContext tc = TransportContext.FromMediaDescription(sessionDescription, lastChannel++, lastChannel++, md, rtcpEnabled, remoteSsrc, minimumSequentialRtpPackets); //Find range info in the SDP var rangeInfo = md.RangeLine; //If there is a range directive if (rangeInfo == null) { rangeInfo = sessionDescription.RangeLine; if (rangeInfo != null) { string type; Sdp.SessionDescription.TryParseRange(rangeInfo.Parts[0], out type, out tc.m_StartTime, out tc.m_EndTime); } //else if (sessionDescription.TimeDescriptions.Count > 0) //{ //tc.MediaStartTime = TimeSpan.FromMilliseconds(); //tc.MediaEndTime = TimeSpan.FromMilliseconds(); //} } //Check for udp if no existing socket was given if (false == hasSocket && string.Compare(md.MediaProtocol, Media.Rtp.RtpClient.RtpAvpProfileIdentifier, true) == 0) { int localPort = Media.Common.Extensions.Socket.SocketExtensions.FindOpenPort(ProtocolType.Udp); tc.Initialize(localIp, remoteIp, localPort++, localPort++, rtpPort ?? md.MediaPort, rtcpPort ?? md.MediaPort + 1); } else if (hasSocket)//If had a socket use it { tc.Initialize(existingSocket); } else { tc.Initialize(localIp, remoteIp, rtpPort ?? md.MediaPort); } //Try to add the context try { participant.AddContext(tc); } catch (Exception ex) { Media.Common.Extensions.Exception.ExceptionExtensions.TryRaiseTaggedException(tc, "See Tag, Could not add the created TransportContext.", ex); } } //Return the participant return participant; }
public static bool EnableFeedbackReports(RtpClient client) { if (FeedbackInstances.Add(client)) { client.RtcpPacketReceieved += SendFeedback; return true; } return false; }
public static bool DisableFeedbackReports(RtpClient client) { if (FeedbackInstances.Remove(client)) { client.RtcpPacketReceieved -= SendFeedback; return true; } return false; }
/// <summary> /// Returns the amount of bytes read to completely read the application layer framed data /// Where a negitive return value indicates no more data remains. /// </summary> /// <param name="received">How much data was received</param> /// <param name="frameChannel">The output of reading a frameChannel</param> /// <param name="context">The context assoicated with the frameChannel</param> /// <param name="offset">The reference to offset to look for framing data</param> /// <param name="raisedEvent">Indicates if an event was raised</param> /// <param name="buffer">The optional buffer to use.</param> /// <returns>The amount of bytes the frame data SHOULD have</returns> int ReadApplicationLayerFraming(int received, out byte frameChannel, out RtpClient.TransportContext context, ref int offset, out bool raisedEvent, byte[] buffer = null) { //There is no relevant TransportContext assoicated yet. context = null; //The channel of the frame - The Framing Method frameChannel = default(byte); raisedEvent = false; if (received <= 0) return -1; buffer = buffer ?? m_Buffer.Array; int bufferLength = buffer.Length, bufferOffset = offset; //Look for the frame control octet int startOfFrame = Array.IndexOf<byte>(buffer, BigEndianFrameControl, bufferOffset, received); int frameLength = 0; //If not found everything belongs to the upper layer if (startOfFrame == -1) { //System.Diagnostics.Debug.WriteLine("Interleaving: " + received); OnInterleavedData(buffer, bufferOffset, received); raisedEvent = true; //Indicate the amount of data consumed. return received; } else if (startOfFrame > offset) // If the start of the frame is not at the beginning of the buffer { //Determine the amount of data which belongs to the upper layer int upperLayerData = startOfFrame - bufferOffset; //System.Diagnostics.Debug.WriteLine("Moved To = " + startOfFrame + " Of = " + received + " - Bytes = " + upperLayerData + " = " + Encoding.ASCII.GetString(m_Buffer, mOffset, startOfFrame - mOffset)); OnInterleavedData(buffer, bufferOffset, upperLayerData); raisedEvent = true; //Indicate length from offset until next possible frame. (should always be positive, if somehow -1 is returned this will signal a end of buffer to callers) //If there is more data related to upperLayerData it will be evented in the next run. (See RtspClient ProcessInterleaveData notes) return upperLayerData; } //If there is not enough data for a frame header return if (bufferOffset + InterleavedOverhead > bufferLength) return -1; //Todo Determine from Context to use control channel and length. (Check MediaDescription) //NEEDS TO HANDLE CASES WHERE RFC4571 Framing are in play and no $ or Channel are used.... //The amount of data needed for the frame comes from TryReadFrameHeader frameLength = TryReadFrameHeader(buffer, bufferOffset, out frameChannel, true, BigEndianFrameControl, true); //Assign a context if there is a frame of any size if (frameLength >= 0) { //Assign the context context = GetContextByChannels(frameChannel); //Increase the result by the size of the header frameLength += InterleavedOverhead; } //Return the amount of bytes or -1 if any error occured. return frameLength; }