目标

从零开始,在一台机器上把 4 个服务全部拉起,拨一通电话验证:用户说话能识别到文本、能听到 TTS 合成的应答。

预计耗时:首次 30 ~ 60 分钟(含模型加载与 FreeSWITCH 安装),熟练后 5 分钟。

环境前提

要求
OS Windows 10/11(推荐 ASR/TTS 服务) + Linux(推荐 FreeSWITCH 服务)
CPU x64,4 核以上
内存 8GB+(模型加载约 1~2GB)
工具 Visual Studio 2022(含 C++ 工作负载)、CMake、Bun 1.x、MySQL 8、FreeSWITCH 1.10.x
网络 各服务之间能互通对应端口

本例为单机部署,全部跑在同一台机器。FS 部署在 Linux + ASR/TTS 部署在 Windows 的常见拓扑见 部署

Step 1 — 安装 FreeSWITCH + mod_audio_fork

1.1 装 FreeSWITCH

参考 FreeSWITCH 官方文档,本项目验证过 Debian 11 / CentOS 7。装好后能 fs_cli 进 FreeSWITCH 终端。

1.2 编译 mod_audio_fork

1
2
3
4
git clone https://github.com/Wangijun/mod_audio_fork.git
cd mod_audio_fork
chmod +x build.sh
sudo ./build.sh all

完成后 mod_audio_fork.so 会安装到 FS 的 mod 目录。

1.3 启用模块

编辑 conf/autoload_configs/modules.conf.xml

1
<load module="mod_audio_fork"/>

或运行时:

1
fs_cli -x "load mod_audio_fork"

验证:

1
fs_cli -x "module_exists mod_audio_fork"

返回 true 即成功。

Step 2 — 启动 ASR 服务

2.1 构建

1
2
cd C:\path\to\ai-voice-platform\sherpa-asr-online-server
.\build.ps1

2.2 检查配置

打开 build\Release\config.json,确认:

1
2
3
4
5
6
7
8
{
"listenHost": "0.0.0.0", // 监听所有网卡
"listenPort": 10096,
"wsPath": "/audio",
"wsSubprotocol": "audio.drachtio.org",
"maxSessions": 16,
"sampleRate": 16000
}

模型路径默认相对解析到 ../models/,无需改。

2.3 启动

1
2
cd build\Release
.\sherpa_asr_online_server.exe

启动成功日志:

1
2
[sherpa_asr_online_server] listening on 0.0.0.0:10096 ...
[sherpa_asr_online_server] service ready; press Ctrl+C to stop.

2.4 验证健康检查

新开一个终端:

1
curl http://127.0.0.1:10096/health

返回:

1
{ "ok": true, "service": "sherpa-asr-online-server", "activeSessions": 0, ... }

Step 3 — 启动 TTS 服务

3.1 构建

1
2
cd C:\path\to\ai-voice-platform\sherpa-tts-server
.\build.ps1

3.2 检查配置

打开 build\Release\config.json

1
2
3
4
5
6
7
{
"listenHost": "0.0.0.0",
"listenPort": 9080,
"publicBaseUrl": "http://<本机IP>:9080",
"workerThreads": 4,
"ttsPoolSize": 2
}

注意

publicBaseUrl 必须填 FreeSWITCH 能访问到的地址,不要用 127.0.0.1(除非 FS 与 TTS 同机)。

3.3 启动

1
2
cd build\Release
.\sherpa_tts_server.exe

启动成功日志:

1
2
[sherpa_tts_server] loaded TTS worker in 1234 ms
[sherpa_tts_server] listening on http://0.0.0.0:9080

3.4 验证合成

1
2
3
curl -s -X POST http://127.0.0.1:9080/tts \
-H "Content-Type: application/json" \
-d '{"text":"测试语音合成","speakerId":0,"speed":1.0}'

返回:

1
{ "wavUrl": "http://...:9080/wav/tts-xxx.wav", "fileName": "...", "sampleRate": 16000, "cached": false }

浏览器打开 wavUrl 能直接播放即正常。

Step 4 — 启动 MySQL + callflow-esl

4.1 准备 MySQL

1
CREATE DATABASE `ai-voice` DEFAULT CHARSET=utf8mb4;

