Example #1
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 (!Rtp.IsRtpHeader(data))
            {
                return(false);
            }

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

            var vtx = 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)
            {
                Discord.DebugLogger.LogMessage(LogLevel.Warning, "VNext RX", "5 or more voice packets were dropped when receiving", DateTime.Now);
            }

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

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

            var opusSize = Sodium.CalculateSourceSize(encryptedOpus);

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

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

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

            return(true);
        }
        private async Task VoiceReceiverTask()
        {
            var token  = ReceiverToken;
            var client = UdpClient;

            while (!token.IsCancellationRequested)
            {
                if (client.DataAvailable <= 0)
                {
                    continue;
                }

                byte[] data = null, header = null;
                ushort seq = 0;
                uint   ts = 0, ssrc = 0;
                try
                {
                    data = await client.ReceiveAsync().ConfigureAwait(false);

                    header = new byte[RtpCodec.SIZE_HEADER];
                    data   = Rtp.Decode(data, header);

                    var nonce = Rtp.MakeNonce(header);
                    data = Sodium.Decode(data, nonce, Key);

                    // following is thanks to code from Eris
                    // https://github.com/abalabahaha/eris/blob/master/lib/voice/VoiceConnection.js#L623
                    var doff = 0;
                    Rtp.Decode(header, out seq, out ts, out ssrc, out var has_ext);
                    if (has_ext)
                    {
                        if (data[0] == 0xBE && data[1] == 0xDE)
                        {
                            // RFC 5285, 4.2 One-Byte header
                            // http://www.rfcreader.com/#rfc5285_line186

                            var hlen = data[2] << 8 | data[3];
                            var i    = 4;
                            for (; i < hlen + 4; i++)
                            {
                                var b = data[i];
                                // This is unused(?)
                                //var id = (b >> 4) & 0x0F;
                                var len = (b & 0x0F) + 1;
                                i += len;
                            }
                            while (data[i] == 0)
                            {
                                i++;
                            }
                            doff = i;
                        }
                        // TODO: consider implementing RFC 5285, 4.3. Two-Byte Header
                    }

                    data = Opus.Decode(data, doff, data.Length - doff);
                }
                catch { continue; }

                // TODO: wait for ssrc map?
                DiscordUser user = null;
                if (SSRCMap.ContainsKey(ssrc))
                {
                    var id = SSRCMap[ssrc];
                    if (Guild != null)
                    {
                        user = Guild._members.FirstOrDefault(xm => xm.Id == id) ?? await Guild.GetMemberAsync(id).ConfigureAwait(false);
                    }

                    if (user == null)
                    {
                        user = Discord.InternalGetCachedUser(id);
                    }

                    if (user == null)
                    {
                        user = new DiscordUser {
                            Discord = Discord, Id = id
                        }
                    }
                    ;
                }

                await _voiceReceived.InvokeAsync(new VoiceReceiveEventArgs(Discord)
                {
                    SSRC        = ssrc,
                    Voice       = new ReadOnlyCollection <byte>(data),
                    VoiceLength = 20,
                    User        = user
                }).ConfigureAwait(false);
            }
        }