Пример #1
0
        public void Clear()
        {
            SendVideoTrack?.Dispose();
            SendVideoTrack = null;

            offerPc?.Close();
            offerPc?.Dispose();
            offerPc = null;
            answerPc?.Close();
            answerPc?.Dispose();
            answerPc    = null;
            SendTexture = null;
            RecvTexture = null;
        }
Пример #2
0
        public void CreateDataChannel()
        {
            RTCConfiguration config = default;

            config.iceServers = new[] { new RTCIceServer {
                                            urls = new[] { "stun:stun.l.google.com:19302" }
                                        } };
            var peer     = new RTCPeerConnection(ref config);
            var option1  = new RTCDataChannelInit(true);
            var channel1 = peer.CreateDataChannel("test1", ref option1);

            Assert.AreEqual("test1", channel1.Label);

            // It is return -1 when channel is not connected.
            Assert.AreEqual(channel1.Id, -1);

            channel1.Close();
            peer.Close();
        }
Пример #3
0
    private void HangUp()
    {
        RemoveTracks();

        _pc1.Close();
        _pc2.Close();
        Debug.Log("Close local/remote peer connection");
        _pc1.Dispose();
        _pc2.Dispose();
        _pc1 = null;
        _pc2 = null;
        callButton.interactable        = true;
        hangUpButton.interactable      = false;
        bandwidthSelector.interactable = false;
        bandwidthSelector.value        = 0;

        sourceImage.color  = Color.black;
        receiveImage.color = Color.black;
    }
Пример #4
0
        public IEnumerator SetRemoteDescriptionFailed()
        {
            var config = GetDefaultConfiguration();
            var peer1  = new RTCPeerConnection(ref config);
            var peer2  = new RTCPeerConnection(ref config);

            var stream = new MediaStream();
            var obj    = new GameObject("audio");
            var source = obj.AddComponent <AudioSource>();

            source.clip = AudioClip.Create("test", 480, 2, 48000, false);
            var track  = new AudioStreamTrack(source);
            var sender = peer1.AddTrack(track, stream);

            var op1 = peer1.CreateOffer();

            yield return(op1);

            var desc = op1.Desc;
            var op2  = peer1.SetLocalDescription(ref desc);

            yield return(op2);

            // change sdp to cannot parse
            desc.sdp = desc.sdp.Replace("a=mid:0", "a=mid:10");
            var op3 = peer2.SetRemoteDescription(ref desc);

            yield return(op3);

            Assert.True(op3.IsDone);
            Assert.True(op3.IsError);
            Assert.IsNotEmpty(op3.Error.message);

            peer1.RemoveTrack(sender);
            track.Dispose();
            stream.Dispose();
            peer1.Close();
            peer2.Close();
            peer1.Dispose();
            peer2.Dispose();
            Object.DestroyImmediate(source.clip);
            Object.DestroyImmediate(obj);
        }
Пример #5
0
        public void Construct()
        {
            var peer = new RTCPeerConnection();

            Assert.AreEqual(0, peer.GetReceivers().Count());
            Assert.AreEqual(0, peer.GetSenders().Count());
            Assert.AreEqual(0, peer.GetTransceivers().Count());
            Assert.AreEqual(RTCPeerConnectionState.New, peer.ConnectionState);
            Assert.That(() => peer.LocalDescription, Throws.InvalidOperationException);
            Assert.That(() => peer.RemoteDescription, Throws.InvalidOperationException);
            Assert.That(() => peer.PendingLocalDescription, Throws.InvalidOperationException);
            Assert.That(() => peer.PendingRemoteDescription, Throws.InvalidOperationException);
            Assert.That(() => peer.CurrentLocalDescription, Throws.InvalidOperationException);
            Assert.That(() => peer.CurrentRemoteDescription, Throws.InvalidOperationException);
            peer.Close();

            Assert.AreEqual(RTCPeerConnectionState.Closed, peer.ConnectionState);
            peer.Dispose();
        }
