/// <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); }
/// <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; }