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

如何修复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()

问题原因分析

  1. 缺少参数校验:未检查用户是否在语音频道、机器人是否已连接,容易触发空指针错误
  2. 异常处理缺失:录制过程中的异常仅静默打印,无法及时发现问题根源
  3. 空值未处理:stop_record可能返回空字节流,直接写入文件会生成无效文件
  4. 客户端实现问题:自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 23:45:01