Пример #6
0
    private void HangUp()
    {
        RemoveTracks();

        _pc1.Close();
        _pc2.Close();
        _pc1.Dispose();
        _pc2.Dispose();
        _pc1 = null;
        _pc2 = null;
        callButton.interactable    = true;
        hangUpButton.interactable  = false;
        codecSelector.interactable = true;
        codecSelector.value        = 0;
        actualCodecText.text       = string.Empty;

        sourceImage.color  = Color.black;
        receiveImage.color = Color.black;
    }
        public IEnumerator RestartIceInvokeOnNegotiationNeeded()
        {
            RTCConfiguration config = default;

            config.iceServers = new[] { new RTCIceServer {
                                            urls = new[] { "stun:stun.l.google.com:19302" }
                                        } };
            var peer1 = new RTCPeerConnection(ref config);
            var peer2 = new RTCPeerConnection(ref config);

            peer1.OnIceCandidate = candidate => { peer2.AddIceCandidate(candidate); };
            peer2.OnIceCandidate = candidate => { peer1.AddIceCandidate(candidate); };

            AudioStreamTrack track = new AudioStreamTrack();

            peer1.AddTrack(track);

            yield return(SignalingOffer(peer1, peer2));

            bool isInvokeOnNegotiationNeeded1 = false;
            bool isInvokeOnNegotiationNeeded2 = false;

            peer1.OnNegotiationNeeded = () => isInvokeOnNegotiationNeeded1 = true;
            peer2.OnNegotiationNeeded = () => isInvokeOnNegotiationNeeded2 = true;

            peer1.RestartIce();
            var op9 = new WaitUntilWithTimeout(() => isInvokeOnNegotiationNeeded1, 5000);

            yield return(op9);

            Assert.That(op9.IsCompleted, Is.True);

            peer2.RestartIce();
            var op10 = new WaitUntilWithTimeout(() => isInvokeOnNegotiationNeeded2, 5000);

            yield return(op10);

            Assert.That(op10.IsCompleted, Is.True);

            track.Dispose();
            peer1.Close();
            peer2.Close();
        }
    private void HangUp()
    {
        foreach (var image in receiveImages.Concat(sourceImages))
        {
            image.texture = null;
            DestroyImmediate(image.gameObject);
        }

        receiveImages.Clear();
        sourceImages.Clear();

        foreach (var cam in cameras)
        {
            DestroyImmediate(cam.gameObject);
        }

        cameras.Clear();

        foreach (var track in videoStreamTrackList)
        {
            track.Dispose();
        }

        videoStreamTrackList.Clear();
        sendingSenderList.Clear();

        _pc1.Close();
        _pc2.Close();
        Debug.Log("Close local/remote peer connection");
        _pc1.Dispose();
        _pc2.Dispose();
        _pc1                              = null;
        _pc2                              = null;
        videoIndex                        = 0;
        objectIndex                       = 0;
        widthInput.interactable           = true;
        heightInput.interactable          = true;
        callButton.interactable           = true;
        hangUpButton.interactable         = false;
        addVideoObjectButton.interactable = false;
        addTracksButton.interactable      = false;
    }
        public void GetConfiguration()
        {
            var config = GetDefaultConfiguration();
            var peer   = new RTCPeerConnection(ref config);

            var config2 = peer.GetConfiguration();

            Assert.NotNull(config.iceServers);
            Assert.NotNull(config2.iceServers);
            Assert.AreEqual(config.iceServers.Length, config2.iceServers.Length);
            Assert.AreEqual(config.iceServers[0].username, config2.iceServers[0].username);
            Assert.AreEqual(config.iceServers[0].credential, config2.iceServers[0].credential);
            Assert.AreEqual(config.iceServers[0].urls, config2.iceServers[0].urls);
            Assert.AreEqual(config.iceTransportPolicy, config2.iceTransportPolicy);
            Assert.AreEqual(config.iceCandidatePoolSize, config2.iceCandidatePoolSize);
            Assert.AreEqual(config.bundlePolicy, config2.bundlePolicy);

            peer.Close();
            peer.Dispose();
        }
Пример #10
0
    private void HangUp()
    {
        videoStream.Dispose();
        receiveStream.Dispose();
        videoStream   = null;
        receiveStream = null;

        _pc1.Close();
        _pc2.Close();
        Debug.Log("Close local/remote peer connection");
        _pc1.Dispose();
        _pc2.Dispose();
        _pc1 = null;
        _pc2 = null;
        receiveImage.texture            = null;
        callButton.interactable         = true;
        hangUpButton.interactable       = false;
        addTracksButton.interactable    = false;
        removeTracksButton.interactable = false;
    }
Пример #11
0
        private static Task <RTCPeerConnection> CreatePeerConnection()
        {
            RTCConfiguration config = new RTCConfiguration
            {
                iceServers = new List <RTCIceServer> {
                    new RTCIceServer {
                        urls = STUN_URL
                    }
                }
            };
            var pc = new RTCPeerConnection(config);

            var dc = pc.createDataChannel("test", null);

            dc.onopen    += () => logger.LogDebug($"Data channel {dc.label} opened.");
            dc.onclose   += () => logger.LogDebug($"Data channel {dc.label} closed.");
            dc.onmessage += async(msg) =>
            {
                logger.LogInformation($"Data channel message received: {msg}.");
                await dc.sendasync($"echo: {msg}");
            };

            pc.onconnectionstatechange += (state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
            };

            // Diagnostics.
            pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            pc.OnSendReport    += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");

            return(Task.FromResult(pc));
        }
Пример #12
0
        private void DoProcessMessage(Message message)
        {
            switch (message.type)
            {
            case "Offer":
                // var offer = JsonUtility.FromJson<RTCSessionDescriptionInit>(message.args);
                if (RTCSessionDescriptionInit.TryParse(message.args, out RTCSessionDescriptionInit offer))
                {
                    Debug.Log($"Got remote SDP, type {offer.type}");

                    var result = rtcPeerConnection.setRemoteDescription(offer);
                    if (result != SetDescriptionResultEnum.OK)
                    {
                        Debug.Log($"Failed to set remote description, {result}.");
                        rtcPeerConnection.Close("Failed to set remote description");
                    }
                    else
                    {
                        if (rtcPeerConnection.signalingState == RTCSignalingState.have_remote_offer)
                        {
                            var answerSdp = rtcPeerConnection.createAnswer();
                            rtcPeerConnection.setLocalDescription(answerSdp);

                            Debug.Log($"Sending SDP answer");

                            Send("Offer", answerSdp.toJSON());
                        }
                    }
                }
                break;

            case "IceCandidate":
                if (RTCIceCandidateInit.TryParse(message.args, out RTCIceCandidateInit candidate))
                {
                    Debug.Log($"Got remote Ice Candidate, uri {candidate.candidate}");
                    rtcPeerConnection.addIceCandidate(candidate);
                }
                break;
            }
        }
Пример #13
0
        public IEnumerator SetRemoteDescriptionFailed()
        {
            var config = GetConfiguration();
            var peer1  = new RTCPeerConnection(ref config);
            var peer2  = new RTCPeerConnection(ref config);

            var stream = new MediaStream();
            var track  = new AudioStreamTrack("audio");
            var sender = peer1.AddTrack(track, stream);

            RTCOfferOptions options1 = default;
            var             op1      = peer1.CreateOffer(ref options1);

            yield return(op1);

            var desc = op1.Desc;
            var op2  = peer1.SetLocalDescription(ref desc);

            yield return(op2);

            // change sdp to cannot parse
            desc.sdp = desc.sdp.Replace("m=audio", "m=audiable");
            var op3 = peer2.SetRemoteDescription(ref desc);

            yield return(op3);

            Assert.True(op3.IsDone);
            Assert.True(op3.IsError);
            Assert.IsNotEmpty(op3.Error.message);

            peer1.RemoveTrack(sender);
            track.Dispose();
            stream.Dispose();
            peer1.Close();
            peer2.Close();
            peer1.Dispose();
            peer2.Dispose();
        }
