Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
76ba800
Refactor voice receiving
KubaZ2 Feb 24, 2026
de0f19a
Add DoNotParallelize attribute to idle timeout tests
KubaZ2 Feb 24, 2026
b493ee9
Fix a test
KubaZ2 Feb 24, 2026
585eb58
Update BufferedVoiceReceiveHandlerTests to support async operations
KubaZ2 Feb 24, 2026
0ccb532
Improve tests and the buffered handler
KubaZ2 Feb 24, 2026
9b09128
feat: Enhance voice handling with loss correction capabilities
KubaZ2 Feb 24, 2026
78d436e
Correct (I believe) logic for handling lost packets in BufferedVoiceR…
KubaZ2 Feb 25, 2026
a6dd028
Refactor
KubaZ2 Feb 26, 2026
0c1a91c
Update buffered voice receive handler
KubaZ2 Feb 26, 2026
28578ea
Improve voice receive handler
KubaZ2 Feb 28, 2026
6633b33
Add FEC support
KubaZ2 Feb 28, 2026
2c9f64a
Add FEC handling to BufferedVoiceReceiveHandlerTests
KubaZ2 Feb 28, 2026
e32d0cf
Improve EvictLostFrames and rewrite and improve tests
KubaZ2 Mar 1, 2026
a0253f3
Refactor VoiceReceiveEventArgs and LostVoiceReceiveEventArgs for clar…
KubaZ2 Mar 1, 2026
00b7a5c
Refactor test voice commands
KubaZ2 Mar 1, 2026
38207bd
Replace delegate with Action for VoiceReceive event in VoiceReceiveHa…
KubaZ2 Mar 1, 2026
6b42f84
Remove commented-out code and unnecessary console output in VoiceClie…
KubaZ2 Mar 1, 2026
741287f
Remove unused using directive from BufferedVoiceReceiveHandler
KubaZ2 Mar 1, 2026
f89d2e4
Optimize buffered voice receive handler state allocations
KubaZ2 Mar 1, 2026
0b767c3
Refactor window and buffer range checks in BufferedVoiceReceiveHandle…
KubaZ2 Mar 1, 2026
286ca61
Rename stop timer to timeout timer and improve datagram length valida…
KubaZ2 Mar 1, 2026
c2ef169
Refactor BufferedVoiceReceiveHandler to use VoiceReceiveData for impr…
KubaZ2 Mar 1, 2026
9912051
Merge alpha
KubaZ2 May 28, 2026
221ddfd
Remove BufferedVoiceReceiveHandler
KubaZ2 May 28, 2026
23dbe8c
Cleanup
KubaZ2 May 28, 2026
66cd723
Simplify voice receive event args
KubaZ2 May 28, 2026
0250885
Abandon VoiceReceiveHandler
KubaZ2 May 28, 2026
40af027
Fix voice guide snippet
KubaZ2 May 28, 2026
94ff6bb
Remove allow unsafe blocks from NetCord.Test.csproj
KubaZ2 May 28, 2026
5595a5b
Make voice receiving exception safe against malformed payloads (hopef…
KubaZ2 May 28, 2026
90fce34
Skip decryption when there is no VoiceReceive event handler and remov…
KubaZ2 May 28, 2026
27e8b1d
Fix voice receive logging and refactor
KubaZ2 May 28, 2026
02743d1
Add a new line
KubaZ2 May 28, 2026
c57c2bd
Add a comment to ReceiveVoice in guide snippet
KubaZ2 May 28, 2026
322d8c2
Make IP Discovery mandatory
KubaZ2 May 29, 2026
60cf5a5
Optimize logging
KubaZ2 May 29, 2026
3592f93
Improve a guides snippet comment and refactor VoiceClient slightly
KubaZ2 May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions Documentation/guides/basic-concepts/Voice/VoiceModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ public async Task<string> EchoAsync()
voiceState.ChannelId.GetValueOrDefault(),
new VoiceClientConfiguration
{
ReceiveHandler = new VoiceReceiveHandler(), // Required to receive voice
Logger = new ConsoleLogger(),
});

