private void Start_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine("Inside StartCall Button");

            string sIP = "192.168.57.121";

            //string sIP = "38.127.68.60";
            //string sIP = "60.68.127.38";
            int        iFriendPort = 60002;
            int        iRet        = 0;
            P2PWrapper p2pWrapper  = P2PWrapper.GetInstance();

            iRet = p2pWrapper.InitializeLibraryR(100 /*UserID*/);
            System.Console.WriteLine("MediaEngineLib==> InitializeLibrary, iRet = " + iRet);
            p2pWrapper.CreateSessionR(200 /*FriendID*/, 1 /*Audio*/, sIP, iFriendPort);
            p2pWrapper.CreateSessionR(200, 2 /*Video*/, sIP, iFriendPort);
            p2pWrapper.SetRelayServerInformationR(200, 1, sIP, iFriendPort);
            p2pWrapper.SetRelayServerInformationR(200, 2, sIP, iFriendPort);
            iRet = p2pWrapper.StartAudioCallR(200);
            iRet = p2pWrapper.StartVideoCallR(200, 288 /*Height*/, 352 /*Width*/);
            System.Diagnostics.Debug.WriteLine("MediaEngineLib==> StartVideoCall, iRet = " + iRet);
            p2pWrapper.SetLoggingStateR(true, 5);
            p2pWrapper.LinkWithConnectivityLib(null);

            AudioSender oAlpha = new AudioSender();

            oAlpha.AudioData = System.IO.File.ReadAllBytes(@"AudioSending(1).pcm");
            //oAlpha.bStartSending = true;

            Thread oThread = new Thread(new ThreadStart(oAlpha.StartSendingAudio));

            oThread.Start();
        }
Beispiel #2
0
        private bool ProcessPacket(ReadOnlySpan <byte> data, ref Memory <byte> opus, ref Memory <byte> pcm, IList <ReadOnlyMemory <byte> > pcmPackets, out AudioSender voiceSender, out AudioFormat outputFormat)
        {
            voiceSender  = null;
            outputFormat = default;

            if (!this.Rtp.IsRtpHeader(data))
            {
                return(false);
            }

            this.Rtp.DecodeHeader(data, out var sequence, out var timestamp, out var ssrc, out var hasExtension);

            if (!this.TransmittingSSRCs.TryGetValue(ssrc, out var vtx))
            {
                var decoder = Opus.CreateDecoder();

                vtx = new AudioSender(ssrc, decoder)
                {
                    // user isn't present as we haven't received a speaking event yet.
                    User = null
                };
            }

            voiceSender = vtx;
            if (sequence <= vtx.LastSequence) // out-of-order packet; discard
            {
                return(false);
            }
            var gap = vtx.LastSequence != 0 ? sequence - 1 - vtx.LastSequence : 0;

            if (gap >= 5)
            {
                this.Discord.Logger.LogWarning(VoiceNextEvents.VoiceReceiveFailure, "5 or more voice packets were dropped when receiving");
            }

            Span <byte> nonce = stackalloc byte[Sodium.NonceSize];

            this.Sodium.GetNonce(data, nonce, this.SelectedEncryptionMode);
            this.Rtp.GetDataFromPacket(data, out var encryptedOpus, this.SelectedEncryptionMode);

            var opusSize = Sodium.CalculateSourceSize(encryptedOpus);

            opus = opus.Slice(0, opusSize);
            var opusSpan = opus.Span;

            try
            {
                this.Sodium.Decrypt(encryptedOpus, opusSpan, nonce);

                // Strip extensions, if any
                if (hasExtension)
                {
                    // RFC 5285, 4.2 One-Byte header
                    // http://www.rfcreader.com/#rfc5285_line186
                    if (opusSpan[0] == 0xBE && opusSpan[1] == 0xDE)
                    {
                        var headerLen = opusSpan[2] << 8 | opusSpan[3];
                        var i         = 4;
                        for (; i < headerLen + 4; i++)
                        {
                            var @byte = opusSpan[i];

                            // ID is currently unused since we skip it anyway
                            //var id = (byte)(@byte >> 4);
                            var length = (byte)(@byte & 0x0F) + 1;

                            i += length;
                        }

                        // Strip extension padding too
                        while (opusSpan[i] == 0)
                        {
                            i++;
                        }

                        opusSpan = opusSpan.Slice(i);
                    }

                    // TODO: consider implementing RFC 5285, 4.3. Two-Byte Header
                }

                if (opusSpan[0] == 0x90)
                {
                    // I'm not 100% sure what this header is/does, however removing the data causes no
                    // real issues, and has the added benefit of removing a lot of noise.
                    opusSpan = opusSpan.Slice(2);
                }

                if (gap == 1)
                {
                    var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder);
                    var fecpcm          = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
                    var fecpcmMem       = fecpcm.AsSpan();
                    this.Opus.Decode(vtx.Decoder, opusSpan, ref fecpcmMem, true, out _);
                    pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
                }
                else if (gap > 1)
                {
                    var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder);
                    for (var i = 0; i < gap; i++)
                    {
                        var fecpcm    = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
                        var fecpcmMem = fecpcm.AsSpan();
                        this.Opus.ProcessPacketLoss(vtx.Decoder, lastSampleCount, ref fecpcmMem);
                        pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
                    }
                }

                var pcmSpan = pcm.Span;
                this.Opus.Decode(vtx.Decoder, opusSpan, ref pcmSpan, false, out outputFormat);
                pcm = pcm.Slice(0, pcmSpan.Length);
            }
            finally
            {
                vtx.LastSequence = sequence;
            }

            return(true);
        }