Пример #14
0
        public void GetConfiguration()
        {
            var config = GetDefaultConfiguration();
            var peer   = new RTCPeerConnection(ref config);

            var config2 = peer.GetConfiguration();

            Assert.That(config.iceServers, Is.Not.Null);
            Assert.That(config2.iceServers, Is.Not.Null);
            Assert.That(config.iceServers.Length, Is.EqualTo(config2.iceServers.Length));
            Assert.That(config.iceServers[0].username, Is.EqualTo(config2.iceServers[0].username));
            Assert.That(config.iceServers[0].credential, Is.EqualTo(config2.iceServers[0].credential));
            Assert.That(config.iceServers[0].urls, Is.EqualTo(config2.iceServers[0].urls));
            Assert.That(config.iceTransportPolicy, Is.EqualTo(RTCIceTransportPolicy.All));
            Assert.That(config.iceTransportPolicy, Is.EqualTo(config2.iceTransportPolicy));
            Assert.That(config.enableDtlsSrtp, Is.Null);
            Assert.That(config.enableDtlsSrtp, Is.EqualTo(config2.enableDtlsSrtp));
            Assert.That(config.iceCandidatePoolSize, Is.EqualTo(config2.iceCandidatePoolSize));
            Assert.That(config.bundlePolicy, Is.EqualTo(config2.bundlePolicy));

            peer.Close();
            peer.Dispose();
        }
        public void CreateDataChannelFailed()
        {
            RTCConfiguration config = default;

            config.iceServers = new[] { new RTCIceServer {
                                            urls = new[] { "stun:stun.l.google.com:19302" }
                                        } };
            var peer = new RTCPeerConnection(ref config);

            // Cannot be set along with "maxRetransmits" and "maxPacketLifeTime"
            var options = new RTCDataChannelInit
            {
                id                = 231,
                maxRetransmits    = 1,
                maxPacketLifeTime = 1,
                negotiated        = false,
                ordered           = false,
                protocol          = ""
            };

            Assert.Throws <ArgumentException>(() => peer.CreateDataChannel("test1", options));
            peer.Close();
        }
Пример #16
0
        RTCPeerConnection CreatePeerConnection(ISignaling signaling, string connectionId, bool isOffer)
        {
            if (m_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
            {
                peer.Close();
            }

            var pc = new RTCPeerConnection();

            m_mapConnectionIdAndPeer[connectionId] = pc;

            pc.OnDataChannel = new DelegateOnDataChannel(channel => { OnDataChannel(pc, channel); });
            pc.SetConfiguration(ref m_conf);
            pc.OnIceCandidate = new DelegateOnIceCandidate(candidate =>
            {
                signaling.SendCandidate(connectionId, candidate);
            });
            pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state =>
            {
                if (state == RTCIceConnectionState.Disconnected)
                {
                    pc.Close();
                    m_mapConnectionIdAndPeer.Remove(connectionId);
                }
            });

            pc.OnTrack = trackEvent =>
            {
                foreach (var viewer in m_listVideoReceiveViewer)
                {
                    viewer.AddTrack(connectionId, trackEvent.Track);
                }
            };

            pc.OnNegotiationNeeded = () => StartCoroutine(OnNegotiationNeeded(signaling, connectionId, isOffer));
            return(pc);
        }
Пример #17
0
        public IEnumerator CreateAnswer()
        {
            var config = GetConfiguration();

            var peer1 = new RTCPeerConnection(ref config);
            var peer2 = new RTCPeerConnection(ref config);
            var conf  = new RTCDataChannelInit(true);

            peer1.CreateDataChannel("data", ref conf);

            RTCOfferOptions  options1 = default;
            RTCAnswerOptions options2 = default;
            var op1 = peer1.CreateOffer(ref options1);

            yield return(op1);

            var desc = op1.Desc;
            var op2  = peer1.SetLocalDescription(ref desc);

            yield return(op2);

            var op3 = peer2.SetRemoteDescription(ref desc);

            yield return(op3);

            var op4 = peer2.CreateAnswer(ref options2);

            yield return(op4);

            Assert.True(op4.IsDone);
            Assert.False(op4.IsError);

            peer1.Close();
            peer2.Close();
            peer1.Dispose();
            peer2.Dispose();
        }
Пример #18
0
        public IEnumerator SetLocalDescriptionFailed()
        {
            var peer   = new RTCPeerConnection();
            var stream = new MediaStream();
            var obj    = new GameObject("audio");
            var source = obj.AddComponent <AudioSource>();

            source.clip = AudioClip.Create("test", 480, 2, 48000, false);
            var track  = new AudioStreamTrack(source);
            var sender = peer.AddTrack(track, stream);

            var op = peer.CreateOffer();

            yield return(op);

            Assert.True(op.IsDone);
            Assert.False(op.IsError);
            var desc = op.Desc;

            // change sdp to cannot parse
            desc.sdp = desc.sdp.Replace("a=mid:0", "a=mid:10");
            var op2 = peer.SetLocalDescription(ref desc);

            yield return(op2);

            Assert.True(op2.IsDone);
            Assert.True(op2.IsError);
            Assert.IsNotEmpty(op2.Error.message);
            Assert.That(peer.RemoveTrack(sender), Is.EqualTo(RTCErrorType.None));
            track.Dispose();
            stream.Dispose();
            peer.Close();
            peer.Dispose();
            Object.DestroyImmediate(source.clip);
            Object.DestroyImmediate(obj);
        }
Пример #19
0
 protected override void OnClose(CloseEventArgs e)
 {
     base.OnClose(e);
     PeerConnection.Close("remote party close");
 }
