/// <summary> /// Encodes, encrypts, and sends the provided PCM data to the connected voice channel. /// </summary> /// <param name="pcmData">PCM data to encode, encrypt, and send.</param> /// <param name="blockSize">Millisecond length of the PCM data.</param> /// <param name="bitRate">Bitrate of the PCM data.</param> /// <returns>Task representing the sending operation.</returns> public async Task SendAsync(byte[] pcmData, int blockSize, int bitRate = 16) { if (!IsInitialized) { throw new InvalidOperationException("The connection is not initialized"); } await PlaybackSemaphore.WaitAsync().ConfigureAwait(false); var rtp = Rtp.Encode(Sequence, Timestamp, SSRC); var dat = Opus.Encode(pcmData, 0, pcmData.Length, bitRate); dat = Sodium.Encode(dat, Rtp.MakeNonce(rtp), Key); dat = Rtp.Encode(rtp, dat); await SendSpeakingAsync(true).ConfigureAwait(false); // works: //await UdpClient.SendAsync(dat, dat.Length).ConfigureAwait(false); await UdpClient.SendNativelyAsync(dat, dat.Length).ConfigureAwait(false); Sequence++; Timestamp += 48 * (uint)blockSize; PlaybackSemaphore.Release(); }
/// <summary> /// Encodes, encrypts, and sends the provided PCM data to the connected voice channel. /// </summary> /// <param name="pcmData">PCM data to encode, encrypt, and send.</param> /// <param name="blockSize">Millisecond length of the PCM data.</param> /// <param name="bitRate">Bitrate of the PCM data.</param> /// <returns>Task representing the sending operation.</returns> public async Task SendAsync(byte[] pcmData, int blockSize, int bitRate = 16) { if (!IsInitialized) { throw new InvalidOperationException("The connection is not initialized"); } await PlaybackSemaphore.WaitAsync().ConfigureAwait(false); var rtp = Rtp.Encode(Sequence, Timestamp, SSRC); var dat = Opus.Encode(pcmData, 0, pcmData.Length, bitRate); dat = Sodium.Encode(dat, Rtp.MakeNonce(rtp), Key); dat = Rtp.Encode(rtp, dat); if (SynchronizerTicks == 0) { SynchronizerTicks = Stopwatch.GetTimestamp(); SynchronizerResolution = (Stopwatch.Frequency * 0.02); TickResolution = 10_000_000.0 / Stopwatch.Frequency; Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", $"Timer accuracy: {Stopwatch.Frequency.ToString("#,##0", CultureInfo.InvariantCulture)}/{SynchronizerResolution.ToString(CultureInfo.InvariantCulture)} (high resolution? {Stopwatch.IsHighResolution})", DateTime.Now); } else { // Provided by Laura#0090 (214796473689178133); this is Python, but adaptable: // // delay = max(0, self.delay + ((start_time + self.delay * loops) + - time.time())) // // self.delay // sample size // start_time // time since streaming started // loops // number of samples sent // time.time() // DateTime.Now var cts = Math.Max(Stopwatch.GetTimestamp() - SynchronizerTicks, 0); if (cts < SynchronizerResolution) { await Task.Delay(TimeSpan.FromTicks((long)((SynchronizerResolution - cts) * TickResolution))).ConfigureAwait(false); } SynchronizerTicks += SynchronizerResolution; } await SendSpeakingAsync(true).ConfigureAwait(false); await UdpClient.SendAsync(dat, dat.Length).ConfigureAwait(false); Sequence++; Timestamp += 48 * (uint)blockSize; PlaybackSemaphore.Release(); }
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); } }