Beispiel #1
0
        public void Dispose()
        {
            lock (classLock)
            {
                try
                {
                    disposed = true;

                    // Unhook events
                    RtpEvents.ReceiverReport         -= new RtpEvents.ReceiverReportEventHandler(RtpReceiverReport);
                    RtpEvents.DuplicateCNameDetected -= new RtpEvents.DuplicateCNameDetectedEventHandler(DuplicateCNameDetected);

                    if (updateThread != null)
                    {
                        updateThread.Dispose();
                        updateThread = null;
                    }

                    if (rtpSession != null)
                    {
                        rtpSession.Dispose();
                        rtpSession = null;
                        rtpSender  = null;
                    }
                }
                catch (Exception e)
                {
                    eventLog.WriteEntry(string.Format(CultureInfo.CurrentCulture,
                                                      Strings.ErrorDisposingConnectivityDetector, e.ToString()), EventLogEntryType.Error, 0);
                }
            }
        }
 protected override void CreateRtpSenders()
 {
     //
     // Don't call base.CreateRtpSenders, because we need to customize the 
     // creation of our RtpSenders
     //
     videoSender = CreateRtpSender(videoProps);
     audioSender = CreateRtpSender(audioProps);
 }
Beispiel #3
0
        private void InitializeNetwork()
        {
            RtpEvents.ReceiverReport         += new RtpEvents.ReceiverReportEventHandler(RtpReceiverReport);
            RtpEvents.DuplicateCNameDetected += new RtpEvents.DuplicateCNameDetectedEventHandler(DuplicateCNameDetected);

            // Create participant
            rtpParticipant = new RtpParticipant(cName, name);
            rtpParticipant.SetTool(true);

            // Create session with Participant and Rtp data
            rtpSession = new RtpSession(new IPEndPoint(IPAddress.Parse(ipAddress), port), rtpParticipant, true, true);

            // Create RtpSender
            rtpSender = rtpSession.CreateRtpSender(HostName, PayloadType.PipecleanerSignal, null);
        }
Beispiel #4
0
        /// <summary>
        /// Finishes the creation of an RtpSender by adding it to local collections and announcing
        /// it to the remote sites via an Rtcp packet
        /// </summary>
        private void _CreateRtpSender(RtpSender rtpSender)
        {
            // Add it to the collection
            lock(rtpSenders)
            {
                rtpSenders.Add(rtpSender.SSRC, rtpSender);
            }

            // We would like to try and have the stream constructed at the remote sites before the
            // Rtp data starts arriving so that none of it is missed.  To help with that, when an
            // RtpSender is constructed, it sets an Sdes private extension indicating its payload
            // type.  Here we force an Rtcp compound packet to be sent and give it a little time.
            rtcpSender.SendRtcpDataNow();
            Thread.Sleep(250);
        }
Beispiel #5
0
        private void InitializeNetwork()
        {
            RtpEvents.ReceiverReport            += new RtpEvents.ReceiverReportEventHandler(RtpReceiverReport);
            RtpEvents.DuplicateCNameDetected    += new RtpEvents.DuplicateCNameDetectedEventHandler(DuplicateCNameDetected);

            // Create participant
            rtpParticipant = new RtpParticipant(cName, name);
            rtpParticipant.SetTool(true);

            // Create session with Participant and Rtp data
            rtpSession = new RtpSession(new IPEndPoint(IPAddress.Parse(ipAddress), port), rtpParticipant, true, true);

            // Create RtpSender
            rtpSender = rtpSession.CreateRtpSender(HostName, PayloadType.PipecleanerSignal, null, null);
        }
Beispiel #6
0
        private void DisposeSender()
        {
            // Pri2: Account for the case that the sender creation has been fired, but the sender hasn't yet been created.
            if (rtpSender != null)
            {
                this.rtpSender.Dispose();
                this.rtpSender = null;
            }

            this.createSenderFired = false;
        }