Пример #20
0
        private RTCPeerConnection Createpc()
        {
            List <RTCCertificate> presetCertificates = null;

            if (File.Exists(LOCALHOST_CERTIFICATE_PATH))
            {
                var localhostCert = new X509Certificate2(LOCALHOST_CERTIFICATE_PATH, (string)null, X509KeyStorageFlags.Exportable);
                presetCertificates = new List <RTCCertificate> {
                    new RTCCertificate {
                        Certificate = localhostCert
                    }
                };
            }

            RTCConfiguration pcConfiguration = new RTCConfiguration
            {
                certificates = presetCertificates,
            };

            var pc = new RTCPeerConnection(pcConfiguration);

            pc.GetRtpChannel().MdnsResolve = MdnsResolve;
            //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isrelay) => logger.LogDebug($"{_peerName}: STUN message received from {ep}, message class {msg.Header.MessageClass}.");

            var dataChannel = pc.createDataChannel(_dataChannelLabel, null);

            dataChannel.onDatamessage -= DataChannel_onDatamessage;
            dataChannel.onDatamessage += DataChannel_onDatamessage;
            _dataChannels.Add(_dataChannelLabel, dataChannel);

            pc.onicecandidateerror        += (candidate, error) => logger.LogWarning($"{_peerName}: Error adding remote ICE candidate. {error} {candidate}");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"{_peerName}: ICE connection state change to {state}.");
            pc.onconnectionstatechange    += (state) =>
            {
                logger.LogDebug($"{_peerName}: Peer connection state changed to {state}.");

                if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed)
                {
                    pc.Close("remote disconnection");
                }
            };

            pc.onicecandidate += (candidate) =>
            {
                if (pc.signalingState == RTCSignalingState.have_local_offer ||
                    pc.signalingState == RTCSignalingState.have_remote_offer)
                {
                    OnIceCandidateAvailable?.Invoke(new RTCIceCandidateInit()
                    {
                        candidate     = candidate.ToString(),
                        sdpMid        = candidate.sdpMid,
                        sdpMLineIndex = candidate.sdpMLineIndex
                    });
                }
            };

            pc.ondatachannel += (dc) =>
            {
                dc.onopen        += () => logger.LogDebug($"{_peerName}: Data channel now open label {dc.label}, stream ID {dc.id}.");
                dc.onDatamessage -= DataChannel_onDatamessage;
                dc.onDatamessage += DataChannel_onDatamessage;
                logger.LogDebug($"{_peerName}: Data channel created by remote peer, label {dc.label}, stream ID {dc.id}.");
                _dataChannels.Add(dc.label, dc);
            };

            return(pc);
        }
Пример #21
0
        private static async Task <RTCPeerConnection> CreatePeerConnection()
        {
            RTCConfiguration config = new RTCConfiguration
            {
                iceServers = new List <RTCIceServer> {
                    new RTCIceServer {
                        urls = STUN_URL
                    }
                }
            };
            var pc = new RTCPeerConnection(config);

            WindowsVideoEndPoint winVideoEP = new WindowsVideoEndPoint(new VpxVideoEncoder(), WEBCAM_NAME);
            //WindowsVideoEndPoint winVideoEP = new WindowsVideoEndPoint(new FFmpegVideoEncoder(), WEBCAM_NAME);
            //WindowsVideoEndPoint winVideoEP = new WindowsVideoEndPoint(WEBCAM_NAME, 1920, 1080, 30);
            //winVideoEP.RestrictFormats(x => x.Codec == SIPSorceryMedia.Abstractions.V1.VideoCodecsEnum.H264);
            bool initResult = await winVideoEP.InitialiseVideoSourceDevice();

            if (!initResult)
            {
                throw new ApplicationException("Could not initialise video capture device.");
            }
            var audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions {
                AudioSource = AudioSourcesEnum.Music
            });

            MediaStreamTrack videoTrack = new MediaStreamTrack(winVideoEP.GetVideoSourceFormats(), MediaStreamStatusEnum.SendRecv);

            pc.addTrack(videoTrack);
            MediaStreamTrack audioTrack = new MediaStreamTrack(audioSource.GetAudioSourceFormats(), MediaStreamStatusEnum.SendRecv);

            pc.addTrack(audioTrack);

            winVideoEP.OnVideoSourceEncodedSample  += pc.SendVideo;
            audioSource.OnAudioSourceEncodedSample += pc.SendAudio;
            pc.OnVideoFormatsNegotiated            += (videoFormats) =>
                                                      winVideoEP.SetVideoSourceFormat(videoFormats.First());
            pc.OnAudioFormatsNegotiated += (audioFormats) =>
                                           audioSource.SetAudioSourceFormat(audioFormats.First());

            pc.onconnectionstatechange += async(state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.connected)
                {
                    await audioSource.StartAudio();

                    await winVideoEP.StartVideo();
                }
                else if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
                else if (state == RTCPeerConnectionState.closed)
                {
                    await winVideoEP.CloseVideo();

                    await audioSource.CloseAudio();
                }
            };

            // Diagnostics.
            pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            pc.OnSendReport    += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");

            return(pc);
        }
