Skip to content

Commit 9baa245

Browse files
authored
Add new voice guides (#359)
* Add voice overview and voice connection guides * Add glossary to index.md * Add sending voice guide * Add a Voice section project * Update the functional project, add /record for the receiving voice guide * Add an almost finished voice receiving guide * Update voice guides * Fix toc * Improve voice overview guide * Fix compilation error of VoiceSetup * Cleanup * Fix toc names and cleanup "see also" * Add sample.mp3 made by Mewyk * Add native libs to voice example * Fix wording * Update sample sound * Update allowed frame durations and add new Opus applications and add a note guiding how to check which Opus application to use * Normalize "Opus application" * Improve record sample command to flush the input stream * Move troubleshooting to a separate guide * Improve troubleshooting guide * Add "Maintaining Voice Connections" to the voice connection guide * Explain external socket address discovery failure mitigation better * Update timeouts in the guide * Update code examples * Fix overview * Improve voice guides * Add troubleshooting in see also * Add native dependencies note in voice guides and move installing-native-dependencies.md to root directory * Remove the old voice guide * MB -> MiB * Use netcord.dev instead of preview.netcord.dev for a sample sound
1 parent 4a936fd commit 9baa245

33 files changed

Lines changed: 1189 additions & 21 deletions

Documentation/docfx.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
],
3636
"resource": [
3737
{
38-
"files": [ "images/**", "robots.txt" ]
38+
"files": [ "images/**", "sounds/**", "robots.txt" ]
3939
}
4040
],
4141
"overwrite": [

Documentation/guides/basic-concepts/http-interactions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This guide will show you how to receive and handle Discord interactions, like sl
1010

1111
## Required Dependencies
1212

13-
Before you get started, ensure that you've installed the necessary native dependencies. Follow the [installation guide](installing-native-dependencies.md) to set them up.
13+
Before you get started, ensure that you've installed the necessary native dependencies. Follow the [installation guide](../installing-native-dependencies.md) to set them up.
1414

1515
## Setting Up HTTP Interactions in C#
1616

Documentation/guides/basic-concepts/voice.md

Lines changed: 0 additions & 16 deletions
This file was deleted.

Documentation/guides/basic-concepts/installing-native-dependencies.md renamed to Documentation/guides/installing-native-dependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ When using static linking on Windows, you need to link to the static libraries (
5252

5353
<NativeLibrary Include="C:\path\to\libsodium" Condition="$(RuntimeIdentifier.StartsWith('win'))" />
5454
<DirectPInvoke Include="libsodium" />
55-
55+
5656
<NativeLibrary Include="C:\path\to\opus" Condition="$(RuntimeIdentifier.StartsWith('win'))" />
5757
<DirectPInvoke Include="opus" />
5858
</ItemGroup>

Documentation/guides/toc.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,18 @@
2020
href: basic-concepts/http-interactions.md
2121
- name: Sharding
2222
href: basic-concepts/sharding.md
23-
- name: Voice
24-
href: basic-concepts/voice.md
23+
- name: Voice
24+
items:
25+
- name: Overview
26+
href: voice/index.md
27+
- name: Voice Connection
28+
href: voice/voice-connection.md
29+
- name: Sending Voice
30+
href: voice/sending-voice.md
31+
- name: Receiving Voice
32+
href: voice/receiving-voice.md
33+
- name: Troubleshooting
34+
href: voice/troubleshooting.md
2535
- name: Services
2636
items:
2737
- name: Introduction
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Threading.Channels;
2+
3+
using NetCord.Gateway.Voice;
4+
5+
namespace MyBot;
6+
7+
internal static class Examples
8+
{
9+
public static void VoiceReceiveEvent(VoiceClient voiceClient)
10+
{
11+
voiceClient.VoiceReceive += args =>
12+
{
13+
if (voiceClient.Cache.SsrcUsers.TryGetValue(args.Ssrc, out var userId))
14+
Console.WriteLine($"Received audio from user {userId}");
15+
else
16+
Console.WriteLine($"Received audio from unknown user with SSRC {args.Ssrc}");
17+
18+
return default;
19+
};
20+
}
21+
22+
public static void VoiceReceiveEventAsyncHandler(VoiceClient voiceClient)
23+
{
24+
voiceClient.VoiceReceive += args =>
25+
{
26+
// You may consider renting a buffer
27+
// from 'ArrayPool<T>' in a real application
28+
var ownedFrame = args.Frame.ToArray();
29+
30+
return HandleAsync(ownedFrame, args.Ssrc);
31+
32+
static async ValueTask HandleAsync(byte[] frame, uint ssrc)
33+
{
34+
await Task.Delay(100); // Simulate async processing
35+
36+
Console.WriteLine($"Processed audio frame from SSRC {ssrc}");
37+
}
38+
};
39+
}
40+
41+
public static async Task BufferingReceivedAudio(VoiceClient voiceClient)
42+
{
43+
// Configure the channel to drop frames when the buffer is full;
44+
// 1000 frames means from 2.5 to 120 seconds of
45+
// audio depending on the Opus frame duration;
46+
// Dropping frames should generally never happen, but it is a good
47+
// idea to handle it just in case to avoid running out of memory
48+
var channel = Channel.CreateBounded<(byte[] Frame, uint? Timestamp)>(new BoundedChannelOptions(1000)
49+
{
50+
FullMode = BoundedChannelFullMode.DropWrite,
51+
});
52+
53+
var writer = channel.Writer;
54+
55+
voiceClient.VoiceReceive += args =>
56+
{
57+
// You may consider renting a buffer from 'ArrayPool<T>' in a real application
58+
var frame = args.Frame.ToArray();
59+
60+
return writer.WriteAsync((frame, args.Timestamp));
61+
};
62+
63+
await foreach (var (frame, timestamp) in channel.Reader.ReadAllAsync())
64+
Console.WriteLine($"Received audio frame of size {frame.Length} with timestamp {timestamp}");
65+
}
66+
67+
public static void CreateOpusDecodeStream(Stream stream)
68+
{
69+
OpusDecodeStream opusDecodeStream = new(stream,
70+
PcmFormat.Float,
71+
VoiceChannels.Stereo);
72+
}
73+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<ItemGroup>
4+
<ProjectReference Include="..\..\..\..\NetCord\NetCord.csproj" />
5+
</ItemGroup>
6+
7+
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Diagnostics;
2+
3+
using NetCord.Gateway.Voice;
4+
5+
namespace MyBot;
6+
7+
internal static class Examples
8+
{
9+
public static void CreateVoiceStream(VoiceClient voiceClient)
10+
{
11+
var voiceStream = voiceClient.CreateVoiceStream();
12+
}
13+
14+
public static void CreateVoiceStreamWithConfiguration(VoiceClient voiceClient)
15+
{
16+
var voiceStream = voiceClient.CreateVoiceStream(new VoiceStreamConfiguration
17+
{
18+
NormalizeSpeed = false,
19+
FrameDuration = 60,
20+
});
21+
}
22+
23+
public static void CreateOpusEncodeStream(Stream voiceStream)
24+
{
25+
OpusEncodeStream opusEncodeStream = new(voiceStream,
26+
PcmFormat.Float,
27+
VoiceChannels.Stereo,
28+
OpusApplication.Audio);
29+
}
30+
31+
public static void CreateOpusEncodeStreamWithConfiguration(Stream voiceStream)
32+
{
33+
OpusEncodeStream opusEncodeStream = new(voiceStream,
34+
PcmFormat.Float,
35+
VoiceChannels.Stereo,
36+
OpusApplication.Audio,
37+
new OpusEncodeStreamConfiguration
38+
{
39+
Segment = false,
40+
FrameDuration = 2.5f,
41+
});
42+
}
43+
44+
public static async Task FfmpegAsync(OpusEncodeStream opusEncodeStream, string input)
45+
{
46+
using var process = Process.Start(new ProcessStartInfo
47+
{
48+
FileName = "ffmpeg",
49+
ArgumentList =
50+
{
51+
// Input file
52+
"-i", input,
53+
// Output format 32-bit float PCM with native endianness
54+
"-f", BitConverter.IsLittleEndian ? "f32le" : "f32be",
55+
// Sampling rate 48kHz
56+
"-ar", "48000",
57+
// 2 channels (stereo)
58+
"-ac", "2",
59+
// Output to stdout
60+
"pipe:1",
61+
},
62+
RedirectStandardOutput = true,
63+
})!;
64+
65+
var ffmpegOutput = process.StandardOutput.BaseStream;
66+
67+
// Copy the FFmpeg output directly to the OpusEncodeStream
68+
await ffmpegOutput.CopyToAsync(opusEncodeStream);
69+
70+
// Flush the OpusEncodeStream to ensure all data is sent,
71+
// appended by the silence frames to prevent audio interpolation
72+
await opusEncodeStream.FlushAsync();
73+
}
74+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<ItemGroup>
4+
<ProjectReference Include="..\..\..\..\NetCord\NetCord.csproj" />
5+
</ItemGroup>
6+
7+
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using NetCord.Gateway;
2+
using NetCord.Gateway.Voice;
3+
4+
namespace MyBot;
5+
6+
internal static class Examples
7+
{
8+
public static async Task LongerConnectionTimeoutAsync(GatewayClient client,
9+
ulong guildId,
10+
ulong channelId,
11+
VoiceClientConfiguration? configuration)
12+
{
13+
var voiceClient = await client.JoinVoiceChannelAsync(guildId, channelId, configuration, TimeSpan.FromSeconds(15));
14+
}
15+
16+
public static async Task LongerExternalSocketAddressDiscoveryTimeoutAsync()
17+
{
18+
VoiceClientConfiguration configuration = new()
19+
{
20+
ExternalSocketAddressDiscoveryTimeout = TimeSpan.FromSeconds(15),
21+
};
22+
}
23+
24+
public static async Task KeepAliveAsync(VoiceClient voiceClient, CancellationToken cancellationToken)
25+
{
26+
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
27+
28+
while (await timer.WaitForNextTickAsync(cancellationToken))
29+
await voiceClient.SendDatagramAsync(ReadOnlyMemory<byte>.Empty, cancellationToken);
30+
}
31+
}

0 commit comments

Comments
 (0)