Beispiel #7
0
 public void Add(uint ssrc, RtpSender rtpSender)
 {
     Dictionary.Add(ssrc, rtpSender);
 }
        /// <summary>
        /// Thread entry point for attempting to recreate the network connection
        /// </summary>
        private void ResetThread()
        {
            while( true ) {
                using( Synchronizer.Lock( this ) ) {
                    try {
                        // Try to Reconnect
                        ushort fec;
                        short interpacketdelay;
                        using( Synchronizer.Lock( this.m_Model.SyncRoot ) ) {
                            using( Synchronizer.Lock( this.m_Model.ViewerState.SyncRoot ) ) {
                                fec = (ushort)this.m_Model.ViewerState.ForwardErrorCorrection;
                                interpacketdelay = (short)this.m_Model.ViewerState.InterPacketDelay;
                            }
                        }
                        if( fec == 0 )
                            this.m_RtpSender = this.m_RtpSession.CreateRtpSender( "Classroom Presenter", PayloadType.dynamicPresentation, null);
                        else
                            this.m_RtpSender = this.m_RtpSession.CreateRtpSenderFec( "Classroom Presenter", PayloadType.dynamicPresentation, null, 0, fec );
                        this.m_RtpSender.DelayBetweenPackets = interpacketdelay;

                        return;
                    } catch( Exception ) {
                        System.Threading.Thread.Sleep( 100 );
                    }
                }
            }
        }
        // CF1, CF2
        private void DShowNetworkFiltersForm_Load(object sender, System.EventArgs e)
        {
            HookRtpEvents();  // CF1
            JoinRtpSession(); // CF1

            if(presenter)
            {
                // CF2 Add devices to UI
                foreach(FilterInfo fi in VideoSource.Sources())
                {
                    cklbCameras.Items.Add(fi);
                }

                rtpSender = rtpSession.CreateRtpSender("Video", PayloadType.dynamicVideo, null); // CF1
            }
            else
            {
                cklbCameras.Visible = false;
            }
        }
Beispiel #10
0
 /// <summary>
 /// Creates the RtpSender for this stream asynchronously.  This causes the 500ms Thread.Sleep that occurs in
 /// RtpSession.CreateRtpSender to not cause performance problems during playback.
 /// </summary>
 private void CreateSender(object stateVar)
 {
     // For multi-threading safety purposes, we create the sender before assigning the new sender to the class variable.
     RtpSender newSender = rtpSession.CreateRtpSender(string.Format(CultureInfo.CurrentCulture, 
         Strings.Playback, cname, name), this.streamPayload, this.privExtns);
     this.rtpSender = newSender;
 }
Beispiel #11
0
        protected void DisposeRtpSender(RtpSender rtpSender)
        {
            if(!rtpSenders.Contains(rtpSender))
            {
                throw new ArgumentException("RtpSender did not originate from this capability!");
            }

            rtpSenders.Remove(rtpSender);
            rtpSender.Dispose();
        }
Beispiel #12
0
        /// <summary>
        /// Send all the frames that should be sent up to this point in time.
        /// </summary>
        /// <param name="bytesSent">Number of bytes sent.</param>
        /// <param name="cumulativeLateness">Sum of temporal disparities over all frames sent.</param>
        /// <param name="firstStoredTick">The 'start' time on the first index in this whole stream.</param>
        /// <param name="sender">The RtpSender to send data on.</param>
        /// <param name="startTimeTicks">The start time of sending, in ticks.</param>
        /// <param name="timeUntilFrame">The temporal distance between the current frame and the next one to be sent, in ticks.</param>
        /// <returns>Number of frames sent.</returns>
        public int SendFrames( RtpSender sender, long timeBoundary, out long timeUntilFrame, ref long totalBytesSent, ref long cumulativeLateness )
        {
            if( this.populating )
                throw new InvalidOperationException(Strings.BufferplayerSendframesError);

            int framesSent = 0;

            try
            {
                while ( currentIndex < indexCount && indices[currentIndex].timestamp <= timeBoundary )
                {
                    long startTimer = DateTime.Now.Ticks;

                    int frameLength = 1 + indices[currentIndex].end - indices[currentIndex].start;

                    if ( frameLength > 0)
                    {
                        // Calculate how late the frame will be
                        long lateness = (timeBoundary - indices[currentIndex].timestamp) / Constants.TicksPerMs;
                        if( lateness > Constants.TicksSpent )
                            Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
                                "--- FRAME LATENESS OF: {0} ms", lateness));
                        cumulativeLateness += lateness;

                        // Send Frame
                        buffer.Reset( indices[currentIndex].start - indices[0].start, frameLength );
                        sender.Send( buffer );
                    }
                    else
                    {
                        // (pbristow) Why would this happen???
                        Debug.Fail("Frame of length zero found.");
                    }

                    totalBytesSent += frameLength;
                    ++framesSent;
                    ++currentIndex;

                    long takenTime = DateTime.Now.Ticks - startTimer;
                    if( takenTime > Constants.TicksSpent )
                        Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
                            "TIME WASTED TO SEND A FRAME: {0} ms, ID: {1}, bytes: {2}",
                            (takenTime / Constants.TicksPerMs), streamID, frameLength));
                }

                if ( currentIndex == indexCount )
                {
                    timeUntilFrame = 0;
                }
                else
                {
                    timeUntilFrame = (indices[currentIndex].timestamp - timeBoundary);
                }
            }
            catch(ObjectDisposedException)
            {
                // Sender is disposed by Stop being called
                timeUntilFrame = long.MaxValue;
            }

            return framesSent;
        }
