Skip to content

Commit

Permalink
Attempt to implement rtcp receiver reports; Attempt to implement fec;…
Browse files Browse the repository at this point in the history
… minor improvements here and there
  • Loading branch information
OoLunar committed Jan 10, 2024
1 parent db5e67c commit 0cca024
Show file tree
Hide file tree
Showing 18 changed files with 304 additions and 85 deletions.
1 change: 1 addition & 0 deletions examples/HelloWorld/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Threading.Tasks;
using DSharpPlus.VoiceLink.Enums;
using DSharpPlus.VoiceLink.VoiceEncrypters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
Expand Down
6 changes: 4 additions & 2 deletions src/DSharpPlus.VoiceLink/DSharpPlus.VoiceLink.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
</PropertyGroup>
<ItemGroup>
<None Include="../../LICENSE" Pack="true" PackagePath="" />
<ProjectReference Include="../../libs/DSharpPlus/DSharpPlus/DSharpPlus.csproj" />
<PackageReference Include="Geralt" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
Expand All @@ -26,4 +25,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="..\..\libs\DSharpPlus\DSharpPlus\DSharpPlus.csproj" />
</ItemGroup>
</Project>
22 changes: 15 additions & 7 deletions src/DSharpPlus.VoiceLink/Opus/OpusDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,22 @@ public unsafe void Init(OpusSampleRate sampleRate, int channels)
}
}

/// <inheritdoc cref="OpusNativeMethods.Decode(OpusDecoder*, byte*, int, short*, int, int)"/>
public unsafe int Decode(ReadOnlySpan<byte> data, ref Span<short> pcm, int frameSize, bool decodeFec)
/// <inheritdoc cref="OpusNativeMethods.Decode(OpusDecoder*, byte*, int, byte*, int, int)"/>
public unsafe int Decode(ReadOnlySpan<byte> data, Span<byte> pcm, bool decodeFec)
{
int decodedLength;
fixed (OpusDecoder* pinned = &this)
fixed (byte* dataPointer = data)
fixed (short* pcmPointer = pcm)
fixed (byte* pcmPointer = pcm)
{
decodedLength = OpusNativeMethods.Decode(pinned, dataPointer, data.Length, pcmPointer, frameSize, decodeFec ? 1 : 0);
decodedLength = OpusNativeMethods.Decode(
pinned,
dataPointer,
data.Length,
pcmPointer,
OpusNativeMethods.PacketGetNbFrames(dataPointer, data.Length),
decodeFec ? 1 : 0
);
}

// Less than zero means an error occurred
Expand All @@ -48,16 +55,17 @@ public unsafe int Decode(ReadOnlySpan<byte> data, ref Span<short> pcm, int frame
throw new OpusException((OpusErrorCode)decodedLength);
}

return decodedLength * sizeof(short) * 2; // Multiplied by the sample size, which is size of short times the number of channels
// Multiplied by the sample size, which is size of short times the number of channels
return decodedLength * sizeof(short) * 2;
}

/// <inheritdoc cref="OpusNativeMethods.DecodeFloat(OpusDecoder*, byte*, int, float*, int, int)"/>
public unsafe int DecodeFloat(ReadOnlySpan<byte> data, ref Span<float> pcm, int frameSize, bool decodeFec)
public unsafe int DecodeFloat(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize, bool decodeFec)
{
int decodedLength;
fixed (OpusDecoder* pinned = &this)
fixed (byte* dataPointer = data)
fixed (float* pcmPointer = pcm)
fixed (byte* pcmPointer = pcm)
{
decodedLength = OpusNativeMethods.DecodeFloat(pinned, dataPointer, data.Length, pcmPointer, frameSize, decodeFec ? 1 : 0);
}
Expand Down
2 changes: 1 addition & 1 deletion src/DSharpPlus.VoiceLink/Opus/OpusException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public sealed class OpusException : Exception
OpusErrorCode.Unimplemented => "Error -5: Invalid/unsupported request number.",
OpusErrorCode.InvalidState => "Error -6: An encoder or decoder structure is invalid or already freed.",
OpusErrorCode.AllocFail => "Error -7: Memory allocation has failed.",
_ => "Unknown error."
_ => $"Error {(int)errorCode}: Unknown error."
};
}
}
4 changes: 2 additions & 2 deletions src/DSharpPlus.VoiceLink/Opus/OpusNativeMethods.Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ internal static partial class OpusNativeMethods
/// <param name="decodeFec">Flag (0 or 1) to request that any in-band forward error correction data be decoded. If no such data is available, the frame is decoded as if it were lost.</param>
/// <returns>Number of decoded samples or an <see cref="OpusErrorCode"/></returns>
[LibraryImport("opus", EntryPoint = "opus_decode")]
public static unsafe partial int Decode(OpusDecoder* decoder, byte* data, int length, short* pcm, int frameSize, int decodeFec);
public static unsafe partial int Decode(OpusDecoder* decoder, byte* data, int length, byte* pcm, int frameSize, int decodeFec);