Expand All @@ -141,14 +140,9 @@ public async Task<string> EchoAsync()

voiceClient.VoiceReceive += args =>
{
// If the timestamp is null, the packet was lost.
// We skip it, which mirrors the packet loss to the echo recipients.
if (args.Timestamp is not { } timestamp)
return default;

// Pass current user voice directly to SendAsync to create echo
// Send the received voice back if the received voice is from the user that invoked the command
if (voiceClient.Cache.SsrcUsers.TryGetValue(args.Ssrc, out var voiceUserId) && voiceUserId == userId)
voiceClient.SendVoice(args.SequenceNumber, timestamp, args.Frame);
voiceClient.SendVoice(args.SequenceNumber, args.Timestamp, args.Frame);

return default;
};
Expand Down
2 changes: 1 addition & 1 deletion Documentation/guides/basic-concepts/voice.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ Follow the [installation guide](installing-native-dependencies.md) to install th
[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L13-L110)]

### Receiving Voice
[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L112-L158)]
[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L112-L152)]
23 changes: 16 additions & 7 deletions NetCord/Gateway/Voice/EventArgs/VoiceReceiveEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
using System.Runtime.InteropServices;

namespace NetCord.Gateway.Voice;

public readonly ref struct VoiceReceiveEventArgs(byte[]? buffer, int frameIndex, int frameLength, uint ssrc, uint? timestamp, ushort sequenceNumber)
[StructLayout(LayoutKind.Auto)]
public readonly ref struct VoiceReceiveEventArgs
{
internal readonly byte[]? _buffer = buffer;
public VoiceReceiveEventArgs(ReadOnlySpan<byte> frame, uint ssrc, uint timestamp, ushort sequenceNumber)
{
Frame = frame;
Ssrc = ssrc;
Timestamp = timestamp;
SequenceNumber = sequenceNumber;
}

/// <summary>
/// The voice frame data.
/// </summary>
public ReadOnlySpan<byte> Frame => new(_buffer, frameIndex, frameLength);
public readonly ReadOnlySpan<byte> Frame { get; }

/// <summary>
/// The synchronization source (SSRC) of the sender of the voice frame.
/// </summary>
public uint Ssrc => ssrc;
public readonly uint Ssrc { get; }

/// <summary>
/// The timestamp of the voice frame. <see langword="null"/> when the frame was lost.
/// The timestamp of the voice frame.
/// </summary>
public uint? Timestamp => timestamp;
public readonly uint Timestamp { get; }

/// <summary>
/// The sequence number of the voice frame.
/// </summary>
public ushort SequenceNumber => sequenceNumber;
public readonly ushort SequenceNumber { get; }
Comment thread
KubaZ2 marked this conversation as resolved.
}
8 changes: 0 additions & 8 deletions NetCord/Gateway/Voice/IVoiceReceiveHandler.cs

This file was deleted.

17 changes: 0 additions & 17 deletions NetCord/Gateway/Voice/NullVoiceReceiveHandler.cs

This file was deleted.

