ESP32 + MCP over MQTT:实现智能设备语音交互

本系列教程路线图

本文是《从 0 到 1 打造情感陪伴智能体》系列的第四篇。若你还未阅读前几篇,建议先回顾它们,以便更好地理解本文内容。

篇章 功能 难度
1 整体介绍:背景 + 环境准备 + 设备上线
2 从"命令式控制"到"语义控制":MCP over MQTT 封装设备能力 ★★
3 接入 LLM,实现"自然语言 → 设备控制" ★★
4 语音 I/O:麦克风数据上传 + 语音识别 + 语音合成回放 ★★★
5 人格、情感、记忆:从"控制器"到"陪伴体" ★★★
6 给智能体增加"眼睛":图像采集 + 多模态理解 ★★★

回顾:接入 LLM 实现「自然语言 → 设备控制」

在上一篇文章中,我们构建了从自然语言指令到设备控制调用的完整链路。通过集成大语言模型结合 MCP over MQTT 的通信协议,实现了设备智能体的雏形。这一智能体不仅能「理解」用户输入的文字,还能根据语义生成函数调用并作用于真实设备。然而,纯文本的交互方式在用户体验上仍存在局限,对用户来说并不直观和自然。

本期我们将实现交互方式的全面升级:通过集成语音识别(ASR)与语音合成(TTS)组件,构建完整的语音交互智能体,让用户可以通过语音方式直接与设备进行沟通。

本篇目标:让智能体能「听」能「说」

我们期望实现这样的场景:

  • 你对着智能体说:"我今天有点累了"。
  • ESP32 收集你的语音并上传到云端。
  • AI 理解你的情感,生成关怀式的回应。
  • 智能体用温柔的声音说:"辛苦了,要不要我为你放点轻柔的音乐?"

为此,我们将在原有架构的基础上,新增语音识别与语音合成组件,让智能体具备「听」和「说」的能力。

架构升级与实现路径


全新架构:新增语音识别(ASR)与语音合成(TTS)组件

  1. ESP32 调用麦克风模块,录制用户语音,生成本地音频文件;
  2. ESP32 通过 MQTT 协议把音频数据上传至云端,由 EMQX 数据桥接到 Web Hook,然后由 App 处理;
  3. App 将音频文件发送至 ASR(语音识别)+ TTS(语音合成服务),生成回复语音;
  4. 识别出的文本被传送给大语言模型(LLM)
  5. LLM 理解上下文语义并通过 MCP Over MQTT 调用部署在 ESP32 上的工具,实现设备控制;
  6. App 将生成的语音压缩为 MP3 格式,编码为 Base64 格式后,再次通过 MQTT 将文件下发至 ESP32;
  7. ESP32 播放音频文件,实现完整的语音交互闭环。

这种架构充分发挥了 MQTT 在物联网场景下的轻量通信优势,同时借助云端强大的 LLM、ASR 和 TTS 能力,为终端设备提供了自然、上下文感知的语音交互体验。ESP32 保持设备轻量化,只负责音频采集与播放,核心处理任务则由云端完成,从而兼顾了性能与成本。

由于在上一篇文章中已经实现了通过 MCP 协议控制终端设备的能力(上述架构图中的步骤 4 和 5 ),我们本篇文章的主要精力放在语音处理上。

ESP32:智能语音采集

硬件配置

我们需要配置两套 I2S 接口,一套用于录音,一套用于播放。

录音配置(I2S_NUM_0):

c 复制代码
// 录音引脚配置
#define I2S_REC_BCLK  7    // 时钟信号
#define I2S_REC_LRCL  8    // 左右声道切换
#define I2S_REC_DOUT  9    // 数据输入
// 录音参数
#define I2S_SAMPLE_RATE   8000    // 8kHz采样率,适合语音
#define I2S_SAMPLE_BITS   16      // 16位深度
#define I2S_CHANNEL_NUM   1       // 单声道
#define RECORD_SECONDS    3       // 录音3秒

播放配置(I2S_NUM_1):

c 复制代码
// 播放引脚配置
#define I2S_PLAY_BCLK  2   // 时钟信号
#define I2S_PLAY_LRCL  1   // 左右声道切换  
#define I2S_PLAY_DOUT  42  // 数据输出

智能语音检测算法

问题的关键在于如何让 ESP32 知道什么时候该开始录音,既要节省设备电量,也要确保录音的完整性。

语音能量检测:

c 复制代码
uint32_t calculateAudioEnergy(int16_t *samples, size_t count) {
  uint64_t sum = 0;
  for (size_t i = 0; i < count; i++) {
    int32_t s = samples[i];
    sum += (uint64_t)(s * s);  // 计算音频能量
  }
  return (uint32_t)(sum / count);
}

