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
相关产品推荐
相关产品推荐

