/// <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(); }
/// <summary> Produces Opus encoded audio from PCM samples. </summary> /// <param name="input">PCM samples to encode.</param> /// <param name="inputOffset">Offset of the frame in pcmSamples.</param> /// <param name="output">Buffer to store the encoded frame.</param> /// <returns>Length of the frame contained in outputBuffer.</returns> public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output) { int result = 0; fixed(byte *inPtr = input) result = Opus.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); if (result < 0) { throw new Exception(((OpusError)result).ToString()); } return(result); }
internal void PreparePacket(ReadOnlySpan <byte> pcm, ref Memory <byte> target) { var audioFormat = AudioFormat; var packetArray = ArrayPool <byte> .Shared.Rent(Rtp.CalculatePacketSize(audioFormat.SampleCountToSampleSize(audioFormat.CalculateMaximumFrameSize()), SelectedEncryptionMode)); var packet = packetArray.AsSpan(); Rtp.EncodeHeader(Sequence, Timestamp, SSRC, packet); var opus = packet.Slice(Rtp.HeaderSize, pcm.Length); Opus.Encode(pcm, ref opus); Sequence++; Timestamp += (uint)audioFormat.CalculateFrameSize(audioFormat.CalculateSampleDuration(pcm.Length)); Span <byte> nonce = new byte[Sodium.NonceSize]; switch (SelectedEncryptionMode) { case EncryptionMode.XSalsa20_Poly1305: Sodium.GenerateNonce(packet.Slice(0, Rtp.HeaderSize), nonce); break; case EncryptionMode.XSalsa20_Poly1305_Suffix: Sodium.GenerateNonce(nonce); break; case EncryptionMode.XSalsa20_Poly1305_Lite: Sodium.GenerateNonce(Nonce++, nonce); break; default: ArrayPool <byte> .Shared.Return(packetArray); throw new Exception("Unsupported encryption mode."); } Span <byte> encrypted = new byte[Sodium.CalculateTargetSize(opus)]; Sodium.Encrypt(opus, encrypted, nonce); encrypted.CopyTo(packet.Slice(Rtp.HeaderSize)); packet = packet.Slice(0, Rtp.CalculatePacketSize(encrypted.Length, SelectedEncryptionMode)); Sodium.AppendNonce(nonce, packet, SelectedEncryptionMode); target = target.Slice(0, packet.Length); packet.CopyTo(target.Span); ArrayPool <byte> .Shared.Return(packetArray); }
private static void SenderMode(ArgsParser parser) { if (parser .Keys("r", "remote").Value(out var remote, new IPEndPoint(IPAddress.Loopback, 19000)) .Keys("ssrc").Value(out var ssrc, 0x0000001u) .Keys("d", "packet-duration").Value(out var packetDuration, 60u) .Keys("i", "input-device").Value(out var device, 0) .Result() != null) { return; } var rnd = new Random(); var seqNumber = (ushort)rnd.Next(); var timestamp = (uint)rnd.Next(); var samples = new short[SAMPLE_RATE / MS_IN_SECOND * packetDuration]; var marker = true; var codec = new Opus(); var rtpSender = new UdpTransport <IPacket>(new RtpContract(), 0); rtpSender.Start(); var waveIn = new WaveInEvent { DeviceNumber = device, WaveFormat = new WaveFormat(SAMPLE_RATE, BIT_PER_SAMPLE, CHANNEL_COUNT), BufferMilliseconds = (int)packetDuration, }; waveIn.DataAvailable += (sender, e) => { var payload = new byte[e.BytesRecorded]; Buffer.BlockCopy(e.Buffer, 0, samples, 0, e.BytesRecorded); Array.Resize(ref payload, codec.Encode(samples, 0, samples.Length, payload, 0, payload.Length)); var packet = new RtpPacket { Ssrc = ssrc, PayloadType = PAYLOAD_TYPE_OPUS, Marker = marker, Timestamp = timestamp, SequenceNumber = seqNumber, Payload = payload, }; rtpSender.Send(new PacketContainer <IPacket> { Target = remote, Payload = packet }); timestamp += packetDuration; seqNumber += 1; marker = false; }; waveIn.StartRecording(); Console.WriteLine("Press any key to stop"); Console.ReadKey(); }