/// <summary>
        /// Dynamically creates a Sdp.SessionDescription for the given SourceStream using the information already present and only re-writing the necessary values.
        /// </summary>
        /// <param name="stream">The source stream to create a SessionDescription for</param>
        /// <returns>The created SessionDescription</returns>
        internal Sdp.SessionDescription CreateSessionDescription(SourceMedia stream)
        {
            if (stream == null) throw new ArgumentNullException("stream");
            //else if (SessionDescription != null) throw new NotImplementedException("There is already a m_SessionDescription for this session, updating is not implemented at this time");

            string sessionId = Media.Ntp.NetworkTimeProtocol.DateTimeToNptTimestamp(DateTime.UtcNow).ToString(), sessionVersion = Media.Ntp.NetworkTimeProtocol.DateTimeToNptTimestamp(DateTime.UtcNow).ToString();

            string originatorString = "ASTI-Media-Server " + sessionId + " " + sessionVersion + " IN " + (m_RtspSocket.AddressFamily == AddressFamily.InterNetworkV6 ? "IP6 " : "IP4 " ) + ((IPEndPoint)m_RtspSocket.LocalEndPoint).Address.ToString();

            string sessionName = "ASTI-Streaming-Session-" + stream.Name;

            Sdp.SessionDescription sdp;

            RtpClient sourceClient;

            if (stream is RtpSource)
            {
                RtpSource rtpSource = stream as RtpSource;
                //Make the new SessionDescription
                sdp = new Sdp.SessionDescription(rtpSource.SessionDescription.ToString());

                //Remove the old connection lines if they exist
                while(sdp.ConnectionLine != null) sdp.Remove(sdp.ConnectionLine, false);

                sourceClient = rtpSource.RtpClient;
            }
            else
            {
                sdp = new Sdp.SessionDescription(0);
            }

            //Change the DocumentVersion and update the name
            sdp.SessionName = sessionName;

            //Change the DocumentVersion and update the originator
            sdp.OriginatorAndSessionIdentifier = originatorString;

            //Type = broadcast
            //charset

            string protcol = RtspMessage.MessageIdentifier.ToLowerInvariant(), controlLineBase = "a=control:" + protcol + "://" + ((IPEndPoint)(m_RtspSocket.LocalEndPoint)).Address.ToString() + "/live/" + stream.Id;
            //check for rtspu later...

            //Find an existing control line
            Media.Sdp.SessionDescriptionLine controlLine = sdp.ControlLine;

            //If there was one remove it
            while (controlLine != null)
            {
                sdp.Remove(controlLine);
                controlLine = sdp.ControlLine;
            }
            //Determine if session level control line should be present

            //Rewrite a new connection line
            string addressString = LocalEndPoint.Address.ToString();// +"/127/2";

            //int lastPort = Utility.FindOpenPort( stream.m_ForceTCP ? ProtocolType.Tcp : ProtocolType.Udp);

            //Indicate a port in the sdp, setup should also use this port, this should essentially reserve the port for the setup process...
            //if (!stream.m_ForceTCP)
                //addressString += "/127" +'/' +  lastPort + 1;
            //else
                //addressString += + ((IPEndPoint)RemoteEndPoint).Port;

            //Check for the existing connectionLine
            Sdp.Lines.SessionConnectionLine connectionLine = sdp.ConnectionLine as Sdp.Lines.SessionConnectionLine;

            //Add the new line if needed
            if (connectionLine == null) sdp.ConnectionLine = connectionLine = new Sdp.Lines.SessionConnectionLine()
            {
                ConnectionAddress = addressString,
                ConnectionAddressType = m_RtspSocket.AddressFamily == AddressFamily.InterNetworkV6 ? Media.Sdp.Lines.SessionConnectionLine.IP6 : Media.Sdp.Lines.SessionConnectionLine.IP4,
                ConnectionNetworkType = Media.Sdp.Lines.SessionConnectionLine.InConnectionToken
            };

            IEnumerable<Sdp.SessionDescriptionLine> bandwithLines;

            //Indicate that the server will not accept media as input for this session
            //Put the attribute in the Session Description,
            //Should check that its not already set?
            //sdp.Add(new Sdp.SessionDescriptionLine("a=recvonly"));

            //Remove any existing session range lines, don't upate the version
            while (sdp.RangeLine != null) sdp.Remove(sdp.RangeLine, false);

            //Todo add a Range line which shows the length of this media.

            //Iterate the source MediaDescriptions, could just create a new one with the fmt which contains the profile level information
            foreach (Sdp.MediaDescription md in sdp.MediaDescriptions)
            {
                //Find a control line
                controlLine = md.ControlLine;

                //Rewrite it if present to reflect the appropriate MediaDescription
                while (controlLine != null)
                {
                    md.Remove(controlLine);
                    controlLine = md.ControlLine;
                }

                //Remove old bandwith lines
                bandwithLines = md.BandwidthLines;

                //Remove existing bandwidth information, should check for AS
                if(stream.m_DisableQOS) foreach (Sdp.SessionDescriptionLine line in bandwithLines) md.Remove(line);

                //Remove all other alternate information
                //Should probably only remove certain ones.
                foreach (Sdp.SessionDescriptionLine line in md.Lines.Where(l => l.Parts.Any(p => p.Contains("alt"))).ToArray()) md.Remove(line);

                //Add a control line for the MedaiDescription (which is `rtsp://./Id/audio` (video etc)
                //Should be a TrackId and not the media type to allow more then one media type to be controlled.
                //e.g. Two audio streams or text streams is valid.
                md.Add(new Sdp.SessionDescriptionLine("a=control:" + "/live/" + stream.Id + '/' + md.MediaType));

                //Add the connection line for the media
                //md.Add(connectionLine);

                //Should check for Timing Info and update for playing streams

                if (stream.m_DisableQOS)
                {
                    md.Add(Sdp.Lines.SessionBandwidthLine.DisabledSendLine);
                    md.Add(Sdp.Lines.SessionBandwidthLine.DisabledReceiveLine);
                    md.Add(Sdp.Lines.SessionBandwidthLine.DisabledApplicationSpecificLine);
                }
                //else
                //{
                //    //ToDo use whatever values are defined for the session's bandwidth atp
                //    md.Add(new Sdp.SessionDescriptionLine("b=RS:140"));
                //    md.Add(new Sdp.SessionDescriptionLine("b=RR:140"));

                //    md.Add(new Sdp.SessionDescriptionLine("b=AS:0")); //Determine if AS needs to be forwarded
                //}

                //Should actually reflect outgoing port for this session
                md.MediaPort = 0;

                //Determine if attached and set the MediaPort.
                if (m_RtpClient != null)
                {
                    var context = m_RtpClient.GetContextForMediaDescription(md);

                    if (context != null) md.MediaPort = ((IPEndPoint)context.RemoteRtp).Port;

                    //Set any other variables which may be been changed in the session
                }

                #region Independent TCP

                //if (!stream.m_ForceTCP)
                //{
                //    //md.MediaPort = lastPort;
                //    //lastPort += 2;
                //}
                //else
                //{
                //    //VLC `Blows up` when this happens
                //    //bad SDP "m=" line: m=audio 40563 TCP/RTP/AVP 96
                //    //md.MediaProtocol = "TCP/RTP/AVP";
                //    //fmt should be the same

                //    //This mainly implies that stand-alone RTP over TCP is occuring anyway.

                //    //Since this code supports the RtspServer this is fine for now.

                //    //The RtpClient also deals with the framing from RTSP when used in conjunction with so..
                //    //This needs to be addressed in the RtpClient which allows currently allows Rtp and Rtcp to be duplexed in TCP and UDP
                //    //but does not handle the case of TCP when a sender wants to connect with 2 seperate TCP sockets as per RFC4571.

                //    //a=setup:passive
                //    //a=connection:new

                //    //The RtpClient would then need to have a RtcpSocket ready on the 'standby' just in case the remote end point connected and began sending the data.

                //    //The other way to hanle this would be use only a single socket in both cases and change the remote endpoint = 0.... and decypher the data based on the end point... e.g. the port.
                //}

                #endregion

                //Verify Timing lines.
                //Lines should have a startTime equal the Uptime of the stream
                //Lines should have a stopTime equal to the EndTime of the stream.

            }

            //Top level stream control line (Should only be added if Aggregate Control of the stream is allowed.
            //sdp.Add(new Sdp.SessionDescriptionLine(controlLineBase));

            return sdp;
        }
        //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);
        }
Example #3
0
        /// <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;
        }