/// <inheritdoc cref="Decode(OpusDecoder*, byte*, int, byte*, int, int)"/>
[LibraryImport("opus", EntryPoint = "opus_decode_float")]
public static unsafe partial int DecodeFloat(OpusDecoder* decoder, byte* data, int length, float* pcm, int frameSize, int decodeFec);
public static unsafe partial int DecodeFloat(OpusDecoder* decoder, byte* data, int length, byte* pcm, int frameSize, int decodeFec);

/// <summary>
/// Perform a CTL function on an Opus decoder.
Expand Down
57 changes: 57 additions & 0 deletions src/DSharpPlus.VoiceLink/Rtp/RtcpHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Buffers.Binary;

namespace DSharpPlus.VoiceLink.Rtp
{
public readonly record struct RtcpHeader
{
/// <summary>
/// Gets the version of the RTCP header.
/// </summary>
public int Version { get; init; }

/// <summary>
/// Gets whether the RTCP header has padding.
/// </summary>
public int Padding { get; init; }

/// <summary>
/// Gets the report count of the RTCP header.
/// </summary>
public int ReportCount { get; init; }

/// <summary>
/// Gets the packet type of the RTCP header.
/// </summary>
public int PacketType { get; init; }

/// <summary>
/// Gets the length of the RTCP header.
/// </summary>
public int Length { get; init; }

/// <summary>
/// Gets the SSRC of the RTCP header.
/// </summary>
public uint Ssrc { get; init; }

public RtcpHeader(ReadOnlySpan<byte> data)
{
if (data.Length < 8)
{
throw new ArgumentException("The source buffer must have a minimum of 8 bytes for it to be a RTCP header.", nameof(data));
}
else if (data[1] != 201)
{
throw new ArgumentException("The source buffer must contain a RTCP receiver report.", nameof(data));
}

Version = data[0] >> 6;
Padding = (data[0] >> 5) & 0b00000001;
ReportCount = data[0] & 0b00011111;
PacketType = data[1];
Length = BinaryPrimitives.ReadUInt16BigEndian(data[2..4]);
Ssrc = BinaryPrimitives.ReadUInt32BigEndian(data[4..8]);
}
}
}
31 changes: 31 additions & 0 deletions src/DSharpPlus.VoiceLink/Rtp/RtcpReceiverReportPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;

namespace DSharpPlus.VoiceLink.Rtp
{
public readonly record struct RtcpReceiverReportPacket
{
/// <summary>
/// Gets the header of the RTCP packet.
/// </summary>
public RtcpHeader Header { get; init; }

/// <summary>
/// Gets the report blocks of the RTCP packet.
/// </summary>
public IReadOnlyList<RtcpReportBlock> ReportBlocks { get; init; }

public RtcpReceiverReportPacket(RtcpHeader header, ReadOnlySpan<byte> data)
{
List<RtcpReportBlock> reportBlocks = new(header.ReportCount);
for (int i = 0; i < header.ReportCount; i++)
{
reportBlocks.Add(new RtcpReportBlock(data));
data = data[24..];
}

Header = header;
ReportBlocks = reportBlocks;
}
}
}
27 changes: 27 additions & 0 deletions src/DSharpPlus.VoiceLink/Rtp/RtcpReportBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Buffers.Binary;