Beispiel #13
0
 private void DisposeRtpSession()
 {
     if (rtpSession != null)
     {
         rtpSession.Dispose();
         rtpSession = null;
         rtpSender = null;
     }
 }
Beispiel #14
0
 public void JoinRTPSession(bool receive)
 {
     if (receive)
     {
         RegisterRtpUserEvents();
         RegisterRtpStreamEvents();
     }
     rtpSession = new RtpSession(network.GetChannel(channel), new RtpParticipant(Environment.MachineName, Environment.MachineName), true, receive);
     rtpSender = rtpSession.CreateRtpSender("VOIP Listener", PayloadType.dynamicAudio, null);
 }
Beispiel #15
0
        public void RenderNetwork(RtpSender rtpSender)
        {
            if(rtpSender == null)
            {
                string msg = Strings.NullRtpSenderError;

                Debug.Fail(msg);
                throw new ArgumentNullException(Strings.RtpSender, msg);
            }

            renderer = (Renderer)Filter.NetworkRenderer();
            ((IRtpRenderer)renderer.BaseFilter).Initialize2(rtpSender,this.rtpRendererFlags);
            iGB.AddFilter(renderer.BaseFilter, renderer.FriendlyName);
            renderer.AddedToGraph(fgm);

            // Connect last pin (device or compressor) to the network renderer
            iGB.Connect(compressor != null ? compressor.OutputPin : source.OutputPin,
                renderer.InputPin);
        }