Пример #22
0
        public async Task <RTCSessionDescriptionInit> GotOffer(RTCSessionDescriptionInit offer)
        {
            _logger.LogDebug($"SDP offer received.");
            _logger.LogTrace($"Offer SDP:\n{offer.sdp}");

            var pc = new RTCPeerConnection();

            if (_publicIPv4 != null)
            {
                var rtpPort             = pc.GetRtpChannel().RTPPort;
                var publicIPv4Candidate = new RTCIceCandidate(RTCIceProtocol.udp, _publicIPv4, (ushort)rtpPort, RTCIceCandidateType.host);
                pc.addLocalIceCandidate(publicIPv4Candidate);
            }

            if (_publicIPv6 != null)
            {
                var rtpPort             = pc.GetRtpChannel().RTPPort;
                var publicIPv6Candidate = new RTCIceCandidate(RTCIceProtocol.udp, _publicIPv6, (ushort)rtpPort, RTCIceCandidateType.host);
                pc.addLocalIceCandidate(publicIPv6Candidate);
            }

            MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false,
                                                               new List <SDPAudioVideoMediaFormat> {
                new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU)
            }, MediaStreamStatusEnum.SendRecv);

            pc.addTrack(audioTrack);
            MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(VideoCodecsEnum.VP8, VP8_PAYLOAD_ID), MediaStreamStatusEnum.SendRecv);

            pc.addTrack(videoTrack);

            pc.OnRtpPacketReceived += (IPEndPoint rep, SDPMediaTypesEnum media, RTPPacket rtpPkt) =>
            {
                pc.SendRtpRaw(media, rtpPkt.Payload, rtpPkt.Header.Timestamp, rtpPkt.Header.MarkerBit, rtpPkt.Header.PayloadType);
                //_logger.LogDebug($"RTP {media} pkt received, SSRC {rtpPkt.Header.SyncSource}, SeqNum {rtpPkt.Header.SequenceNumber}.");
            };
            //peerConnection.OnReceiveReport += RtpSession_OnReceiveReport;
            //peerConnection.OnSendReport += RtpSession_OnSendReport;

            pc.OnTimeout += (mediaType) => _logger.LogWarning($"Timeout for {mediaType}.");
            pc.onconnectionstatechange += (state) =>
            {
                _logger.LogDebug($"Peer connection state changed to {state}.");

                if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice failure");
                }
            };

            var setResult = pc.setRemoteDescription(offer);

            if (setResult == SetDescriptionResultEnum.OK)
            {
                var offerSdp = pc.createOffer(null);
                await pc.setLocalDescription(offerSdp);

                var answer = pc.createAnswer(null);
                await pc.setLocalDescription(answer);

                _logger.LogTrace($"Answer SDP:\n{answer.sdp}");

                return(answer);
            }
            else
            {
                return(null);
            }
        }
Пример #23
0
        private static RTCPeerConnection CreatePeerConnection()
        {
            RTCConfiguration config = new RTCConfiguration
            {
                iceServers = new List <RTCIceServer> {
                    new RTCIceServer {
                        urls = STUN_URL
                    }
                }
            };
            var pc = new RTCPeerConnection(config);

            var testPatternSource = new VideoTestPatternSource();
            WindowsVideoEndPoint windowsVideoEndPoint = new WindowsVideoEndPoint(true);
            var audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions {
                AudioSource = AudioSourcesEnum.Music
            });

            MediaStreamTrack videoTrack = new MediaStreamTrack(windowsVideoEndPoint.GetVideoSourceFormats(), MediaStreamStatusEnum.SendRecv);

            pc.addTrack(videoTrack);
            MediaStreamTrack audioTrack = new MediaStreamTrack(audioSource.GetAudioSourceFormats(), MediaStreamStatusEnum.SendRecv);

            pc.addTrack(audioTrack);

            testPatternSource.OnVideoSourceRawSample        += windowsVideoEndPoint.ExternalVideoSourceRawSample;
            windowsVideoEndPoint.OnVideoSourceEncodedSample += pc.SendVideo;
            audioSource.OnAudioSourceEncodedSample          += pc.SendAudio;
            pc.OnVideoFormatsNegotiated += (sdpFormat) =>
                                           windowsVideoEndPoint.SetVideoSourceFormat(SDPMediaFormatInfo.GetVideoCodecForSdpFormat(sdpFormat.First().FormatCodec));
            pc.onconnectionstatechange += async(state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.connected)
                {
                    await audioSource.StartAudio();

                    await windowsVideoEndPoint.StartVideo();

                    await testPatternSource.StartVideo();
                }
                else if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
                else if (state == RTCPeerConnectionState.closed)
                {
                    await testPatternSource.CloseVideo();

                    await windowsVideoEndPoint.CloseVideo();

                    await audioSource.CloseAudio();
                }
            };

            // Diagnostics.
            pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            pc.OnSendReport    += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");

            return(pc);
        }