namespace DSharpPlus.VoiceLink.Rtp
{
public readonly record struct RtcpReportBlock
{
public ushort SynchronizationSource { get; }
public ushort FractionLost { get; }
public uint CumulativePacketsLost { get; }
public uint ExtendedHighestSequenceNumberReceived { get; }
public uint InterarrivalJitter { get; }
public uint LastSenderReport { get; }
public uint DelaySinceLastSenderReport { get; }

public RtcpReportBlock(ReadOnlySpan<byte> data)
{
SynchronizationSource = BinaryPrimitives.ReadUInt16BigEndian(data);
FractionLost = data[2];
CumulativePacketsLost = BinaryPrimitives.ReadUInt32BigEndian(data[3..]);
ExtendedHighestSequenceNumberReceived = BinaryPrimitives.ReadUInt32BigEndian(data[7..]);
InterarrivalJitter = BinaryPrimitives.ReadUInt32BigEndian(data[11..]);
LastSenderReport = BinaryPrimitives.ReadUInt32BigEndian(data[15..]);
DelaySinceLastSenderReport = BinaryPrimitives.ReadUInt32BigEndian(data[19..]);
}
}
}
28 changes: 28 additions & 0 deletions src/DSharpPlus.VoiceLink/Rtp/RtcpUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace DSharpPlus.VoiceLink.Rtp
{
public static class RtcpUtilities
{
/// <summary>
/// Determines if the given buffer contains a valid RTCP header.
/// </summary>
/// <param name="source">The data to reference.</param>
/// <returns>Whether the data contains a valid RTCP header.</returns>
public static bool IsRtcpReceiverReport(ReadOnlySpan<byte> source) => source.Length >= 8 && source[1] == 201;

public static RtcpHeader DecodeHeader(ReadOnlySpan<byte> source)
{
if (source.Length < 8)
{
throw new ArgumentException("The source buffer must have a minimum of 8 bytes for it to be a RTCP header.", nameof(source));
}
else if (source[1] != 201)
{
throw new ArgumentException("The source buffer must contain a RTCP receiver report.", nameof(source));
}

return new RtcpHeader(source);
}
}
}
22 changes: 21 additions & 1 deletion src/DSharpPlus.VoiceLink/Rtp/RtpHeader.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
namespace DSharpPlus.VoiceLink.Rtp
{
public readonly struct RtpHeader
public readonly record struct RtpHeader
{
public byte FirstMetadata { get; init; }
public byte SecondMetadata { get; init; }
public ushort Sequence { get; init; }
public uint Timestamp { get; init; }
public uint Ssrc { get; init; }

// The version is the first two bits of the first byte.
public byte Version => (byte)(FirstMetadata & 0b11000000);

// The padding bit is the third bit of the first byte.
public bool HasPadding => (FirstMetadata & 0b00100000) != 0;

// The extension bit is the fourth bit of the first byte.
public bool HasExtension => (FirstMetadata & 0b00010000) != 0;

// The CSRC count is the last four bits of the first byte.
public byte CsrcCount => (byte)((FirstMetadata & 0b00001111) >> 4);

// The marker bit is the first bit of the second byte.
public bool HasMarker => (SecondMetadata & 0b10000000) != 0;

// The payload type is the last seven bits of the second byte.
public byte PayloadType => (byte)(SecondMetadata & 0b01111111);
}
}
3 changes: 3 additions & 0 deletions src/DSharpPlus.VoiceLink/Rtp/RtpUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class RtpUtilities
public const byte VersionWithExtension = 0x90;
public const byte Version = 0x80;
public const byte PayloadType = 0x78;
public static readonly byte[] RtpExtensionOneByte = [190, 222];

/// <summary>
/// Determines if the given buffer contains a valid RTP header.
Expand Down Expand Up @@ -67,6 +68,8 @@ public static RtpHeader DecodeHeader(ReadOnlySpan<byte> source)

