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