防误触机制:

c 复制代码
// 滑动平均过滤噪音
static uint32_t energyHistory[5] = {0, 0, 0, 0, 0};
static int historyIndex = 0;
// 需要至少3个样本都超过阈值才触发录音
int highEnergyCount = 0;
for (int i = 0; i < 5; i++) {
  if (energyHistory[i] > ENERGY_THRESHOLD) {
    highEnergyCount++;
  }
}
if (highEnergyCount >= 3 && avgEnergy > ENERGY_THRESHOLD) {
  Serial.println("检测到语音,开始录音!");
  performRecording();
}

状态机管理

为了避免录音和播放冲突,我们用状态机来管理:

c 复制代码
enum SystemState {
  STATE_IDLE,           // 空闲状态(监听语音)
  STATE_RECORDING,      // 正在录音
  STATE_PLAYING,        // 正在播放
  STATE_COOLDOWN        // 播放后冷却期
};

防回声循环:

  • 播放语音后进入冷却期(3秒)。
  • 清空音频缓冲区,避免把回放的声音当成新的语音指令。
  • 连续录音限制,避免无限循环对话。

音频数据上传

录音完成后,ESP32 会生成标准的 WAV 文件并通过 MQTT 上传:

c 复制代码
// 构建 WAV 文件头
typedef struct WAVHeader {
  char riff_header[4];       // "RIFF"
  uint32_t wav_size;         // 文件大小
  char wave_header[4];       // "WAVE"
  char fmt_header[4];        // "fmt "
  uint32_t fmt_chunk_size;   // 格式块大小
  uint16_t audio_format;     // 音频格式(1=PCM)
  uint16_t num_channels;     // 声道数
  uint32_t sample_rate;      // 采样率
  uint32_t byte_rate;        // 字节率
  uint16_t block_align;      // 块对齐
  uint16_t bits_per_sample;  // 位深度
  char data_header[4];       // "data"
  uint32_t data_bytes;       // 数据大小
} WAVHeader;
// 通过 MQTT 发送音频
mqtt_client.publish("emqx/esp32/audio", wav_buffer, wav_size);

Python:AI 情感处理

服务架构

我们用 FastAPI 构建了异步的音频处理服务:

python 复制代码
from fastapi import FastAPI, HTTPException, BackgroundTasks
from openai import AsyncOpenAI
import base64
import lameenc
import numpy as np
app = FastAPI(title="语音情感助手API", version="1.0.0")
# 通义千问客户端
openai_client = AsyncOpenAI(
    api_key="your-dashscope-api-key",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

情感提示词设计

这是让 AI 理解情感的关键:

复制代码
OPENAI_PROMPT = """
在这个会话中,您将作为简单的情感助手,依据我提供的语音,生成精简回复。
请注意回复不能超过 20 个字符并且回复的内容应当是对语音内容的情感分析或回应。
"""

为什么限制 20 字?

  • 让回应更精准,避免啰嗦
  • 减少语音合成和传输时间
  • 更符合情感陪伴的温暖简洁风格

多模态 AI 调用

这是整个系统的核心,直接用语音输入生成语音输出:

python 复制代码
async def call_qwen_ai_generate_audio(base64_audio: str) -> bytes:
    completion = await openai_client.chat.completions.create(
        model="qwen-omni-turbo",  # 多模态模型
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_audio",
                        "input_audio": {
                            "data": f"data:;base64,{base64_audio}",
                            "format": "wav",
                        },
                    },
                    {
                        "type": "text", 
                        "text": "作为情感助手,用不超过20字回应用户的情感需求"
                    }
                ],
            }
        ],
        modalities=["text", "audio"],        # 同时输出文本和音频
        audio={"voice": "Cherry", "format": "wav"},  # 使用 Cherry 音色
        stream=True,                         # 流式处理
        stream_options={"include_usage": True}
    )
    # 接收流式响应
    text_string = ""
    audio_string = ""
    async for chunk in completion:
        if not chunk.choices:
            continue
        if hasattr(chunk.choices[0].delta, "audio"):
            audio_string += chunk.choices[0].delta.audio["data"]
        elif hasattr(chunk.choices[0].delta, "content"):
            text_string += chunk.choices[0].delta.content
    # 返回音频数据
    decoded_audio = base64.b64decode(audio_string)
    return decoded_audio

这种方式的优势:

  • 端到端处理:语音直接到语音,保持情感连贯性
  • 理解更准确:AI 能听出语调、情绪,不只是文字内容
  • 回应更自然:生成的语音带有合适的情感色彩