20 changes: 12 additions & 8 deletions NetCord/Gateway/Voice/OpusDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ public OpusDecoder(VoiceChannels channels)
/// <param name="data">Input payload. Use <see langword="null"/> to indicate packet loss.</param>
/// <param name="pcm">Output signal.</param>
/// <param name="frameSize">Number of samples per channel in the output signal.</param>
/// <param name="decodeFec">Whether to decode using forward error correction data, if available.</param>
/// <returns>The number of decoded samples per channel.</returns>
public int Decode(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize)
public int Decode(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize, bool decodeFec)
Comment thread
KubaZ2 marked this conversation as resolved.
{
ValidatePcm(pcm.Length, frameSize, PcmFormat.Short, _channels, nameof(pcm));

int result = OpusDecode(_decoder, data, data.Length, pcm, frameSize, 0);
int result = OpusDecode(_decoder, data, data.Length, pcm, frameSize, decodeFec ? 1 : 0);
Comment thread
KubaZ2 marked this conversation as resolved.

Comment thread
KubaZ2 marked this conversation as resolved.
Comment thread
KubaZ2 marked this conversation as resolved.
Comment thread
KubaZ2 marked this conversation as resolved.
ValidateResult(result);

Expand All @@ -51,10 +52,11 @@ public int Decode(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize)
/// <param name="data">Input payload. Use <see langword="null"/> to indicate packet loss.</param>
/// <param name="pcm">Output signal.</param>
/// <param name="frameSize">Number of samples per channel in the output signal.</param>
/// <param name="decodeFec">Whether to decode using forward error correction data, if available.</param>
/// <returns>The number of decoded samples per channel.</returns>
public int Decode(ReadOnlySpan<byte> data, Span<short> pcm, int frameSize)
public int Decode(ReadOnlySpan<byte> data, Span<short> pcm, int frameSize, bool decodeFec)
{
Comment thread
KubaZ2 marked this conversation as resolved.
return Decode(data, MemoryMarshal.AsBytes(pcm), frameSize);
return Decode(data, MemoryMarshal.AsBytes(pcm), frameSize, decodeFec);
}

/// <summary>
Expand All @@ -63,12 +65,13 @@ public int Decode(ReadOnlySpan<byte> data, Span<short> pcm, int frameSize)
/// <param name="data">Input payload. Use <see langword="null"/> to indicate packet loss.</param>
/// <param name="pcm">Output signal.</param>
/// <param name="frameSize">Number of samples per channel in the output signal.</param>
/// <param name="decodeFec">Whether to decode using forward error correction data, if available.</param>
/// <returns>The number of decoded samples per channel.</returns>
public int DecodeFloat(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize)
public int DecodeFloat(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize, bool decodeFec)
{
Comment thread
KubaZ2 marked this conversation as resolved.
ValidatePcm(pcm.Length, frameSize, PcmFormat.Float, _channels, nameof(pcm));

int result = OpusDecodeFloat(_decoder, data, data.Length, pcm, frameSize, 0);
int result = OpusDecodeFloat(_decoder, data, data.Length, pcm, frameSize, decodeFec ? 1 : 0);

ValidateResult(result);

Expand All @@ -81,10 +84,11 @@ public int DecodeFloat(ReadOnlySpan<byte> data, Span<byte> pcm, int frameSize)
/// <param name="data">Input payload. Use <see langword="null"/> to indicate packet loss.</param>
/// <param name="pcm">Output signal.</param>
/// <param name="frameSize">Number of samples per channel in the output signal.</param>
/// <param name="decodeFec">Whether to decode using forward error correction data, if available.</param>
/// <returns>The number of decoded samples per channel.</returns>
public int DecodeFloat(ReadOnlySpan<byte> data, Span<float> pcm, int frameSize)
public int DecodeFloat(ReadOnlySpan<byte> data, Span<float> pcm, int frameSize, bool decodeFec)
{
Comment thread
KubaZ2 marked this conversation as resolved.
return DecodeFloat(data, MemoryMarshal.AsBytes(pcm), frameSize);
return DecodeFloat(data, MemoryMarshal.AsBytes(pcm), frameSize, decodeFec);
}

public void Dispose()
Expand Down
4 changes: 2 additions & 2 deletions NetCord/Gateway/Voice/Streams/OpusDecodeStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public OpusDecodeStream(Stream next, PcmFormat format, VoiceChannels channels, b

private int Decode(ReadOnlySpan<byte> data, Span<byte> pcm)
{
return _decoder.Decode(data, pcm, _frameSize);
return _decoder.Decode(data, pcm, _frameSize, false);
}

private int DecodeFloat(ReadOnlySpan<byte> data, Span<byte> pcm)
{
return _decoder.DecodeFloat(data, pcm, _frameSize);
return _decoder.DecodeFloat(data, pcm, _frameSize, false);
}

public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
Expand Down
Loading