From 688a5e17b344a40ae23550d1aa348651dd4dd457 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 5 Apr 2026 12:26:31 -0700 Subject: [PATCH] docs: add antaf integration plan for ESP32 device Two approaches to replace self-hosted Qwen3-32B with Ant Afu AI: - Plan A: Custom LLM Provider (text API via Frida HTTP Bridge) - Plan B: Full voice passthrough (audio injection via voice bridge) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/antaf-integration-plan.md | 335 +++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 docs/antaf-integration-plan.md diff --git a/docs/antaf-integration-plan.md b/docs/antaf-integration-plan.md new file mode 100644 index 0000000..8468090 --- /dev/null +++ b/docs/antaf-integration-plan.md @@ -0,0 +1,335 @@ +# 蚂蚁阿福接入小智 ESP32 — 实施方案 + +## 项目目标 + +将蚂蚁阿福 App 的 AI 能力接入小智 ESP32 硬件终端,用户通过 ESP32 设备语音对话, +后端对接蚂蚁阿福代替自建 LLM,省去 GPU 资源(两张 RTX 3090 + Qwen3-32B)。 + +--- + +## 系统架构 + +### 方案A:文字接入(自定义 LLM Provider) + +``` +ESP32 设备 PlugAI 服务端 手机 +┌──────────┐ WebSocket ┌──────────────────┐ HTTP/SSE ┌─���────────────┐ +│ 麦克风 │ ──────────────→│ ASR (FunASR) │ │ 蚂蚁阿福 App │ +│ 唤���词 │ │ 语音→文字 │ │ + Frida 注入 │ +│ AEC/NS │ │ │ GET /chat?q= │ │ +│ │ │ AntafLLM Provider│──────────────→│ HTTP Bridge │ +│ │ │ (新增) │←──────────────│ (port 18900) │ +│ │ │ │ SSE 流式回答 │ │ +│ 喇叭 │←───────────────│ TTS (EdgeTTS) │ │ │ +│ │ WebSocket │ 文字→语音 │ │ │ +└──────────┘ └──────────────────┘ └──────────────┘ +``` + +**数据流**: ESP32 音频 → FunASR(语音转文字) → AntafLLM(文���发给阿福) → EdgeTTS(回答转语音) → ESP32 播放 + +### 方案B:语音直通(替代整个 ASR+LLM+TTS) + +``` +ESP32 设备 PlugAI 服务端 手机 +┌──────────┐ WebSocket ┌─────���────────────┐ TCP 二进制 ┌──���───────────┐ +│ 麦克风 │ ──────────────→│ 音频转发模块(新增) │ │ 蚂蚁��福 App │ +│ │ │ Opus解码 │ PCM注入mic │ + Frida ��入 │ +│ │ │ 重采样 24k→48k │──────────────→│ Voice Bridge │ +│ │ │ │ PCM speaker │ (port 18901) │ +│ 喇叭 │←───────────────│ 重采样 24k→24k │←──────────────│ libantaudio │ +│ │ WebSocket │ Opus编码 │ │ │ +└──────────┘ └──────────────────┘ └──────────────┘ +``` + +**数据流**: ESP32 音频 → 解码+重采样 → 注入阿福麦克风 → 阿福完整处理(ASR+LLM+TTS) → 捕获音频 → 编码 → ESP32 播放 + +--- + +## 可行性评估 + +| 维度 | 方案A (文字接入) | 方案B (语音直通) | +|------|-----------------|-----------------| +| 可行性 | **高** | **中低** | +| 实现难度 | 低 (1个Python文件) | 高 (改JS+写转发模块) | +| 改动范围 | 新增 LLM Provider + 改配置 | 改 voice_bridge.js + 新增转发模块 | +| 延迟 | 中 (ASR+网络+TTS 各一轮) | 低 (音频直通) | +| 音质 | EdgeTTS (微软高质量) | 阿福原生 TTS | +| GPU 依赖 | 无 (省掉 Qwen3-32B) | 无 | +| 手机依赖 | 需要 (App+Frida+adb) | 需要 (App+Frida+adb) | +| 核心风险 | 低 | **voice_bridge 当前不支持音频注入** | + +**结论**: 先实施方案A,验证通过后再做方案B。 + +--- + +## 方案A 详细实施 + +### 前置条件 + +| 组件 | 状态 | 说明 | +|------|------|------| +| ESP32 设备 | 已就绪 | 固件已烧录,WiFi+服务端已配置 | +| 小智服务端 | 已就绪 | ws://14.18.247.51:8010 运行中 | +| ASR (FunASR) | 已就绪 | CPU 模式 | +| TTS (EdgeTTS) | 已就绪 | 微软免费 | +| 蚂蚁阿福 HTTP Bridge | 已就绪 | http_bridge_stream.js (port 18900) | +| Frida + 手机 | 需部署 | 手机需连到服务端可达的网络 | + +### 第1步:创建 AntafLLM Provider + +文件路径:`backend/main/xiaozhi-server/core/providers/llm/antaf/antaf.py` + +```python +import requests +from config.logger import setup_logging +from core.providers.llm.base import LLMProviderBase + +TAG = __name__ +logger = setup_logging() + + +class LLMProvider(LLMProviderBase): + """ + 蚂蚁阿福 LLM Provider + 通过 Frida HTTP Bridge (port 18900) 对接蚂蚁阿福 App 的文字对话 API。 + Bridge 运行在手机上,通过 adb forward 或网络暴露 SSE 流式接口。 + """ + + def __init__(self, config): + self.bridge_url = config.get("bridge_url", "http://127.0.0.1:18900") + self.timeout = config.get("timeout", 60) + logger.bind(tag=TAG).info( + f"AntafLLM 初始化: bridge={self.bridge_url}, timeout={self.timeout}s" + ) + + def response(self, session_id, dialogue, **kwargs): + """ + 流式返回蚂蚁阿福的回答。 + 1. 从 dialogue 取最后一条用户消息 + 2. GET {bridge_url}/chat?q={query} + 3. 解析 SSE 流,yield 每个 delta 文本 + """ + # 提取最后一条用户消息 + query = "" + for msg in reversed(dialogue): + if msg.get("role") == "user": + query = msg.get("content", "") + break + + if not query: + logger.bind(tag=TAG).warning("对话中没有用户消息") + yield "抱歉,我没有收到您的问题。" + return + + logger.bind(tag=TAG).info(f"AntafLLM 请求: {query[:50]}...") + + try: + url = f"{self.bridge_url}/chat" + resp = requests.get( + url, + params={"q": query}, + stream=True, + timeout=self.timeout + ) + resp.encoding = "utf-8" + + for line in resp.iter_lines(decode_unicode=True): + if not line: + continue + if line.startswith("data: "): + data = line[6:] # 去掉 "data: " 前缀 + if data == "[DONE]": + break + if data and len(data.strip()) > 0: + yield data + + except requests.exceptions.ConnectionError: + logger.bind(tag=TAG).error("无法连接蚂��阿福 Bridge,请检查手机和 Frida 状态") + yield "抱歉,蚂蚁阿福服务暂时不可用。" + except requests.exceptions.Timeout: + logger.bind(tag=TAG).error(f"蚂蚁阿福 Bridge 超时 ({self.timeout}s)") + yield "抱歉,回答超时了。" + except Exception as e: + logger.bind(tag=TAG).error(f"AntafLLM 异常: {e}") + yield "抱歉,发生了错误。" +``` + +### 第2步:修改服务端配置 + +编辑 `backend/main/xiaozhi-server/data/.config.yaml`: + +```yaml +selected_module: + LLM: antaf # 改为���蚁阿福 + +LLM: + antaf: + type: antaf + bridge_url: http://<手机IP>:18900 # 手机的 HTTP Bridge 地址 + timeout: 60 # SSE 流超时时间 +``` + +也可以保留原来的 Qwen3 配置,方便切换: + +```yaml +LLM: + antaf: + type: antaf + bridge_url: http://<手机IP>:18900 + timeout: 60 + Qwen3: + type: openai + model_name: Qwen3-32B + url: http://127.0.0.1:30000/v1 + api_key: EMPTY +``` + +### 第3步:网络打通 + +手机的 Frida Bridge 端口需要让 PlugAI 服���器能访问到。有两种方式: + +#### 方式1:手机直连局域网(推荐) + +如果手机和 PlugAI 服务器在同一网络(或手机有公网可达 IP): +```bash +# 手机上启动 bridge 后,服务端直接访问 +# bridge_url: http://<手机内网IP>:18900 +curl http://<手机IP>:18900/chat?q=hello +``` + +#### 方式2:adb forward + SSH 隧道 + +手机通过 USB 连接一台中间机器,再通过 SSH 隧道暴露��� +```bash +# 中间机器上 +adb forward tcp:18900 tcp:18900 + +# PlugAI 上建 SSH 隧道 +ssh -L 18900:127.0.0.1:18900 user@中间机器IP +# bridge_url: http://127.0.0.1:18900 +``` + +### 第4步:启动与测试 + +```bash +# 1. 手机端:启动 Frida + HTTP Bridge +frida -U -p -l http_bridge_stream.js + +# 2. 先测 bridge 连通性 +curl -N 'http://<手机IP>:18900/chat?q=你好' + +# 3. PlugAI 服务端:重启小智服务 +cd /home/ZeroStack/xiaozhi/xiaozhi-esp32-server/main/xiaozhi-server +source /home/ZeroStack/xiaozhi/venv/bin/activate +python app.py + +# 4. ESP32 设备:唤醒测试 +# 说 "你好小智" → 提问 → 应该听到蚂蚁阿福的回答(EdgeTTS 合成的语音) +``` + +--- + +## 方案B 详细实施(后续) + +### 核心改造:voice_bridge.js 支持音频注入 + +当前 voice_bridge.js 的 `MFAntAudio3AV2Filter::process` hook 只**读取** micIn 缓冲区。 +需要改造为可以从外部**写入** micIn 缓冲区,替换真实麦克风输入。 + +#### 改造要点 + +```javascript +// voice_bridge.js 新增功能 +var injectBuffer = null; // 外部注入的 PCM 数据 + +// 新增 inject 命令:接收外部 PCM 音频帧 +// 客户端发送: [4字节长度][type=3][960字节PCM数据] +// type 3 = inject audio + +Interceptor.attach(processAddr, { + onEnter: function(args) { + var micIn = args[1]; // 麦克风输入缓冲区 (960 bytes) + var frameSize = args[4]; // 960 + + if (injectBuffer !== null) { + // 用注入数据覆盖真实麦克风输入 + micIn.writeByteArray(injectBuffer); + injectBuffer = null; + } + } +}); +``` + +#### 采样率转换 + +| 来源 | 格式 | 需转换为 | +|------|------|---------| +| ESP32 → 服务端 | Opus 24kHz mono | PCM 48kHz mono (阿福 mic) | +| 阿福 speaker 输出 | PCM 24kHz stereo | Opus 24kHz mono (ESP32) | + +服务端需要: +- libopus 解码/编码 +- resampy 或 scipy 做采样率转换 +- 960字节帧对齐(20ms @ 48kHz) + +#### 新增音频转发模块 + +文件��径:`backend/main/xiaozhi-server/core/providers/asr/antaf_voice/antaf_voice.py` + +这是一个特殊的 ASR Provider,它不做语音识别,而是: +1. 接收 ESP32 的 Opus 音频流 +2. 解码为 PCM,重采样 24k→48k +3. 通过 TCP 发送到 voice_bridge (port 18901) 的 inject 命令 +4. 接收 voice_bridge 的 speaker 输出 +5. 重采样 24k stereo → 24k mono,Opus 编码 +6. 直接发回 ESP32(跳过 LLM 和 TTS) + +#### 方案B 风险点 + +1. **帧时序同步**: ESP32 音频帧和阿福 process() 调用频率可能不一致 +2. **延迟累积**: 网络传输 + 两次重采样 + 注入延迟 +3. **VAD 冲突**: 阿福自带 VAD 可能与注入音频不匹配 +4. **回声消除失效**: 注入 mic 数据后,阿��的 AEC 参考信号(spkRef)对不上 +5. **对话控制**: 何时 open_voice / close_voice 需要与 ESP32 唤醒状态同步 + +--- + +## 依赖清单 + +### 方案A(新增依赖) +- `requests` — Python HTTP 库(服务端 venv 中应已有) + +### 方案B(新增��赖) +- `opuslib` 或 `pyogg` — Opus 编解码 +- `resampy` 或 `scipy.signal` — 采样率转换 +- `numpy` — 音频数据处理 + +--- + +## 文件清单 + +### 方案A +| 操�� | 文件 | +|------|------| +| 新增 | `backend/main/xiaozhi-server/core/providers/llm/antaf/__init__.py` | +| 新增 | `backend/main/xiaozhi-server/core/providers/llm/antaf/antaf.py` | +| 修改 | `backend/main/xiaozhi-server/data/.config.yaml` | + +### 方案B(额外) +| 操作 | 文件 | +|------|------| +| 修改 | `antaf/voice_bridge.js` (新增 inject 命令) | +| 新增 | `backend/main/xiaozhi-server/core/providers/asr/antaf_voice/antaf_voice.py` | +| 新增 | `backend/main/xiaozhi-server/core/utils/audio_resample.py` | + +--- + +## 里程碑 + +| 阶段 | 目标 | 预期产出 | +|------|------|---------| +| M1 | 方案A 代码实现 | AntafLLM Provider + 配置 | +| M2 | 网络打通 | PlugAI ↔ 手机 Bridge 连通 | +| M3 | 端到端测试 | ESP32 唤醒→阿福回答→语音播报 | +| M4 | 方案B 原型 | voice_bridge 音频注入验证 | +| M5 | 方案B 集成 | 全语音直通链路 |