Пример #24
0
        static async Task Main()
        {
            Console.WriteLine("SIPSorcery sip.js Demo");

            AddConsoleLogger();

            var sipTransport = new SIPTransport();

            EnableTraceLogs(sipTransport);

            var sipChannel = new SIPWebSocketChannel(IPAddress.Loopback, 80);

            var wssCertificate   = new System.Security.Cryptography.X509Certificates.X509Certificate2("localhost.pfx");
            var sipChannelSecure = new SIPWebSocketChannel(IPAddress.Loopback, 443, wssCertificate);

            sipTransport.AddSIPChannel(sipChannel);
            sipTransport.AddSIPChannel(sipChannelSecure);

            var userAgent = new SIPUserAgent(sipTransport, null, true);

            userAgent.OnIncomingCall += async(ua, req) =>
            {
                Log.LogDebug($"Auto-answering incoming call from {req.Header.From}.");
                var uas = userAgent.AcceptCall(req);

                RTCConfiguration pcConfiguration = new RTCConfiguration
                {
                    certificates = new List <RTCCertificate>
                    {
                        new RTCCertificate
                        {
                            X_CertificatePath = DTLS_CERTIFICATE_PATH,
                            X_KeyPath         = DTLS_KEY_PATH,
                            X_Fingerprint     = DTLS_CERTIFICATE_FINGERPRINT
                        }
                    },
                    //X_RemoteSignallingAddress = context.UserEndPoint.Address,
                    //iceServers = new List<RTCIceServer> { new RTCIceServer { urls = SIPSORCERY_STUN_SERVER } }
                };

                var peerConnection = new RTCPeerConnection(pcConfiguration);
                var dtls           = new DtlsHandshake(DTLS_CERTIFICATE_PATH, DTLS_KEY_PATH);

                peerConnection.OnTimeout += (mediaType) =>
                {
                    peerConnection.Close("remote timeout");
                };

                peerConnection.oniceconnectionstatechange += async(state) =>
                {
                    Log.LogDebug($"ICE connection state change to {state}.");

                    if (state == RTCIceConnectionState.connected)
                    {
                        var remoteEndPoint = peerConnection.AudioDestinationEndPoint;
                        Log.LogInformation($"ICE connected to remote end point {remoteEndPoint}.");

                        await Task.Run(() => DoDtlsHandshake(peerConnection, dtls))
                        .ContinueWith((dtlsResult) =>
                        {
                            Log.LogDebug($"dtls handshake result {dtlsResult.Result}.");

                            if (dtlsResult.Result)
                            {
                                var remoteEP = peerConnection.AudioDestinationEndPoint;
                                peerConnection.SetDestination(SDPMediaTypesEnum.audio, remoteEP, remoteEP);
                            }
                            else
                            {
                                dtls.Shutdown();
                                peerConnection.Close("dtls handshake failed.");
                            }
                        });
                    }
                };

                peerConnection.onconnectionstatechange += (state) =>
                {
                    if (state == RTCPeerConnectionState.connected)
                    {
                        var remoteEP = peerConnection.AudioDestinationEndPoint;

                        Log.LogDebug($"DTLS connected on {remoteEP}.");

                        peerConnection.SetDestination(SDPMediaTypesEnum.audio, remoteEP, remoteEP);
                        peerConnection.SetDestination(SDPMediaTypesEnum.video, remoteEP, remoteEP);

                        peerConnection.OnReceiveReport += RtpSession_OnReceiveReport;
                        peerConnection.OnSendReport    += RtpSession_OnSendReport;
                        // peerConnection.OnRtpPacketReceived += OnRtpPacketReceived;
                    }
                };

                MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPMediaFormat> {
                    new SDPMediaFormat(SDPMediaFormatsEnum.PCMU)
                }, MediaStreamStatusEnum.SendRecv);
                peerConnection.addTrack(audioTrack);
                //MediaStreamTrack videoTrack = new MediaStreamTrack("1", SDPMediaTypesEnum.video, false, new List<SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }, MediaStreamStatusEnum.Inactive);
                //peerConnection.addTrack(videoTrack);

                var answerResult = await userAgent.Answer(uas, peerConnection);
            };

            Console.Write("press any key to exit...");
            Console.Read();

            sipTransport.Shutdown();
        }
Пример #25
0
        public IEnumerator IceConnectionStateChange()
        {
            RTCConfiguration config = default;

            config.iceServers = new[] { new RTCIceServer {
                                            urls = new[] { "stun:stun.l.google.com:19302" }
                                        } };
            var peer1 = new RTCPeerConnection(ref config);
            var peer2 = new RTCPeerConnection(ref config);

            peer1.OnIceCandidate = candidate => { peer2.AddIceCandidate(ref candidate); };
            peer2.OnIceCandidate = candidate => { peer1.AddIceCandidate(ref candidate); };

            MediaStream stream = Audio.CaptureStream();

            peer1.AddTrack(stream.GetTracks().First());

            RTCOfferOptions  options1 = default;
            RTCAnswerOptions options2 = default;
            var op1 = peer1.CreateOffer(ref options1);

            yield return(op1);

            var desc = op1.Desc;
            var op2  = peer1.SetLocalDescription(ref desc);

            yield return(op2);

            var op3 = peer2.SetRemoteDescription(ref desc);

            yield return(op3);

            var op4 = peer2.CreateAnswer(ref options2);

            yield return(op4);

            desc = op4.Desc;
            var op5 = peer2.SetLocalDescription(ref desc);

            yield return(op5);

            var op6 = peer1.SetRemoteDescription(ref desc);

            yield return(op6);

            var op7 = new WaitUntilWithTimeout(
                () => peer1.IceConnectionState == RTCIceConnectionState.Connected ||
                peer1.IceConnectionState == RTCIceConnectionState.Completed, 5000);

            yield return(op7);

            Assert.True(op7.IsCompleted);
            var op8 = new WaitUntilWithTimeout(
                () => peer2.IceConnectionState == RTCIceConnectionState.Connected ||
                peer2.IceConnectionState == RTCIceConnectionState.Completed, 5000);

            yield return(op8);

            Assert.True(op8.IsCompleted);

            stream.Dispose();
            peer1.Close();
            peer2.Close();
        }
Пример #26
0
        void OnOffer(ISignaling signaling, DescData e)
        {
            RTCSessionDescription _desc;

            _desc.type = RTCSdpType.Offer;
            _desc.sdp  = e.sdp;
            var connectionId = e.connectionId;

            if (m_mapConnectionIdAndPeer.ContainsKey(connectionId))
            {
                return;
            }
            var pc = new RTCPeerConnection();

            m_mapConnectionIdAndPeer.Add(e.connectionId, pc);

            pc.OnDataChannel = new DelegateOnDataChannel(channel => { OnDataChannel(pc, channel); });
            pc.SetConfiguration(ref m_conf);
            pc.OnIceCandidate = new DelegateOnIceCandidate(candidate =>
            {
                signaling.SendCandidate(e.connectionId, candidate);
            });
            pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state =>
            {
                if (state == RTCIceConnectionState.Disconnected)
                {
                    pc.Close();
                    m_mapConnectionIdAndPeer.Remove(e.connectionId);
                }
            });
            //make video bit rate starts at 16000kbits, and 160000kbits at max.
            string pattern = @"(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n";

            _desc.sdp = Regex.Replace(_desc.sdp, pattern, "$1;x-google-start-bitrate=16000;x-google-max-bitrate=160000\r\n");
            pc.SetRemoteDescription(ref _desc);
            foreach (var track in m_listVideoStreamTrack)
            {
                pc.AddTrack(track);
            }
            foreach (var track in m_audioStream.GetTracks())
            {
                pc.AddTrack(track);
            }

            RTCAnswerOptions options = default;
            var op = pc.CreateAnswer(ref options);

            while (op.MoveNext())
            {
            }
            if (op.IsError)
            {
                Debug.LogError($"Network Error: {op.Error}");
                return;
            }

            var desc        = op.Desc;
            var opLocalDesc = pc.SetLocalDescription(ref desc);

            while (opLocalDesc.MoveNext())
            {
            }
            if (opLocalDesc.IsError)
            {
                Debug.LogError($"Network Error: {opLocalDesc.Error}");
                return;
            }

            signaling.SendAnswer(connectionId, desc);
        }