return new RtpHeader()
{
FirstMetadata = source[0],
SecondMetadata = source[1],
Sequence = BinaryPrimitives.ReadUInt16BigEndian(source[2..4]),
Timestamp = BinaryPrimitives.ReadUInt32BigEndian(source[4..8]),
Ssrc = BinaryPrimitives.ReadUInt32BigEndian(source[8..12])
Expand Down
5 changes: 3 additions & 2 deletions src/DSharpPlus.VoiceLink/VoiceEncrypters/IVoiceEncrypter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public interface IVoiceEncrypter

int GetEncryptedSize(int length);
int GetDecryptedSize(int length);
bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target);
bool Decrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target);
bool TryEncryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target);
bool TryDecryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target);
bool TryDecryptReportPacket(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target) => false;
}
}
6 changes: 3 additions & 3 deletions src/DSharpPlus.VoiceLink/VoiceEncrypters/XSalsa20Poly1305.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed record XSalsa20Poly1305 : IVoiceEncrypter
public int GetEncryptedSize(int length) => length + SodiumXSalsa20Poly1305.MacSize;
public int GetDecryptedSize(int length) => length - SodiumXSalsa20Poly1305.MacSize;

public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryEncryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand All @@ -35,10 +35,10 @@ public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOn
target[..12].CopyTo(nonce);

// Encrypt the data
return SodiumXSalsa20Poly1305.Encrypt(data, key, nonce, target[24..]) == 0;
return SodiumXSalsa20Poly1305.Encrypt(data, key, nonce, target[12..]) == 0;
}

public bool Decrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryDecryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed record XSalsa20Poly1305Lite : IVoiceEncrypter
public int GetEncryptedSize(int length) => length + SodiumXSalsa20Poly1305.MacSize;
public int GetDecryptedSize(int length) => length - SodiumXSalsa20Poly1305.MacSize;

public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryEncryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand All @@ -47,7 +47,7 @@ public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOn
return SodiumXSalsa20Poly1305.Encrypt(data, key, nonce, target[16..]) == 0;
}

public bool Decrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryDecryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed record XSalsa20Poly1305Suffix : IVoiceEncrypter
public int GetEncryptedSize(int length) => length + SodiumXSalsa20Poly1305.MacSize;
public int GetDecryptedSize(int length) => length - SodiumXSalsa20Poly1305.MacSize;

public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryEncryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand All @@ -40,7 +40,7 @@ public bool Encrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOn
return SodiumXSalsa20Poly1305.Encrypt(data, key, nonce, target[36..]) == 0;
}

public bool Decrypt(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
public bool TryDecryptOpusPacket(VoiceLinkUser voiceLinkUser, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key, Span<byte> target)
{
if (data.Length < SodiumXSalsa20Poly1305.MacSize)
{
Expand Down
11 changes: 0 additions & 11 deletions src/DSharpPlus.VoiceLink/VoiceLinkConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Net;
using DSharpPlus.Net.WebSocket;
using DSharpPlus.VoiceLink.VoiceEncrypters;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -11,15 +9,6 @@ public sealed record VoiceLinkConfiguration
public IServiceCollection ServiceCollection { get; set; } = new ServiceCollection();
internal IServiceProvider ServiceProvider => _serviceProvider ??= ServiceCollection.BuildServiceProvider();
private IServiceProvider? _serviceProvider;

public IWebProxy? Proxy { get; set; }
public WebSocketClientFactoryDelegate WebSocketClientFactory
{
internal get => _webSocketClientFactory;
set => _webSocketClientFactory = value is null ? throw new ArgumentNullException(nameof(value)) : value;
}
private WebSocketClientFactoryDelegate _webSocketClientFactory = WebSocketClient.CreateNew;

public int MaxHeartbeatQueueSize { get; set; } = 5;
public IVoiceEncrypter VoiceEncrypter { get; set; } = new XSalsa20Poly1305();
}
Expand Down
Loading

0 comments on commit 0cca024

Please sign in to comment.