You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VoiceNext(DSharpPlus)无报错却无法播放音频问题求助

解决DSharpPlus+FFmpeg+YoutubeExplode语音播放无声音问题

核心错误原因

  • FFmpeg进程未启动:代码仅创建了Process实例,但未调用process.Start()启动转码进程,完全没有音频输出逻辑。
  • FFmpeg参数顺序错误:FFmpeg遵循「输入选项→-i输入源→输出选项→输出目标」的参数规则,原代码把输出选项放在输入源之前,导致转码指令无法被正确解析。
  • 同步流复制未异步处理且未关闭输入:使用同步stream.CopyTo(ffmpegIn)会阻塞线程,且未关闭FFmpeg的标准输入流,导致FFmpeg一直等待输入,无法输出转码后的音频。
  • 未处理FFmpeg错误输出:未开启标准错误重定向,无法排查FFmpeg的转码失败原因。
  • 空引用风险未处理:未检查VoiceNext连接是否存在,若用户未执行/join就调用/play会引发异常。
  • 不必要的dynamic类型:用dynamic接收Stream完全多余,强类型即可。

修正后的完整代码

using DSharpPlus;
using DSharpPlus.Entities;
using DSharpPlus.SlashCommands;
using DSharpPlus.VoiceNext;
using System.Diagnostics;
using YoutubeExplode;
using YoutubeExplode.Videos.Streams;

namespace Nexus
{
    public class MusicCommands : ApplicationCommandModule
    {
        [SlashCommand("join", "Join a voice channel")]
        public async Task JoinCommand(InteractionContext ctx)
        {
            DiscordChannel channel = ctx.Member.VoiceState?.Channel;
            // 检查用户是否在语音频道
            if (channel == null)
            {
                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                    new DiscordInteractionResponseBuilder().WithContent("请先加入一个语音频道!"));
                return;
            }
            await channel.ConnectAsync();
            await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                new DiscordInteractionResponseBuilder().WithContent($"已加入频道:{channel.Name}"));
        }

        [SlashCommand("play", "Play audio source")]
        public async Task PlayCommand(InteractionContext ctx, [Option("ytid", "Youtube ID or link")] string path)
        {
            var vnext = ctx.Client.GetVoiceNext();
            var connection = vnext.GetConnection(ctx.Guild);

            // 检查机器人是否已连接语音频道
            if (connection == null)
            {
                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                    new DiscordInteractionResponseBuilder().WithContent("请先使用/join命令让机器人加入语音频道!"));
                return;
            }

            var transmit = connection.GetTransmitSink();
            // 强类型接收音频流,去掉不必要的dynamic
            Stream audioStream = await GetYoutubeVideoStream(path);
            await audioStream.CopyToAsync(transmit);
            await audioStream.DisposeAsync();

            await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                new DiscordInteractionResponseBuilder().WithContent("播放完成!"));
        }

        [SlashCommand("leave", "Leave a voice channel")]
        public async Task LeaveCommand(InteractionContext ctx)
        {
            var vnext = ctx.Client.GetVoiceNext();
            var connection = vnext.GetConnection(ctx.Guild);

            if (connection != null)
            {
                connection.Disconnect();
                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                    new DiscordInteractionResponseBuilder().WithContent("已离开语音频道!"));
            }
            else
            {
                await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, 
                    new DiscordInteractionResponseBuilder().WithContent("机器人未在任何语音频道中!"));
            }
        }

        private static async Task<Stream> GetYoutubeVideoStream(string ytID)
        {
            var youtube = new YoutubeClient();
            var streamManifest = await youtube.Videos.Streams.GetManifestAsync(ytID);
            var streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate();
            var stream = await youtube.Videos.Streams.GetAsync(streamInfo);

            var process = new Process
            {
                StartInfo =
                {
                    FileName = "ffmpeg",
                    // 修正参数顺序:输入源在前,输出选项在后,符合FFmpeg规则
                    Arguments = @"-i - -ac 2 -ar 48000 -f s16le pipe:1",
                    RedirectStandardOutput = true,
                    RedirectStandardError = true, // 开启错误输出,方便排查问题
                    UseShellExecute = false,
                    CreateNoWindow = true // 不显示FFmpeg控制台窗口
                }
            };

            // 启动FFmpeg转码进程
            process.Start();

            // 异步复制YouTube音频流到FFmpeg输入,避免阻塞线程
            await stream.CopyToAsync(process.StandardInput.BaseStream);
            // 关闭输入流,告诉FFmpeg输入已结束
            process.StandardInput.Close();

            // 异步读取FFmpeg错误日志,用于调试
            _ = Task.Run(async () =>
            {
                var errorLog = await process.StandardError.ReadToEndAsync();
                if (!string.IsNullOrEmpty(errorLog))
                {
                    Console.WriteLine($"FFmpeg Error: {errorLog}");
                }
            });

            return process.StandardOutput.BaseStream;
        }
    }
}

内容的提问来源于stack exchange,提问作者Overo3

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:06:07