记下连接串:mysql://root:password@127.0.0.1:3306/ai-voice

4.2 安装依赖与初始化 schema

1
2
cd C:\path\to\ai-voice-platform\callflow-esl
bun install

打开 config.json,按你的环境改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"server": { "host": "0.0.0.0", "port": 9911 },
"http": { "host": "0.0.0.0", "port": 9912 },
"freeswitch": {
"host": "<FS 机器 IP>",
"port": 8021,
"password": "ClueCon",
"callbackHost": "<本机 IP,FS 能访问的>",
"callbackPort": 9911
},
"audioFork": {
"wsUrl": "ws://<本机 IP>:10096/audio",
"bugName": "callflow_asr",
"mixType": "mono",
"sampleRate": "16k"
},
"tts": {
"sherpaHttpEndpoint": "http://127.0.0.1:9080/tts",
"playbackTarget": "wav-url"
},
"db": {
"url": "mysql://root:password@127.0.0.1:3306/ai-voice"
}
}

初始化 schema:

1
2
bun run db:generate
bun run db:push

4.3 插入一条测试业务路由

进 MySQL:

1
2
3
4
5
6
7
8
USE `ai-voice`;

INSERT INTO call_businesses (biz_id, business_name)
VALUES ('default-call-business', 'default-call-business');

-- 把任意被叫号码 1000 路由到 default-call-business
INSERT INTO call_business_number_mappings (destination_number, business_id)
SELECT '1000', id FROM call_businesses WHERE biz_id = 'default-call-business';

4.4 启动服务

1
bun run dev

启动日志:

1
2
3
[callflow-esl] ESL TCP listening on 0.0.0.0:9911
[callflow-esl] HTTP listening on 0.0.0.0:9912
[callflow-esl] db connected

Step 5 — 配置 FreeSWITCH dialplan

编辑 conf/dialplan/default.xml(或自己挂一段):

1
2
3
4
5
6
<extension name="route-to-callflow">
<condition field="destination_number" expression="^(.+)$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="socket" data="<callflow-esl 机器 IP>:9911 async full"/>
</condition>
</extension>

重载:

1
fs_cli -x "reloadxml"

Step 6 — 拨打第一通电话

用任意 SIP 客户端(如 Zoiper、MicroSIP)注册到 FreeSWITCH,拨号 1000

观察 callflow-esl 控制台:

1
2
3
4
5
6
[callflow-esl] outbound session uuid=...
[callflow-esl] route business=default-call-business
[callflow-esl] answered
[callflow-esl] speak text="您好..."
[callflow-esl] audio_fork started ws=ws://.../audio
[callflow-esl] hear timeout=30000

接通后说一段话(中文/英文都行),ASR 服务会返回 partial / final 文本,callflow-esl 会用 TTS 把你说的话回放回来(这是 default-call-business 的行为:echo 业务)。

ASR 服务控制台会看到:

1
2
3
[sherpa_asr_online_server] ws-1 websocket session started on worker=0
[sherpa_asr_online_server] ws-1 metadata={"uuid":"...","direction":"inbound"}
[sherpa_asr_online_server] ws-1 final text=你好世界 reason=endpoint

TTS 服务控制台:

1
2
[sherpa_tts_server] generated public/wav/tts-xxx.wav in 234 ms (rtf=0.45)
[sherpa_tts_server] request textLength=4 cached=false elapsedMs=240

第一通电话成功后

恭喜,端到端链路打通了。下一步:

常见拉起问题速查

现象 排查方向
FS 拨号没反应 dialplan socket IP/端口是否正确;callflow-esl 是否监听 0.0.0.0
outbound 连进来立刻断 看 callflow-esl 日志业务路由是否失败(无 biz_id 也无号码映射)
接通但没声音 playbackTarget=wav-url 时检查 FS 能否 HTTP 访问 publicBaseUrl
ASR partial 全是乱码 检查 audioFork 配置 mixType=mono sampleRate=16k,与 ASR 服务一致
ASR 一直没 final audioFork.eventSubscriptionMode"all";看 ASR 端日志是否发出 final
TTS 失败 503 maxQueuedRequests 太低,或者 ttsPoolSize 不够
MySQL 连不上 db.url / db.password / db.database 是否对得上

更完整的排查表在 FAQ