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