Table of Contents

安装

语音推流需要 Opus 编码器的支持,要使用语音功能,请将 opus 原生库放在 Bot 运行目录内。

.NET Framework 中,请将该原生库放在编译或发布的输出目录中,例如 bin/Debug;.NET (Core) 中,该目录应为 csporj 文件所在目录。

Windows 开发者可以在此处 下载预编译的二进制文件。

Linux 开发者需要从源码编译 Opus,或通过包管理器进行安装。

加入语音频道

语音推流前需要先加入语音频道,调用 IAudioChannel 上的 ConnectAsync,该异步操作会返回一个 IAudioClient 对象,用于后续的语音推流操作。

// The command's Run Mode MUST be set to RunMode.Async, otherwise, being connected to a voice channel will block the gateway thread.
[Command("join", RunMode = RunMode.Async)]
public async Task JoinChannel(IVoiceChannel channel = null)
{
    // Get the audio channel
    channel ??= (Context.User as IGuildUser)?.VoiceChannel;
    // If you need to ensure the user's connected voice channel via Rest
    // if (Context.User is IGuildUser guildUser)
    //     channel ??= (await guildUser.GetConnectedVoiceChannelsAsync()).FirstOrDefault();
    if (channel == null)
    {
        await Context.Channel.SendTextAsync("User must be in a voice channel, or a voice channel must be passed as an argument.");
        return;
    }

    // For the next step with transmitting audio, you would want to pass this Audio Client in to a service.
    _audioClient = await channel.ConnectAsync();
}
Warning

改变语音状态的命令,例如加入或离开音频频道、推流时,应该使用 RunMode.Async,这可以防止在客户端的默认配置中产生死锁的反馈循环。 如果你能确保你的命令在与网关任务不同的任务中运行,那么也可以不需要 RunMode.Async

加入语音频道后,客户端将保持与此频道的连接,直到被踢出频道、掉线、或其它被服务端通知需主动断开连接。

应注意的是,语音连接是基于每个语音频道创建的,对多个语音频道分别调用 ConnectAsync,会创建多个 IAudioClient 的实例。

语音推流

通过 FFmpeg 转码

FFmpeg 是一个开源的、高度多功能的音视频混合工具。这是传输音频前进行转码的推荐方式。

在这之前,你需要安装 FFmpeg CLI,通常的做法是下载一个 FFmpeg 的版本,并将其放置在你的环境变量的 PATH 中(或者与 Bot 在同一位置,与 opus 在同一位置),参见 FFmpeg 的下载页面,或使用操作系统相应的包管理器。

首先,创建一个启动 FFmpeg 的 Process 进程对象,来将输入音频以 PCM 方式转码为 48kHz 采样率的字节流。

using Process ffmpeg = Process.Start(new ProcessStartInfo
{
    FileName = "ffmpeg",
    Arguments = $"""-hide_banner -loglevel panic -i "{source}" -ac 2 -f s16le -ar 48000 pipe:1""",
    UseShellExecute = false,
    RedirectStandardOutput = true,
});

该 ffmpeg 命令的参数中:

  • -hide_banner:用于隐藏启动时的版权和版本信息。
  • -loglevel panic:设置日志级别为 panic,只有最严重的错误才会被记录。
  • -i {source}:指定输入文件或流,{source} 是输入的变量,表示具体的文件路径或网络地址。
  • -ac 2:设置音频通道数量为 2,即立体声。
  • -f s16le:设置输出格式为 16 位有符号小端(Signed 16-bit Little-Endian)PCM(脉冲编码调制)音频。
  • -ar 48000:设置音频采样率为 48kHz。
  • pipe:1:将输出重定向到标准输出,以便在接下来的操作中由程序读取。

由 Kook.Net 进行编码推流

接下来,要向 KOOK 传输音频,需要由 IAudioClient 创建一个 AudioOutStream,由于 ffmpeg 命令输出了 PCM 音频,因此使用 IAudioClient.CreatePcmStream

最后,音频需要从 FFmpeg 的标准输出流传输到你的 AudioOutStream 对象中。 根据你的业务需要,这个步骤中间可能会进行某些处理,但在大多数情况下,使用 Stream.CopyToAsync 即可。

如果你正在实现一个点歌机,你可能会希望等待音频停止播放后再继续播放下一首歌,等待 AudioOutStream.FlushAsync 可以等待音频客户端的内部缓冲区清空。

await using var output = ffmpeg.StandardOutput.BaseStream;
await using var kook = _audioClient.CreatePcmStream(AudioApplication.Voice);
try
{
    await output.CopyToAsync(kook, cancellationToken);
}
finally
{
    await kook.FlushAsync(cancellationToken);
}