音频格式优化

AI 生成的是 WAV 格式,文件比较大,我们转成 MP3 压缩:

python 复制代码
def convert_audio_to_mp3(decoded_audio: bytes, output_file: str):
    audio_np = np.frombuffer(decoded_audio, dtype=np.int16)
    encoder = lameenc.Encoder()
    encoder.set_bit_rate(32)        # 低码率,减少传输量
    encoder.set_in_sample_rate(24000)
    encoder.set_channels(1)
    encoder.set_quality(7)          # 平衡质量与大小
    mp3_data = encoder.encode(audio_np.tobytes())
    mp3_data += encoder.flush()
    # 保存文件并返回 Base64
    with open(output_file, 'wb') as f:
        f.write(mp3_data)
    return base64.b64encode(mp3_data).decode()

异步处理流程

为了不阻塞 API 响应,我们用后台任务处理:

python 复制代码
@app.post("/process_audio")
async def process_audio(request: AudioRequest, background_tasks: BackgroundTasks):
    task_id = str(uuid.uuid4())
    # 添加后台任务
    background_tasks.add_task(
        process_audio_task,
        request.audio,
        task_id
    )
    return AudioResponse(
        success=True,
        message="音频处理任务已启动"
    )
async def process_audio_task(base64_audio: str, task_id: str):
    try:
        # 1. 调用 AI 生成语音回应
        decoded_audio = await call_qwen_ai_generate_audio(base64_audio)
        # 2. 转换为 MP3 格式
        output_file = f"audio_response_{task_id}.mp3"
        base64_mp3_audio = convert_audio_to_mp3(decoded_audio, output_file)
        # 3. 通过 MQTT 发送给 ESP32
        await publish_to_mqtt(base64_mp3_audio)
    except Exception as e:
        logger.error(f"处理音频任务异常: {e}")

EMQX:消息代理

Topic 设计

复制代码
emqx/esp32/audio      → ESP32 上传录音
emqx/esp32/playaudio  → 下发播放音频

Webhook 配置

在 EMQX 控制台配置 Webhook 规则,当收到音频消息时自动转发给 Python 服务:

json 复制代码
{
  "url": "http://your-server:5005/process_audio",
  "method": "POST",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "audio": "${payload}"
  }
}

系统部署与配置流程

环境准备

Python 依赖:

复制代码
pip install fastapi uvicorn httpx lameenc numpy openai pydantic

ESP32 库依赖:

  • WiFi:网络连接
  • PubSubClient:MQTT 客户端
  • SPIFFS:文件系统(存储临时音频文件)
  • ESP32Audio:音频编解码库

配置流程

  1. 获取通义千问 API Key

    • 登录阿里云控制台。
    • 开通 DashScope 服务。
    • 获取 API Key。
  2. 配置 EMQX

    • 创建设备认证信息。
    • 配置 Webhook 规则。
    • 设置消息路由。
  3. ESP32 配置

    c 复制代码
    // WiFi 配置
    const char *ssid = "your-wifi-name";
    const char *password = "your-wifi-password";
    // MQTT 配置
    const char *mqtt_broker = "broker.emqx.io";
    const char *mqtt_username = "emqx";
    const char *mqtt_password = "public";
  4. Python 服务配置

    python 复制代码
    # API 配置
    openai_client = AsyncOpenAI(
        api_key="your-dashscope-api-key",
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
    )
    # EMQX 配置
    EMQX_HTTP_API_URL = "http://127.0.0.1:18083"
    EMQX_USERNAME = "your-username"
    EMQX_PASSWORD = "your-password"

实测效果与性能表现

示例对话场景

场景一:情感安慰

  • 用户(疲惫的语调):"我今天工作好累啊..."
  • AI回应(温柔):"辛苦你了,要好好休息哦"

场景二:日常聊天

  • 用户(开心):"今天天气真不错!"
  • AI回应(活泼):"是呀,心情也变好了呢"

场景三:寻求建议

  • 用户(犹豫):"我不知道该怎么办..."
  • AI回应(鼓励):"慢慢来,我们一起想想"

性能表现

响应时间:

  • 语音检测:实时(<100ms)
  • AI处理:1-2 秒
  • 音频下发:< 500ms
  • 总延迟:约 3 秒内完成整个交互

音质表现:

  • Cherry 音色自然温暖
  • MP3 32kbps 音质清晰
  • 支持情感语调变化

现存问题与解决方案

在实际测试中,我们遇到了几个典型问题:

  • 回声循环:ESP32 会把自己播放的声音再次录入,形成无限回放。