Beispiel #3
0
        private async Task HandleDispatch(JObject jo)
        {
            var opc = (int)jo["op"];
            var opp = jo["d"] as JObject;

            switch (opc)
            {
            case 2:     // READY
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received READY (OP2)");
                var vrp = opp.ToObject <VoiceReadyPayload>();
                this.SSRC        = vrp.SSRC;
                this.UdpEndpoint = new ConnectionEndpoint(vrp.Address, vrp.Port);
                // this is not the valid interval
                // oh, discord
                //this.HeartbeatInterval = vrp.HeartbeatInterval;
                this.HeartbeatTask = Task.Run(this.HeartbeatAsync);
                await this.Stage1(vrp).ConfigureAwait(false);

                break;

            case 4:     // SESSION_DESCRIPTION
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SESSION_DESCRIPTION (OP4)");
                var vsd = opp.ToObject <VoiceSessionDescriptionPayload>();
                this.Key    = vsd.SecretKey;
                this.Sodium = new Sodium(this.Key.AsMemory());
                await this.Stage2(vsd).ConfigureAwait(false);

                break;

            case 5:     // SPEAKING
                // Don't spam OP5
                // No longer spam, Discord supposedly doesn't send many of these
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SPEAKING (OP5)");
                var spd = opp.ToObject <VoiceSpeakingPayload>();
                var foundUserInCache = this.Discord.TryGetCachedUserInternal(spd.UserId.Value, out var resolvedUser);
                var spk = new UserSpeakingEventArgs
                {
                    Speaking = spd.Speaking,
                    SSRC     = spd.SSRC.Value,
                    User     = resolvedUser,
                };

                if (foundUserInCache && this.TransmittingSSRCs.TryGetValue(spk.SSRC, out var txssrc5) && txssrc5.Id == 0)
                {
                    txssrc5.User = spk.User;
                }
                else
                {
                    var opus = this.Opus.CreateDecoder();
                    var vtx  = new AudioSender(spk.SSRC, opus)
                    {
                        User = await this.Discord.GetUserAsync(spd.UserId.Value).ConfigureAwait(false)
                    };

                    if (!this.TransmittingSSRCs.TryAdd(spk.SSRC, vtx))
                    {
                        this.Opus.DestroyDecoder(opus);
                    }
                }

                await this._userSpeaking.InvokeAsync(this, spk).ConfigureAwait(false);

                break;

            case 6:     // HEARTBEAT ACK
                var dt   = DateTime.Now;
                var ping = (int)(dt - this.LastHeartbeat).TotalMilliseconds;
                Volatile.Write(ref this._wsPing, ping);
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HEARTBEAT_ACK (OP6, {0}ms)", ping);
                this.LastHeartbeat = dt;
                break;

            case 8:     // HELLO
                // this sends a heartbeat interval that we need to use for heartbeating
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HELLO (OP8)");
                this.HeartbeatInterval = opp["heartbeat_interval"].ToObject <int>();
                break;

            case 9:     // RESUMED
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received RESUMED (OP9)");
                this.HeartbeatTask = Task.Run(this.HeartbeatAsync);
                break;

            case 12:     // CLIENT_CONNECTED
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_CONNECTED (OP12)");
                var ujpd = opp.ToObject <VoiceUserJoinPayload>();
                var usrj = await this.Discord.GetUserAsync(ujpd.UserId).ConfigureAwait(false);

                {
                    var opus = this.Opus.CreateDecoder();
                    var vtx  = new AudioSender(ujpd.SSRC, opus)
                    {
                        User = usrj
                    };

                    if (!this.TransmittingSSRCs.TryAdd(vtx.SSRC, vtx))
                    {
                        this.Opus.DestroyDecoder(opus);
                    }
                }

                await this._userJoined.InvokeAsync(this, new VoiceUserJoinEventArgs { User = usrj, SSRC = ujpd.SSRC }).ConfigureAwait(false);

                break;

            case 13:     // CLIENT_DISCONNECTED
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_DISCONNECTED (OP13)");
                var ulpd   = opp.ToObject <VoiceUserLeavePayload>();
                var txssrc = this.TransmittingSSRCs.FirstOrDefault(x => x.Value.Id == ulpd.UserId);
                if (this.TransmittingSSRCs.ContainsKey(txssrc.Key))
                {
                    this.TransmittingSSRCs.TryRemove(txssrc.Key, out var txssrc13);
                    this.Opus.DestroyDecoder(txssrc13.Decoder);
                }

                var usrl = await this.Discord.GetUserAsync(ulpd.UserId).ConfigureAwait(false);

                await this._userLeft.InvokeAsync(this, new VoiceUserLeaveEventArgs
                {
                    User = usrl,
                    SSRC = txssrc.Key
                }).ConfigureAwait(false);

                break;

            default:
                this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received unknown voice opcode (OP{0})", opc);
                break;
            }
        }
        private async Task HandleDispatch(JObject jo)
        {
            var opc = (int)jo["op"];
            var opp = jo["d"] as JObject;

            switch (opc)
            {
            case 2:     // READY
                this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP2 received", DateTime.Now);
                var vrp = opp.ToObject <VoiceReadyPayload>();
                this.SSRC = vrp.SSRC;
                this.ConnectionEndpoint = new ConnectionEndpoint(this.ConnectionEndpoint.Hostname, vrp.Port);
                // this is not the valid interval
                // oh, discord
                //this.HeartbeatInterval = vrp.HeartbeatInterval;
                this.HeartbeatTask = Task.Run(this.HeartbeatAsync);
                await this.Stage1(vrp).ConfigureAwait(false);

                break;

            case 4:     // SESSION_DESCRIPTION
                this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP4 received", DateTime.Now);
                var vsd = opp.ToObject <VoiceSessionDescriptionPayload>();
                this.Key    = vsd.SecretKey;
                this.Sodium = new Sodium(this.Key.AsMemory());
                await this.Stage2(vsd).ConfigureAwait(false);

                break;

            case 5:     // SPEAKING
                // Don't spam OP5
                //this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP5 received", DateTime.Now);
                var spd = opp.ToObject <VoiceSpeakingPayload>();
                var spk = new UserSpeakingEventArgs(this.Discord)
                {
                    Speaking = spd.Speaking,
                    SSRC     = spd.SSRC.Value,
                    User     = this.Discord.InternalGetCachedUser(spd.UserId.Value)
                };

#if !NETSTANDARD1_1
                if (spk.User != null && this.TransmittingSSRCs.TryGetValue(spk.SSRC, out var txssrc5) && txssrc5.Id == 0)
                {
                    txssrc5.User = spk.User;
                }
                else
                {
                    var opus = this.Opus.CreateDecoder();
                    var vtx  = new AudioSender(spk.SSRC, opus)
                    {
                        User = await this.Discord.GetUserAsync(spd.UserId.Value).ConfigureAwait(false)
                    };

                    if (!this.TransmittingSSRCs.TryAdd(spk.SSRC, vtx))
                    {
                        this.Opus.DestroyDecoder(opus);
                    }
                }
#endif

                await this._userSpeaking.InvokeAsync(spk).ConfigureAwait(false);

                break;

            case 6:     // HEARTBEAT ACK
                var dt   = DateTime.Now;
                var ping = (int)(dt - this.LastHeartbeat).TotalMilliseconds;
                Volatile.Write(ref this._wsPing, ping);
                this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", $"Received voice heartbeat ACK, ping {ping.ToString("#,##0", CultureInfo.InvariantCulture)}ms", dt);
                this.LastHeartbeat = dt;
                break;

            case 8:     // HELLO
                // this sends a heartbeat interval that we need to use for heartbeating
                this.HeartbeatInterval = opp["heartbeat_interval"].ToObject <int>();
                break;

            case 9:     // RESUMED
                this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP9 received", DateTime.Now);
                this.HeartbeatTask = Task.Run(this.HeartbeatAsync);
                break;

            case 12:     // CLIENT_CONNECTED
                var ujpd = opp.ToObject <VoiceUserJoinPayload>();
                var usrj = await this.Discord.GetUserAsync(ujpd.UserId).ConfigureAwait(false);

#if !NETSTANDARD1_1
                {
                    var opus = this.Opus.CreateDecoder();
                    var vtx  = new AudioSender(ujpd.SSRC, opus)
                    {
                        User = usrj
                    };

                    if (!this.TransmittingSSRCs.TryAdd(vtx.SSRC, vtx))
                    {
                        this.Opus.DestroyDecoder(opus);
                    }
                }
#endif

                await this._userJoined.InvokeAsync(new VoiceUserJoinEventArgs(this.Discord) { User = usrj, SSRC = ujpd.SSRC }).ConfigureAwait(false);

                break;

            case 13:     // CLIENT_DISCONNECTED
                var ulpd = opp.ToObject <VoiceUserLeavePayload>();

#if !NETSTANDARD1_1
                var txssrc = this.TransmittingSSRCs.FirstOrDefault(x => x.Value.Id == ulpd.UserId);
                if (this.TransmittingSSRCs.ContainsKey(txssrc.Key))
                {
                    this.TransmittingSSRCs.TryRemove(txssrc.Key, out var txssrc13);
                    this.Opus.DestroyDecoder(txssrc13.Decoder);
                }
#endif

                var usrl = await this.Discord.GetUserAsync(ulpd.UserId).ConfigureAwait(false);

                await this._userLeft.InvokeAsync(new VoiceUserLeaveEventArgs(this.Discord)
                {
                    User = usrl
#if !NETSTANDARD1_1
                    , SSRC = txssrc.Key
#endif
                }).ConfigureAwait(false);

                break;

            default:
                this.Discord.DebugLogger.LogMessage(LogLevel.Warning, "VoiceNext", $"Unknown opcode received: {opc.ToString(CultureInfo.InvariantCulture)}", DateTime.Now);
                break;
            }
        }
        private bool ProcessPacket(ReadOnlySpan <byte> data, ref Memory <byte> opus, ref Memory <byte> pcm, IList <ReadOnlyMemory <byte> > pcmPackets, out AudioSender voiceSender, out AudioFormat outputFormat)
        {
            voiceSender  = null;
            outputFormat = default;
            if (!this.Rtp.IsRtpHeader(data))
            {
                return(false);
            }

            this.Rtp.DecodeHeader(data, out var sequence, out var timestamp, out var ssrc, out var hasExtension);

            var vtx = this.TransmittingSSRCs[ssrc];

            voiceSender = vtx;
            if (sequence <= vtx.LastSequence) // out-of-order packet; discard
            {
                return(false);
            }
            var gap = vtx.LastSequence != 0 ? sequence - 1 - vtx.LastSequence : 0;

            if (gap >= 5)
            {
                this.Discord.DebugLogger.LogMessage(LogLevel.Warning, "VNext RX", "5 or more voice packets were dropped when receiving", DateTime.Now);
            }

            Span <byte> nonce = stackalloc byte[Sodium.NonceSize];

            this.Sodium.GetNonce(data, nonce, this.SelectedEncryptionMode);
            this.Rtp.GetDataFromPacket(data, out var encryptedOpus, this.SelectedEncryptionMode);

            var opusSize = Sodium.CalculateSourceSize(encryptedOpus);

            opus = opus.Slice(0, opusSize);
            var opusSpan = opus.Span;

            try
            {
                this.Sodium.Decrypt(encryptedOpus, opusSpan, nonce);

                // Strip extensions, if any
                if (hasExtension)
                {
                    // RFC 5285, 4.2 One-Byte header
                    // http://www.rfcreader.com/#rfc5285_line186
                    if (opusSpan[0] == 0xBE && opusSpan[1] == 0xDE)
                    {
                        var headerLen = opusSpan[2] << 8 | opusSpan[3];
                        var i         = 4;
                        for (; i < headerLen + 4; i++)
                        {
                            var @byte = opusSpan[i];

                            // ID is currently unused since we skip it anyway
                            //var id = (byte)(@byte >> 4);
                            var length = (byte)(@byte & 0x0F) + 1;

                            i += length;
                        }

                        // Strip extension padding too
                        while (opusSpan[i] == 0)
                        {
                            i++;
                        }

                        opusSpan = opusSpan.Slice(i);
                    }

                    // TODO: consider implementing RFC 5285, 4.3. Two-Byte Header
                }

                if (gap == 1)
                {
                    var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder);
                    var fecpcm          = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
                    var fecpcmMem       = fecpcm.AsSpan();
                    this.Opus.Decode(vtx.Decoder, opusSpan, ref fecpcmMem, true, out _);
                    pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
                }
                else if (gap > 1)
                {
                    var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder);
                    for (var i = 0; i < gap; i++)
                    {
                        var fecpcm    = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
                        var fecpcmMem = fecpcm.AsSpan();
                        this.Opus.ProcessPacketLoss(vtx.Decoder, lastSampleCount, ref fecpcmMem);
                        pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
                    }
                }

                var pcmSpan = pcm.Span;
                this.Opus.Decode(vtx.Decoder, opusSpan, ref pcmSpan, false, out outputFormat);
                pcm = pcm.Slice(0, pcmSpan.Length);
            }
            finally
            {
                vtx.LastSequence = sequence;
            }

            return(true);
        }
        private void Start_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine("Inside StartCall Button");

            string sIP = "192.168.57.121";

            //string sIP = "38.127.68.60";
            //string sIP = "60.68.127.38";
            int iFriendPort = 60002;
            int iRet = 0;
            P2PWrapper p2pWrapper = P2PWrapper.GetInstance();
            iRet = p2pWrapper.InitializeLibraryR(100/*UserID*/);
            System.Console.WriteLine("MediaEngineLib==> InitializeLibrary, iRet = " + iRet);
            p2pWrapper.CreateSessionR(200/*FriendID*/, 1/*Audio*/, sIP, iFriendPort);
            p2pWrapper.CreateSessionR(200, 2/*Video*/, sIP, iFriendPort);
            p2pWrapper.SetRelayServerInformationR(200, 1, sIP, iFriendPort);
            p2pWrapper.SetRelayServerInformationR(200, 2, sIP, iFriendPort);
            iRet = p2pWrapper.StartAudioCallR(200);
            iRet = p2pWrapper.StartVideoCallR(200, 288  /*Height*/, 352/*Width*/);
            System.Diagnostics.Debug.WriteLine("MediaEngineLib==> StartVideoCall, iRet = " + iRet);
            p2pWrapper.SetLoggingStateR(true, 5);
            p2pWrapper.LinkWithConnectivityLib(null);

            AudioSender oAlpha = new AudioSender();
            oAlpha.AudioData = System.IO.File.ReadAllBytes(@"AudioSending(1).pcm");
            //oAlpha.bStartSending = true;

            Thread oThread = new Thread(new ThreadStart(oAlpha.StartSendingAudio));
            oThread.Start();
        }
        internal VoiceNextConnection(DiscordClient client, DiscordGuild guild, DiscordChannel channel, VoiceNextConfiguration config, VoiceServerUpdatePayload server, VoiceStateUpdatePayload state, AudioSender audioSender)
        {
            Discord = client;
            Guild   = guild;
            Channel = channel;
            SSRCMap = new ConcurrentDictionary <uint, ulong>();

            _userSpeaking = new AsyncEvent <UserSpeakingEventArgs>(Discord.EventErrorHandler, "USER_SPEAKING");
            _userLeft     = new AsyncEvent <VoiceUserLeaveEventArgs>(Discord.EventErrorHandler, "USER_LEFT");
#if !NETSTANDARD1_1
            _voiceReceived = new AsyncEvent <VoiceReceiveEventArgs>(Discord.EventErrorHandler, "VOICE_RECEIVED");
#endif
            _voiceSocketError = new AsyncEvent <SocketErrorEventArgs>(Discord.EventErrorHandler, "VOICE_WS_ERROR");
            TokenSource       = new CancellationTokenSource();

            Configuration = config;
            Opus          = new OpusCodec(48000, 2, Configuration.VoiceApplication);
            Sodium        = new SodiumCodec();
            Rtp           = new RtpCodec();

            ServerData = server;
            StateData  = state;

            var eps = ServerData.Endpoint;
            var epi = eps.LastIndexOf(':');
            var eph = string.Empty;
            var epp = 80;
            if (epi != -1)
            {
                eph = eps.Substring(0, epi);
                epp = int.Parse(eps.Substring(epi + 1));
            }
            else
            {
                eph = eps;
            }
            ConnectionEndpoint = new ConnectionEndpoint {
                Hostname = eph, Port = epp
            };

            ReadyWait     = new TaskCompletionSource <bool>();
            IsInitialized = false;
            IsDisposed    = false;

            PlayingWait       = null;
            PlaybackSemaphore = new SemaphoreSlim(1, 1);

            UdpClient                = new NativeUdpClient(audioSender);
            VoiceWs                  = Discord.Configuration.WebSocketClientFactory(Discord.Configuration.Proxy);
            VoiceWs.Disconnected    += this.VoiceWS_SocketClosed;
            VoiceWs.MessageReceived += this.VoiceWS_SocketMessage;
            VoiceWs.Connected       += this.VoiceWS_SocketOpened;
            VoiceWs.Errored         += this.VoiceWs_SocketErrored;
        }