Пример #27
0
        private static Task <RTCPeerConnection> CreatePeerConnection()
        {
            //var videoEP = new SIPSorceryMedia.Windows.WindowsVideoEndPoint(new VpxVideoEncoder());
            //videoEP.RestrictFormats(format => format.Codec == VideoCodecsEnum.VP8);
            var videoEP = new SIPSorceryMedia.Windows.WindowsVideoEndPoint(new FFmpegVideoEncoder());

            videoEP.RestrictFormats(format => format.Codec == VideoCodecsEnum.H264);

            videoEP.OnVideoSinkDecodedSample += (byte[] bmp, uint width, uint height, int stride, VideoPixelFormatsEnum pixelFormat) =>
            {
                _form.BeginInvoke(new Action(() =>
                {
                    unsafe
                    {
                        if (_picBox.Width != (int)width || _picBox.Height != (int)height)
                        {
                            logger.LogDebug($"Adjusting video display from {_picBox.Width}x{_picBox.Height} to {width}x{height}.");
                            _picBox.Width  = (int)width;
                            _picBox.Height = (int)height;
                        }

                        fixed(byte *s = bmp)
                        {
                            Bitmap bmpImage = new Bitmap((int)width, (int)height, (int)(bmp.Length / height), PixelFormat.Format24bppRgb, (IntPtr)s);
                            _picBox.Image   = bmpImage;
                        }
                    }
                }));
            };

            RTCConfiguration config = new RTCConfiguration
            {
                //iceServers = new List<RTCIceServer> { new RTCIceServer { urls = STUN_URL } }
                X_UseRtpFeedbackProfile = true
            };
            var pc = new RTCPeerConnection(config);

            // Add local receive only tracks. This ensures that the SDP answer includes only the codecs we support.
            if (!_options.NoAudio)
            {
                MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false,
                                                                   new List <SDPAudioVideoMediaFormat> {
                    new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU)
                }, MediaStreamStatusEnum.RecvOnly);
                pc.addTrack(audioTrack);
            }
            MediaStreamTrack videoTrack = new MediaStreamTrack(videoEP.GetVideoSinkFormats(), MediaStreamStatusEnum.RecvOnly);

            //MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(96, "VP8", 90000, "x-google-max-bitrate=5000000"), MediaStreamStatusEnum.RecvOnly);
            pc.addTrack(videoTrack);

            pc.OnVideoFrameReceived     += videoEP.GotVideoFrame;
            pc.OnVideoFormatsNegotiated += (formats) => videoEP.SetVideoSinkFormat(formats.First());

            pc.onconnectionstatechange += async(state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
                else if (state == RTCPeerConnectionState.closed)
                {
                    await videoEP.CloseVideo();
                }
            };

            // Diagnostics.
            //pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"RECV STUN {msg.Header.MessageType} (txid: {msg.Header.TransactionId.HexStr()}) from {ep}.");
            //pc.GetRtpChannel().OnStunMessageSent += (msg, ep, isRelay) => logger.LogDebug($"SEND STUN {msg.Header.MessageType} (txid: {msg.Header.TransactionId.HexStr()}) to {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");

            return(Task.FromResult(pc));
        }
Пример #28
0
        private static Task <RTCPeerConnection> CreatePeerConnection(X509Certificate2 cert)
        {
            //RTCConfiguration config = new RTCConfiguration
            //{
            //    iceServers = new List<RTCIceServer> { new RTCIceServer { urls = STUN_URL } },
            //    certificates = new List<RTCCertificate> { new RTCCertificate { Certificate = cert } }
            //};
            //var pc = new RTCPeerConnection(config);
            var pc = new RTCPeerConnection(null);

            //var testPatternSource = new VideoTestPatternSource(new SIPSorceryMedia.Encoders.VideoEncoder());
            var testPatternSource = new VideoTestPatternSource(new FFmpegVideoEncoder());

            testPatternSource.SetFrameRate(60);
            //testPatternSource.SetMaxFrameRate(true);
            //var videoEndPoint = new SIPSorceryMedia.FFmpeg.FFmpegVideoEndPoint();
            //videoEndPoint.RestrictFormats(format => format.Codec == VideoCodecsEnum.H264);
            //testPatternSource.RestrictFormats(format => format.Codec == VideoCodecsEnum.H264);
            //var videoEndPoint = new SIPSorceryMedia.Windows.WindowsEncoderEndPoint();
            //var videoEndPoint = new SIPSorceryMedia.Encoders.VideoEncoderEndPoint();

            MediaStreamTrack track = new MediaStreamTrack(testPatternSource.GetVideoSourceFormats(), MediaStreamStatusEnum.SendOnly);

            pc.addTrack(track);

            //testPatternSource.OnVideoSourceRawSample += videoEndPoint.ExternalVideoSourceRawSample;
            testPatternSource.OnVideoSourceRawSample     += MesasureTestPatternSourceFrameRate;
            testPatternSource.OnVideoSourceEncodedSample += pc.SendVideo;
            pc.OnVideoFormatsNegotiated += (formats) => testPatternSource.SetVideoSourceFormat(formats.First());

            pc.onconnectionstatechange += async(state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
                else if (state == RTCPeerConnectionState.closed)
                {
                    await testPatternSource.CloseVideo();

                    await testPatternSource.CloseVideo();

                    testPatternSource.Dispose();
                }
                else if (state == RTCPeerConnectionState.connected)
                {
                    await testPatternSource.StartVideo();

                    await testPatternSource.StartVideo();
                }
            };

            // Diagnostics.
            //pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            //pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");
            pc.onsignalingstatechange     += () =>
            {
                if (pc.signalingState == RTCSignalingState.have_remote_offer)
                {
                    logger.LogDebug(pc.RemoteDescription.ToString());
                }
            };

            return(Task.FromResult(pc));
        }
