如何修复Python Discord机器人无法停止录制及导出音频的问题
修复Discord语音录制机器人的停止录制与文件生成问题
问题描述
我编写了一段用于Discord机器人的Python代码,实现加入语音频道、启动录制及停止录制的功能,但运行后无法停止录制,也没有生成音频文件:
@client.command() async def join(ctx: commands.Context): channel: discord.VoiceChannel = ctx.author.voice.channel if ctx.voice_client is not None: return await ctx.voice_client.move_to(channel) await channel.connect(cls=NativeVoiceClient) await ctx.invoke(client.get_command('rec')) @client.command() async def rec(ctx): ctx.voice_client.record(lambda e: print(f"Exception: {e}")) embedVar = discord.Embed(title="Started the Recording!", description="use !stop to stop!", color=0x546e7a) await ctx.send(embed=embedVar) @client.command() async def stop(ctx: commands.Context): if not ctx.voice_client.is_recording(): return await ctx.send(f'Stopping the Recording') wav_bytes = await ctx.voice_client.stop_record() name = str(random.randint(000000, 999999)) with open(f'{name}.wav', 'wb') as f: f.write(wav_bytes) await ctx.voice_client.disconnect()
问题原因分析
- 缺少参数校验:未检查用户是否在语音频道、机器人是否已连接,容易触发空指针错误
- 异常处理缺失:录制过程中的异常仅静默打印,无法及时发现问题根源
- 空值未处理:
stop_record可能返回空字节流,直接写入文件会生成无效文件 - 客户端实现问题:自定义
NativeVoiceClient若未正确实现录制逻辑,会导致启停功能失效
修复后的代码
以下是修正后的完整代码,解决了上述问题:
import discord from discord.ext import commands import random @client.command() async def join(ctx: commands.Context): # 检查用户是否在语音频道 if not ctx.author.voice: await ctx.send("你得先加入一个语音频道!") return channel: discord.VoiceChannel = ctx.author.voice.channel if ctx.voice_client is not None: return await ctx.voice_client.move_to(channel) # 连接语音频道(自定义NativeVoiceClient需确保继承discord.VoiceClient且实现正确) await channel.connect(cls=discord.VoiceClient) await ctx.invoke(client.get_command('rec')) @client.command() async def rec(ctx): if not ctx.voice_client: await ctx.send("机器人还没加入语音频道!") return try: # 启动录制,设置异常回调 ctx.voice_client.record(lambda e: print(f"录制异常: {e}")) embedVar = discord.Embed(title="开始录制!", description="使用!stop停止录制!", color=0x546e7a) await ctx.send(embed=embedVar) except Exception as e: await ctx.send(f"启动录制失败: {str(e)}") print(f"启动录制异常: {e}") @client.command() async def stop(ctx: commands.Context): if not ctx.voice_client: await ctx.send("机器人不在语音频道里!") return if not ctx.voice_client.is_recording(): await ctx.send("当前没有在录制!") return await ctx.send("正在停止录制...") try: wav_bytes = await ctx.voice_client.stop_record() if not wav_bytes: await ctx.send("录制内容为空,未生成文件") await ctx.voice_client.disconnect() return # 生成随机文件名 name = f"{random.randint(0, 999999)}.wav" with open(name, 'wb') as f: f.write(wav_bytes) await ctx.send(f"录制完成!文件已保存为: {name}") await ctx.voice_client.disconnect() except Exception as e: await ctx.send(f"停止录制失败: {str(e)}") print(f"停止录制异常: {e}") await ctx.voice_client.disconnect()
关键修复点
- 增加参数校验:提前检查用户和机器人的语音状态,避免无效操作
- 补充异常捕获:在启动和停止录制时捕获异常,同时通知用户并打印日志
- 完善空值处理:检查录制返回的字节流是否为空,避免生成无效文件
- 明确客户端引用:确保使用正确的语音客户端类,自定义
NativeVoiceClient需符合discord.py规范
更优替代方案
如果官方VoiceClient的录制功能不稳定,可以结合ffmpeg实现更可靠的录制,这种方式支持更多音频格式且稳定性更高:
依赖准备
确保服务器已安装ffmpeg,可通过包管理器安装(如apt install ffmpeg或brew install ffmpeg)
示例代码
import discord from discord.ext import commands import random import asyncio @client.command() async def join(ctx: commands.Context): if not ctx.author.voice: await ctx.send("你得先加入一个语音频道!") return channel = ctx.author.voice.channel if ctx.voice_client: return await ctx.voice_client.move_to(channel) await channel.connect() await ctx.invoke(client.get_command('rec')) @client.command() async def rec(ctx): if not ctx.voice_client: await ctx.send("机器人未连接语音频道!") return # 生成随机文件名 filename = f"{random.randint(0, 999999)}.wav" # 启动ffmpeg进程,将PCM音频流转换为WAV文件 process = await asyncio.create_subprocess_exec( 'ffmpeg', '-f', 's16le', '-ar', '48000', '-ac', '2', '-i', '-', filename, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) # 定义音频回调,将收到的PCM数据写入ffmpeg标准输入 def audio_callback(data): try: process.stdin.write(data) except Exception as e: print(f"音频写入异常: {e}") # 开始监听语音频道 ctx.voice_client.listen(audio_callback) # 将录制进程保存到语音客户端,方便stop命令调用 ctx.voice_client.recording_process = process await ctx.send(f"开始录制,文件将保存为`{filename}`,使用!stop停止") @client.command() async def stop(ctx): if not ctx.voice_client or not hasattr(ctx.voice_client, 'recording_process'): await ctx.send("当前没有在录制!") return # 停止监听语音 ctx.voice_client.stop_listening() # 关闭ffmpeg的标准输入,触发进程结束 ctx.voice_client.recording_process.stdin.close() # 等待ffmpeg进程完成 await ctx.voice_client.recording_process.wait() await ctx.send(f"录制完成!文件已保存为`{ctx.voice_client.recording_process.args[-1]}`") # 删除录制进程引用 del ctx.voice_client.recording_process await ctx.voice_client.disconnect()
这个方案通过直接处理语音PCM流,利用ffmpeg完成编码和文件保存,稳定性和兼容性更强,适合生产环境使用。
内容的提问来源于stack exchange,提问作者levi levi
相关产品推荐
相关产品推荐

