taixf/modules/docs/antaf-integration-plan.md

336 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 蚂蚁阿福接入小智 ESP32 — 实施方案
## 项目目标
将蚂蚁阿福 App 的 AI 能力接入小智 ESP32 硬件终端,用户通过 ESP32 设备语音对话,
后端对接蚂蚁阿福代替自建 LLM省去 GPU 资源(两张 RTX 3090 + Qwen3-32B
---
## 系统架构
### 方案A文字接入自定义 LLM Provider
```
ESP32 设备 PlugAI 服务端 手机
┌──────────┐ WebSocket ┌──────────────────┐ HTTP/SSE ┌─<E2948C><E29480><EFBFBD>────────────┐
│ 麦克风 │ ──────────────→│ ASR (FunASR) │ │ 蚂蚁阿福 App │
│ 唤<><E594A4><EFBFBD>词 │ │ 语音→文字 │ │ + Frida 注入 │
│ AEC/NS │ │ │ GET /chat?q= │ │
│ │ │ AntafLLM Provider│──────────────→│ HTTP Bridge │
│ │ │ (新增) │←──────────────│ (port 18900) │
│ │ │ │ SSE 流式回答 │ │
│ 喇叭 │←───────────────│ TTS (EdgeTTS) │ │ │
│ │ WebSocket │ 文字→语音 │ │ │
└──────────┘ └──────────────────┘ └──────────────┘
```
**数据流**: ESP32 音频 → FunASR(语音转文字) → AntafLLM(文<><E69687><EFBFBD>发给阿福) → EdgeTTS(回答转语音) → ESP32 播放
### 方案B语音直通替代整个 ASR+LLM+TTS
```
ESP32 设备 PlugAI 服务端 手机
┌──────────┐ WebSocket ┌─────<E29480><E29480><EFBFBD>────────────┐ TCP 二进制 ┌──<E29480><E29480><EFBFBD>───────────┐
│ 麦克风 │ ──────────────→│ 音频转发模块(新增) │ │ 蚂蚁<E89A82><E89A81>福 App │
│ │ │ Opus解码 │ PCM注入mic │ + Frida <20><>入 │
│ │ │ 重采样 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("无法连接蚂<E68EA5><E89A82>阿福 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 # 改为<E694B9><E4B8BA><EFBFBD>蚁阿福
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 服<><E69C8D><EFBFBD>器能访问到。有两种方式
#### 方式1手机直连局域网推荐
如果手机和 PlugAI 服务器在同一网络(或手机有公网可达 IP
```bash
# 手机上启动 bridge 后,服务端直接访问
# bridge_url: http://<手机内网IP>:18900
curl http://<手机IP>:18900/chat?q=hello
```
#### 方式2adb forward + SSH 隧道
手机通过 USB 连接一台中间机器,再通过 SSH 隧道暴露<E69AB4><E99CB2><EFBFBD>
```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 <PID> -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
#### 新增音频转发模块
文件<EFBFBD><EFBFBD>`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 monoOpus 编码
6. 直接发回 ESP32跳过 LLM 和 TTS
#### 方案B 风险点
1. **帧时序同步**: ESP32 音频帧和阿福 process() 调用频率可能不一致
2. **延迟累积**: 网络传输 + 两次重采样 + 注入延迟
3. **VAD 冲突**: 阿福自带 VAD 可能与注入音频不匹配
4. **回声消除失效**: 注入 mic 数据后<EFBC8C><E998BF>的 AEC 参考信号spkRef对不上
5. **对话控制**: 何时 open_voice / close_voice 需要与 ESP32 唤醒状态同步
---
## 依赖清单
### 方案A新增依赖
- `requests` — Python HTTP 库(服务端 venv 中应已有)
### 方案B新增<E696B0><E5A29E>
- `opuslib``pyogg` — Opus 编解码
- `resampy``scipy.signal` — 采样率转换
- `numpy` — 音频数据处理
---
## 文件清单
### 方案A
| 操<><E6938D> | 文件 |
|------|------|
| 新增 | `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 集成 | 全语音直通链路 |