Пример #29
0
        public async Task <RTCSessionDescriptionInit> GetOffer(string id)
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentNullException("id", "A unique ID parameter must be supplied when creating a new peer connection.");
            }
            else if (_peerConnections.ContainsKey(id))
            {
                throw new ArgumentNullException("id", "The specified peer connection ID is already in use.");
            }

            RTCConfiguration pcConfiguration = new RTCConfiguration
            {
                certificates = new List <RTCCertificate>
                {
                    new RTCCertificate
                    {
                        X_CertificatePath = DTLS_CERTIFICATE_PATH,
                        X_KeyPath         = DTLS_KEY_PATH,
                        X_Fingerprint     = DTLS_CERTIFICATE_FINGERPRINT
                    }
                }
            };

            var peerConnection = new RTCPeerConnection(pcConfiguration);
            var dtls           = new DtlsHandshake(DTLS_CERTIFICATE_PATH, DTLS_KEY_PATH);

            MediaStreamTrack audioTrack = new MediaStreamTrack("0", SDPMediaTypesEnum.audio, false, new List <SDPMediaFormat> {
                new SDPMediaFormat(SDPMediaFormatsEnum.PCMU)
            }, MediaStreamStatusEnum.RecvOnly);

            peerConnection.addTrack(audioTrack);

            //peerConnection.OnRtpPacketReceived += (SDPMediaTypesEnum media, RTPPacket rtpPkt) => _logger.LogDebug($"RTP {media} pkt received, SSRC {rtpPkt.Header.SyncSource}, SeqNum {rtpPkt.Header.SequenceNumber}.");
            //peerConnection.OnReceiveReport += RtpSession_OnReceiveReport;
            //peerConnection.OnSendReport += RtpSession_OnSendReport;

            peerConnection.OnTimeout += (mediaType) =>
            {
                peerConnection.Close("remote timeout");
                dtls.Shutdown();
            };

            peerConnection.onconnectionstatechange += (state) =>
            {
                if (state == RTCPeerConnectionState.closed || state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed)
                {
                    //peerConnection.OnReceiveReport -= RtpSession_OnReceiveReport;
                    //peerConnection.OnSendReport -= RtpSession_OnSendReport;
                    _logger.LogDebug($"Peer connection {id} closed.");
                    _peerConnections.TryRemove(id, out _);
                }
                else if (state == RTCPeerConnectionState.connected)
                {
                    _logger.LogDebug("Peer connection connected.");
                }
            };

            _ = Task <bool> .Run(() => Task.FromResult(DoDtlsHandshake(peerConnection, dtls)))
                .ContinueWith((t) =>
            {
                _logger.LogDebug($"dtls handshake result {t.Result}.");

                if (t.Result)
                {
                    var remoteEP = peerConnection.IceSession.ConnectedRemoteEndPoint;
                    peerConnection.SetDestination(SDPMediaTypesEnum.audio, remoteEP, remoteEP);
                }
                else
                {
                    dtls.Shutdown();
                    peerConnection.Close("dtls handshake failed.");
                }
            });

            var offerSdp = await peerConnection.createOffer(null);

            await peerConnection.setLocalDescription(offerSdp);

            _peerConnections.TryAdd(id, peerConnection);

            return(offerSdp);
        }
Пример #30
0
        void OnOffer(ISignaling signaling, DescData e)
        {
            RTCSessionDescription _desc;

            _desc.type = RTCSdpType.Offer;
            _desc.sdp  = e.sdp;
            var connectionId = e.connectionId;

            if (m_mapConnectionIdAndPeer.ContainsKey(connectionId))
            {
                return;
            }
            var pc = new RTCPeerConnection();

            m_mapConnectionIdAndPeer.Add(e.connectionId, pc);

            pc.OnDataChannel = new DelegateOnDataChannel(channel => { OnDataChannel(pc, channel); });
            pc.SetConfiguration(ref m_conf);
            pc.OnIceCandidate = new DelegateOnIceCandidate(candidate =>
            {
                signaling.SendCandidate(e.connectionId, candidate);
            });
            pc.OnIceConnectionChange = new DelegateOnIceConnectionChange(state =>
            {
                if (state == RTCIceConnectionState.Disconnected)
                {
                    pc.Close();
                    m_mapConnectionIdAndPeer.Remove(e.connectionId);
                }
            });

            pc.SetRemoteDescription(ref _desc);
            foreach (var track in m_listVideoStreamTrack.Concat(m_audioStream.GetTracks()))
            {
                RTCRtpSender sender = pc.AddTrack(track);
                if (!m_mapTrackAndSenderList.TryGetValue(track, out List <RTCRtpSender> list))
                {
                    list = new List <RTCRtpSender>();
                    m_mapTrackAndSenderList.Add(track, list);
                }
                list.Add(sender);
            }

            RTCAnswerOptions options = default;
            var op = pc.CreateAnswer(ref options);

            while (op.MoveNext())
            {
            }
            if (op.IsError)
            {
                Debug.LogError($"Network Error: {op.Error}");
                return;
            }

            var desc        = op.Desc;
            var opLocalDesc = pc.SetLocalDescription(ref desc);

            while (opLocalDesc.MoveNext())
            {
            }
            if (opLocalDesc.IsError)
            {
                Debug.LogError($"Network Error: {opLocalDesc.Error}");
                return;
            }

            signaling.SendAnswer(connectionId, desc);
        }