Beispiel #16
0
        /// <summary>
        /// Hooks up the provided renderer to a pin of the provided media type on the file
        /// </summary>
        /// <param name="renderer">The filter to render data to</param>
        /// <param name="mediaType">The type of pin to connect to this renderer</param>
        /// <param name="rtpSender">RtpSender that will be used by the renderer</param>
        /// <param name="filterName">The name of the render filter in the graph</param>
        private void HookUpFilter(ref IBaseFilter renderer, Guid mediaType, RtpSender rtpSender,
            string filterName)
        {
            // Pin the Guid, so we can pass it as an IntPtr
            GCHandle hGuid = GCHandle.Alloc(mediaType, GCHandleType.Pinned);

            try
            {
                // Only create the filter once
                if (renderer == null)
                {
                    // ConferenceXP's custom filter for sending data over network
                    renderer = MDShow.RtpRendererClass.CreateInstance();

                    // Initialize the filter with an RtpSender to use
                    ((IRtpRenderer)renderer).Initialize(rtpSender);

                    // Add filter to graph
                    iGB.AddFilter(renderer, filterName);
                }

                // The WM file may not have a pin of the desired category, so this may fail
                // e.g. Audio only file won't have video, video may not have audio, etc.
                iCGB2.RenderStream(IntPtr.Zero, hGuid.AddrOfPinnedObject(), wmASFReader, null, renderer);
            }
            catch (COMException ce)
            {
                Console.WriteLine(ce.ToString());
            }
            finally
            {
                // Always unpin the Guid
                hGuid.Free();
            }
        }
        protected RtpSender CreateRtpSender(RtpSenderProperties props)
        {
            // Setup private extensions
            Hashtable priExns = new Hashtable();

            // Add unique ID
            Debug.Assert(props.ID != Guid.Empty);
            priExns.Add(PEP_IDENTIFIER, props.ID.ToString());

            // Set the delay between packets now, so that it happens in the constructor and goes out with
            //  the first RTCP packet
            priExns.Add(Rtcp.PEP_DBP, props.DelayBetweenPackets.ToString(CultureInfo.InvariantCulture));

            // Set the channel-ness or ownership of the stream...
            if(props.Channel && props.OwnedByLocalParticipant)
            {
                throw new ArgumentException(Strings.CannotBeChannelAndOwned);
            }
            if(props.Channel)
            {
                priExns.Add(PEP_CHANNEL, true.ToString());
            }
            else if(props.OwnedByLocalParticipant)
            {
                priExns.Add(PEP_CHANNEL, false.ToString());
            }
            // else neither are true (i.e. it's owned but not by the local participant - a concept only possible in CXP...)

            // For capabilities with shared forms only
            if(props.SharedFormID != Guid.Empty)
            {
                priExns.Add(PEP_SHAREDFORM, props.SharedFormID.ToString());
            }

            // Create the sender
            RtpSender rtpSender = null;
            
            if(props.FecEnabled && props.FecChecksum > 0)
            {
                rtpSender = Conference.RtpSession.CreateRtpSenderFec(props.Name, 
                    props.PayloadType, priExns, props.FecData, props.FecChecksum);
            }
            else
            {
                rtpSender = Conference.RtpSession.CreateRtpSender(props.Name, 
                    props.PayloadType, priExns);
            }
            
            // Add to collection
            rtpSenders.Add(rtpSender);

            return rtpSender;
        }
        // CF1
        private void LeaveRtpSession()
        {
            UnhookRtpEvents();

            if(rtpSession != null)
            {
                rtpSession.Dispose();
                rtpSession = null;
                rtpSender = null;
                rtpStream = null;
            }
        }
        protected void DisposeRtpSender(RtpSender rtpSender)
        {
            if(!rtpSenders.Contains(rtpSender))
            {
                throw new ArgumentException(Strings.DidNotOriginateFromHere);
            }

            rtpSenders.Remove(rtpSender);
            rtpSender.Dispose();
        }
        public RTPMessageSender(IPEndPoint ep, PresenterModel model, ClassroomModel classroom)
        {
            this.m_Model = model;
            this.m_Classroom = classroom;

            this.m_RtpLocalParticipant = new RtpLocalParticipant(this.m_Model.Participant);

            using(Synchronizer.Lock(this)) {
                // Register the stream event listeners first, since otherwise there would be a chance
                // that streams could be added between creating the RtpSession (which connects immediately).
                this.m_ParticipantManager = new ParticipantManager(this);

                this.m_RtpSession = new RtpSession(ep, this.m_RtpLocalParticipant, true, true);

                // TODO: Choose a meaningful value for the RtpSender name.
                ushort fec;
                short interpacketdelay;
                using (Synchronizer.Lock(model.SyncRoot)) {
                    using (Synchronizer.Lock(model.ViewerState.SyncRoot)) {
                        fec = (ushort)model.ViewerState.ForwardErrorCorrection;
                        interpacketdelay = (short)model.ViewerState.InterPacketDelay;
                    }
                }
                if( fec == 0 )
                    this.m_RtpSender = this.m_RtpSession.CreateRtpSender( "Classroom Presenter", PayloadType.dynamicPresentation, null );
                else
                    this.m_RtpSender = this.m_RtpSession.CreateRtpSenderFec( "Classroom Presenter", PayloadType.dynamicPresentation, null, 0, fec );
                this.m_RtpSender.DelayBetweenPackets = interpacketdelay;

                // Initialize the message chunking utilities.
                this.m_Encoder = new Chunk.ChunkEncoder();
                // TODO: Make the buffer size dynamic, ie., grow if we send a single very large message.
                // Make the buffer store up to 5MB worth of data.
                this.m_FrameBuffer = new FrameBuffer(5 * 1024 * 1024 / this.m_Encoder.MaximumChunkSize);

                // Create the NackManager which is responsible for sending NACKs on behalf of
                // our set of RTPMessageReceivers.
                this.m_NackManager = new RTPNackManager(this, this.m_Classroom);
            }

            // Create network services outside of the "lock(this)" so they can lock their own objects
            // without worrying about locking order.

            // Create the PresenterNetworkService which will watch for changes to the model and send messages.
            this.m_PresenterNetworkService = new PresenterNetworkService(this, this.m_Model);

            // Create the StudentSubmissionsNetworkService which will watch for requests to submit and send messages.
            this.m_StudentSubmissionNetworkService = new StudentSubmissionNetworkService(this, this.m_Model);

            // Create the SynchronizationNetworkService which will watch for all synchronization messages.
            this.m_SynchronizationNetworkService = new SynchronizationNetworkService( this, this.m_Model );

            // Create the ScriptingNetworkService which will watch for all scripting messages.
            this.m_ScriptingNetworkService = new ScriptingNetworkService( this, this.m_Model );

            // Create the BeaconService which will broadcast periodic information about the presentation.
            this.m_BeaconService = new Beacons.DefaultBeaconService(this, this.m_Model);
        }
        protected virtual void CreateRtpSenders()
        {
            // If the derived class hasn't created its own RtpSender, create one for it
            if (rtpSender == null)
            {
                rtpSender = CreateRtpSender();
                msSend = new MemoryStream();
                bfSend = new BinaryFormatter();
            }

            // If the derived class hasn't created its own RtpSenderBackground, create one for it
            if(capProps.BackgroundSender)
            {
                if (rtpSenderBackground == null)
                {
                    rtpSenderBackground = CreateRtpSender();
                    msSendBackground = new MemoryStream();
                    bfSendBackground = new BinaryFormatter();
                }
            }
        }
