使用TTS从MemoryStream的音频输出到Discord Bot

时间:2018-12-24 21:03:27

标签: c# audio ffmpeg discord discord.net

我正在使用Discord.Net包装器在VS2017中编写Discord Bot。除了主要目标:在语音通道中使用TTS音频输出流之外,我已经可以进行所有工作(解析/发送文本命令,加入语音通道)。

基本上,我正在使用SpeechSynthesizer创建MemoryStream并将其写入Discord机器人。问题是,没有音频。完全没有我一直在关注其他几个答案以及Discord.Net网站上的文档,但似乎找不到找到使之起作用的方法。通过url / file进行音频流传输的文档很丰富,但事实并非如此。

var ffmpeg = CreateProcess("");
            var output = ffmpeg.StandardOutput.BaseStream;
            IAudioClient client;
            ConnectedChannels.TryGetValue(guild.Id, out client);
            var discord = client.CreatePCMStream(AudioApplication.Mixed);


            await output.CopyToAsync(discord);
            await discord.FlushAsync();

以上是我一直使用的示例,该示例是通过ffmpeg从文件中获取的。我看到它只是在流上复制,因此我尝试了各种方法进行以下操作:

IAudioClient client;
            ConnectedChannels.TryGetValue(guild.Id, out client);
            var discord = client.CreatePCMStream(AudioApplication.Mixed);

            var synth = new SpeechSynthesizer();
            var stream = new MemoryStream();
            var synthFormat = new SpeechAudioFormatInfo(
                EncodingFormat.Pcm,
                8000,
                16,
                1,
                16000,
                2,
                null);

            synth.SetOutputToAudioStream(stream, synthFormat);
            synth.Speak("this is a test");

            await stream.CopyToAsync(discord);
            await discord.FlushAsync();

我尝试过更改SpeechAudioFormatInfo属性,更改SpeechSynthesizer上的输出,完全删除了异步调用,几乎所有我想到的事情都没有结果。

我意识到我可以将声音输出到虚拟音频设备,并让另一个帐户/机器人来接听,但这不是本练习的目的。 我还意识到,我可以将输出写入文件并进行流传输,但这会增加处理时间。这些TTS指令很小,永远不会超过5个字,并且由于它们应该是“标注”,因此需要快速一点。

最后,我也无法完全找到使用ffmpeg进行这项工作的方法。我读过的所有内容似乎都表明需要物理资源,而不仅仅是内存流。

所以,我机智了。任何援助将不胜感激。

1 个答案:

答案 0 :(得分:0)

Discord.NET对AudioStreams有点挑剔。每个音频连接只需要一个PCMStream,否则它会做一些奇怪的事情。您可以在语音连接时创建PCMStream,然后调用多个SendAsync发送音频。

如果我没记错的话,您应该可以将TTS流作为媒体(mp3或AAC媒体文件)输出 然后像这样播放TTS音频文件

public async Task SendAsync(float volume, string path, AudioOutStream stream)
{
    _currentProcess = CreateStream(path);
    while (true)
    {
        if (_currentProcess.HasExited)
        { break; }
        int blockSize = 2880;
        byte[] buffer = new byte[blockSize];
        int byteCount;
        byteCount = await _currentProcess.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize);
        if (byteCount == 0)
        { break; }
        await stream.WriteAsync(buffer, 0, byteCount);
     }
    await stream.FlushAsync();
}

然后像这样调用ffmpeg:

private static Process CreateStream(string path)
{
    var ffmpeg = new ProcessStartInfo
    {
        FileName = "ffmpeg",
        Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1",
        UseShellExecute = false,
        RedirectStandardOutput = true
    };
    return Process.Start(ffmpeg);
}