我们在播放后增加了 3 秒冷却期,并在每次播放结束后清空 I2S 缓冲区,同时限制连续录音的次数,防止循环放大。

  • 录音误触:环境噪音或空调声导致设备频繁启动录音。

我们通过提高能量阈值、加入滑动平均过滤,以及采用多样本确认机制,有效降低了误触发的概率。

  • 网络断线:WiFi 不稳定时会导致 MQTT 连接中断。

为保证系统稳定运行,我们实现了自动重连机制、心跳检测和连接状态监控,确保设备在网络波动下依然能够可靠工作。

语音交互的局限与优化

局限性

该方案结构清晰、部署便捷,并充分利用了 MQTT 的稳定传输能力,但由于交互模式的批处理特性,仍存在很多明显的局限:

批处理模式导致高延迟

  • 语音采集、LLM 推理、TTS 合成、音频播放必须串行执行,用户需等待整个流程结束。
  • 长语音输入或复杂语义解析时,交互延迟会显著增加。

缺乏流式交互能力

  • ASR 需完整录音后才开始转写,无法实时逐句识别。
  • TTS 必须等待全部文本就绪才生成语音,无法边合成边播放。
  • 音频传输依赖完整文件下发,无法实现流式传输。

固定录音时长限制

  • 3 秒时长可能导致长句被截断。
  • 短句场景下存在无效等待,降低交互效率。

这些因素都限制了方案在生产环境中的实际可用性,与日常智能语音助手的「边说边听、边合成边播放」的体验有明显差距。

优化方向

为了提升语音指令、响应速度和语音交互的自然性,可以从以下几个方向进行优化:

  • 动态录音时长:检测到语音结束自动停止录音;
  • VAD(Voice Activity Detection):更准确的语音端点检测;
  • 音频质量优化:降噪、增强等预处理;
  • ASR 实时语音识别(流式转写);
  • TTS 音频实时生成与播放;
  • MQTT 与 RTP 可协同工作:MQTT 负责可靠的指令和状态交互,RTP 则用于快速传输音频流,使智能体更贴近「实时对话」的体验标准。

下篇预告

在本篇中,我们实现了从自然语言识别到设备播放语音的闭环。然而,一个真正的「智能」体,不止于一般的响应,它还应该懂你、记得你、陪伴你。

下一篇我们将探讨:

  • 如何引入「情感」和「人格」机制:让设备不只是冰冷的指令执行器,而具备温度、风格与情绪表达;
  • 如何让设备「有记忆」:保留用户上下文、偏好和使用习惯,实现个性化长期记忆;
  • 如何结合 LLM 构建人格化代理体:在长期互动中,逐渐演化成「懂你」的数字伙伴。

此外,我们也将讨论不同类型的人设设计,如:安静助理、热情伙伴、严肃专家等,并分析人设如何影响交互体验与应用场景适配。这将是从「设备控制、交互智能体」走向「人格化智能体」的关键一步。敬请期待。

资源

相关推荐
强化学习与机器人控制仿真5 分钟前
字节最新开源模型 DA3(Depth Anything 3)使用教程(一)从任意视角恢复视觉空间
人工智能·深度学习·神经网络·opencv·算法·目标检测·计算机视觉
机器之心20 分钟前
如视发布空间大模型Argus1.0,支持全景图等多元输入,行业首创!
人工智能·openai
Elastic 中国社区官方博客20 分钟前
Elasticsearch:如何创建知识库并使用 AI Assistant 来配置 slack 连接器
大数据·人工智能·elasticsearch·搜索引擎·全文检索·信息与通信
Baihai_IDP22 分钟前
分享一名海外独立开发者的 AI 编程工作流
人工智能·llm·ai编程
油炸小波24 分钟前
02-AI应用开发平台Dify
人工智能·python·dify·coze
机器之心27 分钟前
Gemini 3深夜来袭:力压GPT 5.1,大模型谷歌时代来了
人工智能·openai
菠菠萝宝1 小时前
【Java手搓RAGFlow】-1- 环境准备
java·开发语言·人工智能·llm·openai·rag
AndrewHZ1 小时前
【图像处理基石】如何从动漫参考图中提取色彩风格?
图像处理·人工智能·opencv·pillow·聚类算法·色彩风格·色彩分布
阿里云大数据AI技术1 小时前
PAI Physical AI Notebook详解3:基于仿真的导航模型训练
人工智能
2501_941145851 小时前
深度学习与计算机视觉在工业质检与智能检测系统中的创新应用研究
人工智能·深度学习·计算机视觉