Beispiel #22
0
 public void Add(uint ssrc, RtpSender rtpSender)
 {
     Dictionary.Add(ssrc, rtpSender);
 }
        protected void DisposeRtpSenders()
        {
            if (this.rtpSender != null)
            {
                DisposeRtpSender(this.rtpSender);
                this.rtpSender = null;
            }

            if(rtpSenderBackground != null)
            {
                DisposeRtpSender(rtpSenderBackground);
                rtpSenderBackground = null;
            }

            // Dispose any other RtpSenders that were created
            foreach(RtpSender rtpSender in (ArrayList)rtpSenders.Clone())
            {
                DisposeRtpSender(rtpSender);
            }
        }
Beispiel #24
0
 /// <summary>
 /// Creates the RtpSender for this stream asynchronously.  This causes the 500ms Thread.Sleep that occurs in
 /// RtpSession.CreateRtpSender to not cause performance problems during playback.
 /// </summary>
 private void CreateSender(object stateVar)
 {
     // For multi-threading safety purposes, we create the sender before assigning the new sender to the class variable.
     RtpSender newSender = rtpSession.CreateRtpSender( "Playback: " + cname + " : " + name, this.streamPayload, this.privExtns );
     this.rtpSender = newSender;
 }
        private void _SendObject(object o, RtpSender rtpSender, MemoryStream ms, BinaryFormatter bf)
        {
            if (disposed)
            {
                throw new ObjectDisposedException(name);
            }

            if(!isSending)
            {
                throw new ApplicationException(string.Format(CultureInfo.CurrentCulture, 
                    Strings.CallSendBeforeSendObject, name));
            }

            ms.Position = 0; // set the "useful bytes" "pointer" back to 0
            bf.Serialize(ms, o); // serialize, which puts the "useful bytes pointer" at the end of hte useful data

            // Get a byte[] of the serialized object
            int numBytes = (int)ms.Position; // record the number of "useful bytes"
            byte[] byteObj = new Byte[numBytes];
            ms.Position = 0; // set the pointer back to 0, so we can read from that point
            ms.Read(byteObj, 0, numBytes); // read all the useful bytes

            rtpSender.Send(byteObj);
        }        
Beispiel #26
0
 public RTPSendConnection(IPEndPoint ipe)
 {
     this.m_Participant = new RtpParticipant(Guid.NewGuid().ToString(), "Classroom Playback");
     this.m_Session = new RtpSession(ipe, this.m_Participant, true, false);
     this.m_Sender = this.m_Session.CreateRtpSenderFec("Classroom Presenter", PayloadType.dynamicPresentation, null, 0, 100);
     this.m_Queue = new SendingQueue(this);
 }
Beispiel #27
0
        public void RenderNetwork(RtpSender rtpSender)
        {
            if(rtpSender == null)
            {
                string msg = "Null is not a valid value for parameter 'rtpSender'";

                Debug.Fail(msg);
                throw new ArgumentNullException("rtpSender", msg);
            }

            renderer = (Renderer)Filter.NetworkRenderer();
            ((IRtpRenderer)renderer.BaseFilter).Initialize(rtpSender);
            iGB.AddFilter(renderer.BaseFilter, renderer.FriendlyName);
            renderer.AddedToGraph(fgm);

            // Connect last pin (device or compressor) to the network renderer
            iGB.Connect(compressor != null ? compressor.OutputPin : source.OutputPin,
                renderer.InputPin);
        }
Beispiel #28
0
        public void Dispose()
        {
            lock(classLock)
            {
                try
                {
                    disposed = true;

                    // Unhook events
                    RtpEvents.ReceiverReport            -= new RtpEvents.ReceiverReportEventHandler(RtpReceiverReport);
                    RtpEvents.DuplicateCNameDetected    -= new RtpEvents.DuplicateCNameDetectedEventHandler(DuplicateCNameDetected);

                    if(updateThread != null)
                    {
                        updateThread.Dispose();
                        updateThread = null;
                    }

                    if(rtpSession != null)
                    {
                        rtpSession.Dispose();
                        rtpSession = null;
                        rtpSender = null;
                    }
                }
                catch(Exception e)
                {
                    eventLog.WriteEntry("Error disposing ConnectivityDetector. \n" + e.ToString(),
                        EventLogEntryType.Error, 0);
                }
            }
        }
Beispiel #29
0
 // CF2 Create participant, join session
 // CF3 Retrieve RtpSender
 private void JoinRtpSession(string name)
 {
     rtpSession = new RtpSession(ep, new RtpParticipant(name, name), true, true);
     rtpSender = rtpSession.CreateRtpSenderFec(name, PayloadType.Chat, null, 0, 200);
 }
Beispiel #30
0
 public virtual void RenderNetwork(RtpSender rtpSender, PayloadType payload)
 {
     RenderNetwork(rtpSender);
 }
Beispiel #31
0
 private void LeaveRtpSession()
 {
     if(rtpSession != null)
     {
         // Clean up all outstanding objects owned by the RtpSession
         rtpSession.Dispose();
         rtpSession = null;
         rtpSender = null;
     }
 }
Beispiel #32
0
        /// <summary>
        /// Connect up a branch of the graph for network sending.  Source filter should already be in the graph.
        /// DVSplitter and compressor may also already be connected, but for uncompressed cases, the Splitter
        /// may not be there yet.
        /// </summary>
        /// <param name="rtpSender"></param>
        /// <param name="payload"></param>
        public override void RenderNetwork(RtpSender rtpSender, PayloadType payload)
        {
            if (rtpSender == null) {
                string msg = Strings.NullRtpSenderError;
                Debug.Fail(msg);
                throw new ArgumentNullException(Strings.RtpSender, msg);
            }

            //Splitter may not yet be added in network scenarios without compression
            if (!this.AddDVSplitter()) {
                throw new ApplicationException("Failed to add DV Splitter");
            }

            networkContext = true;

            if (payload == PayloadType.dynamicVideo) {
                videoRenderer = (Renderer)Filter.NetworkRenderer();
                ((IRtpRenderer)videoRenderer.BaseFilter).Initialize2(rtpSender, this.rtpRendererFlags);
                iGB.AddFilter(videoRenderer.BaseFilter, videoRenderer.FriendlyName);
                videoRenderer.AddedToGraph(fgm);
                iGB.Connect(videoCompressor != null ? videoCompressor.OutputPin : splitterVideoOut,
                    videoRenderer.InputPin);
            }
            else if (payload == PayloadType.dynamicAudio) {
                audioRenderer = (Renderer)Filter.NetworkRenderer();
                ((IRtpRenderer)audioRenderer.BaseFilter).Initialize2(rtpSender, this.rtpRendererFlags);
                iGB.AddFilter(audioRenderer.BaseFilter, audioRenderer.FriendlyName);
                audioRenderer.AddedToGraph(fgm);
                iGB.Connect(audioCompressor != null ? audioCompressor.OutputPin : splitterAudioOut,
                    audioRenderer.InputPin);
            }
        }
Beispiel #33
0
 public void RtpSenders(RtpSender audioSender, RtpSender videoSender)
 {
     this.audioSender = audioSender;
     this.videoSender = videoSender;
 }