最近“谁想要本船长的电话号码”这句话在短视频和评论区刷屏,排队要号码的人能从船头排到船尾。玩梗归玩梗,先别急着要船长的号——真要给你一个电话号码,你能让它自动接听、自动说话、把留言转成文字吗?估计大部分人答不上来。
所以这篇文章换个玩法:不为难船长,自己搭一套“自动接听 + 语音播报 + 留言录音 + 离线转写”的电话机器人原型。整套系统全部基于开源组件:Asterisk 做电话交换中心,SIP 软电话做测试端,Python AGI 脚本做对话逻辑,Vosk 做离线语音识别,espeak-ng 或任意 TTS 引擎做文字转语音。
先给结论:这套东西不吃显卡,一台 2 核 2G 的 Linux 机器就能跑,服务方式以 systemd 为主,可以通过 AMI/ARI 对外提供接口,也能扩展成批量外呼任务。文章会带你把环境准备、安装配置、分机注册、自动应答、录音转写、接口调用完整过一遍,最后附常见问题排查和合规使用边界。跑完这套流程,你手里就有一个真正能“接电话”的号码入口,虽然它叫 1000 分机号,不叫船长专线。
1. 电话机器人原型核心能力速览
先把这套原型的关键规格摆出来,方便你判断要不要继续往下看。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 电话语音应答原型,可扩展为 IVR 菜单、语音留言、自动外呼测试 |
| 核心组件 | Asterisk(软交换)、PJSIP(SIP 协议栈)、Python AGI、Vosk、espeak-ng |
| 主要功能 | 自动接听、语音播报、分机互拨、留言录音、离线语音识别、文本合成语音 |
| 硬件要求 | 2 核 CPU / 2GB 内存起步,不需要 GPU;识别模型越大,内存占用越高 |
| 支持平台 | Linux(Debian / Ubuntu 为例),软电话客户端跨平台 |
| 启动方式 | systemd 服务或前台命令,Asterisk 自带 CLI 管理台 |
| 是否支持 API | 支持,可通过 AGI 内联、AMI 管理器、ARI 提供外部接口 |
| 是否支持批量任务 | 可扩展批量外呼,用脚本读取名单依次呼叫 |
| 网络要求 | 局域网内可用;接运营商中继需实名和相应资质 |
| 适合场景 | 内网通信测试、客服导航原型、通话记录转写分析、语音机器人教学 |
这里先说明:下面所有命令、配置、脚本都是可以修改的模板,包名、路径、密码、模型版本需要按你本机实际环境替换。Asterisk 版本不同,PJSIP 配置字段可能有细微差异,以asterisk -V输出和官方文档为准。
2. 适用场景与使用边界
这个原型能做的事,可以用一条链说明白:电话进来 → 系统接听 → 播放欢迎语 → 用户说话或按键 → 录音保存 → Vosk 离线转写 → 文本交给后续程序处理。
具体能落到这些场景:
- 内网电话实验环境,学习 SIP 注册、呼叫路由和软交换原理。
- 搭建一个简单的自动应答导航,比如“按 1 转人工,按 2 听介绍”。
- 做一个“语音留言箱”,把通话留言存成 wav,再用离线语音识别转成文字。
- 对接本地脚本,实现“打个电话触发一个任务”的自动化演示。
- 作为语音机器人课程或实验的基座,后续可以接大模型做对话。
需要划清楚的边界:
- 这套系统只建议跑在自有设备的测试环境,或已经取得合法使用权的线路上。
- 在中国大陆环境接运营商电话线路,涉及入网、实名、码号资质等问题,个人随意接入外线存在合规风险,测试阶段要避免接入公网中继。
- 严禁用于骚扰电话、诈骗、伪造主叫号码、批量营销等行为。
- 如果录音包含他人声音或个人信息,必须提前告知并取得同意,并做好数据加密和访问控制。
- 批量外呼前必须确认被叫方授权,任何自动化呼叫工具都不能成为骚扰工具。
3. 环境准备与前置条件
开始之前,把下面这些条件先对一遍。
3.1 硬件与系统
- 一台 Linux 机器或虚拟机,推荐 Debian 12 或 Ubuntu 22.04,2 核 CPU、2GB 内存起步。
- 不需要独立显卡,Vosk 小模型在 CPU 上就能完成实时识别。
- 磁盘至少留 10GB,主要给系统、模型文件和录音文件。
- 一台测试手机或电脑,安装 SIP 软电话,用来注册分机并打电话测试。
3.2 网络与端口
这套系统默认监听以下端口:
| 端口 | 协议 | 用途 |
|---|---|---|
| 5060 | UDP | SIP 注册和信令 |
| 10000 - 20000 | UDP | RTP 语音流 |
| 5038 | TCP | AMI 管理接口,仅建议监听本机 |
如果没有特殊要求,防火墙只对这些端口做局域网放行;测试阶段也可以直接把服务绑在内网 IP 上,避免暴露到公网。
3.3 依赖组件清单
- Asterisk:核心电话交换机。
- Python 3 + pip:运行 AGI 脚本和识别脚本。
- Vosk 中文模型:离线语音识别。
- espeak-ng:离线 TTS,语音质量一般但部署简单。
- 软电话客户端:Zoiper、MicroSIP、Linphone 都可以。
4. 安装部署与启动方式
4.1 安装 Asterisk
Debian / Ubuntu 系直接用 apt 安装,是最快的路径:
sudo apt update sudo apt install -y asterisk asterisk-core-sounds-en asterisk -V如果系统源里的 Asterisk 版本偏老,也可以从源码编译安装。生产环境建议编译指定版本,这里为了快速跑通,先用系统包。安装完成后确认版本号,能输出版本说明核心程序已经就位。
4.2 配置 PJSIP 分机
Asterisk 安装好之后,先配置两个测试分机和一个自动应答入口。编辑/etc/asterisk/pjsip.conf:
; pjsip.conf 基础模板 [transport-udp] type=transport protocol=udp bind=0.0.0.0:5060 [1001] type=endpoint context=captain-context disallow=all allow=ulaw allow=alaw auth=1001-auth aors=1001-aor [1001-auth] type=auth auth_type=userpass username=1001 password=captain-secret-1001 [1001-aor] type=aor max_contacts=1 [1002] type=endpoint context=captain-context disallow=all allow=ulaw allow=alaw auth=1002-auth aors=1002-aor [1002-auth] type=auth auth_type=userpass username=1002 password=captain-secret-1002 [1002-aor] type=aor max_contacts=1配置要点:
context对应拨号计划里的上下文名称,两边必须一致。disallow=all加allow=ulaw是稳妥的编解码组合,兼容性最好。password只是演示密码,测试完要改,不要让默认口令留在生产环境。- 如果系统里存在旧版
sip.conf,注意不要混用新旧两套栈,Asterisk 默认优先读取pjsip.conf。
4.3 配置拨号计划
编辑/etc/asterisk/extensions.conf,追加以下内容:
[captain-context] ; 自动应答入口:播放欢迎语,然后进入 AGI 脚本 exten => 1000,1,Answer() same => n,Playback(welcome) same => n,AGI(captain_bot.py) same => n,Hangup() ; 分机互拨 exten => 1001,1,Dial(PJSIP/1001,30) same => n,Hangup() exten => 1002,1,Dial(PJSIP/1002,30) same => n,Hangup()这里把分机互拨和自动应答放在同一个上下文里。1000 是自动应答测试号,1001 和 1002 是两个分机号。Playback(welcome)会播放/var/lib/asterisk/sounds/welcome.wav这个文件,我们需要先把这个 wav 准备好。
4.4 准备语音提示文件
用 espeak-ng 生成一段中文欢迎语:
sudo apt install -y espeak-ng mkdir -p /tmp/captain-tts espeak-ng -v cmn "你好,欢迎致电本船长的电话机器人。请在提示音后留言,我们会把留言转成文字。" -w /tmp/captain-tts/welcome.wav sudo cp /tmp/captain-tts/welcome.wav /var/lib/asterisk/sounds/welcome.wav-v cmn指定中文普通话发音。espeak-ng 音质比较机械,但作为原型验证完全够用。如果觉得音质差,可以换成 Piper 或 Edge-TTS,生成文件后同样放到 sounds 目录即可。
4.5 部署 Python AGI 脚本
Asterisk 的 AGI 脚本默认放在/var/lib/asterisk/agi-bin/。新建/var/lib/asterisk/agi-bin/captain_bot.py:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys def agi_command(cmd): # AGI 协议:命令写到 stdout,响应从 stdin 读 print(cmd, flush=True) resp = sys.stdin.readline().strip() return resp def main(): caller = os.environ.get("agi_callerid", "unknown") # 提示用户开始留言 agi_command("STREAM FILE beep \"\"") # 录音 10 秒,保存为 /tmp/message.wav agi_command("RECORD FILE /tmp/message wav 123456 10 0") agi_command("VERBOSE \"captain bot done, caller=%s\" 1" % caller) if __name__ == "__main__": main()设置执行权限并重启 Asterisk:
sudo chmod +x /var/lib/asterisk/agi-bin/captain_bot.py sudo systemctl restart asterisk这个脚本只做了最基础的一件事:播放提示音,然后录 10 秒留言。到这一步,系统已经具备“自动接电话并录音”的能力。
5. 功能测试与效果验证
部署完成后,按下面顺序从基础到进阶依次验证。
5.1 分机注册测试
在软电话里添加两个账号,分别填入 Asterisk 服务器 IP、分机号 1001 / 1002 和对应密码。注册成功后,Asterisk 侧能看到在线状态:
sudo asterisk -rx "pjsip show endpoints"预期输出里能看到 1001 和 1002 的 AOR/Contact 处于在线状态。如果看不到,先检查软电话填的账号密码是否和pjsip.conf一致,再检查 5060 端口是否放行。
5.2 分机互拨测试
用 1001 拨打 1002,预期 1002 响铃,接听后双方能正常通话并听到声音。
判断标准:
- 响铃时间不超过配置里的 30 秒。
- 通话建立后没有单通或剧烈杂音。
- 挂机后 Asterisk 里没有残留呼叫通道。
如果出现单通,优先查 RTP 端口 10000-20000 是否放行,以及双方编解码是否一致。
5.3 自动应答与留言录音测试
用任意分机拨 1000,预期流程:
- 电话立即被接起。
- 播放欢迎语“你好,欢迎致电本船长的电话机器人……”。
- 播放 beep 提示音。
- 对着话筒说话,持续 10 秒后自动挂机。
挂机后检查录音文件:
ls -lh /tmp/message.wav文件存在且大小不是 0,说明录音链路正常。如果提示音播放失败,检查 sounds 目录下 welcome.wav 权限;如果录音文件没生成,重点看 AGI 脚本有没有执行权限,以及/tmp/message.wav路径是否被系统清理策略影响。
5.4 离线语音转写测试
录音文件拿到了,下一步用 Vosk 把它转成文字。先下载中文小模型,官方模型页面地址会根据版本更新,下面 URL 只是示例:
cd /opt wget https://alphacephei.com/vosk/models/vosk-model-small-cn-0.22.zip unzip vosk-model-small-cn-0.22.zip -d /opt/vosk-models安装 Python 依赖:
pip install vosk写一个独立的转写脚本/opt/captain-transcribe.py:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import json import wave from vosk import Model, KaldiRecognizer model = Model("/opt/vosk-models/vosk-model-small-cn-0.22") wf = wave.open("/tmp/message.wav", "rb") rec = KaldiRecognizer(model, wf.getframerate()) text_parts = [] while True: data = wf.readframes(4000) if len(data) == 0: break if rec.AcceptWaveform(data): result = json.loads(rec.Result()) text_parts.append(result.get("text", "")) final = json.loads(rec.FinalResult()) text_parts.append(final.get("text", "")) print("识别结果:", "".join(text_parts))运行:
python3 /opt/captain-transcribe.py如果识别结果为空或错得离谱,优先检查录音采样率和模型是否匹配,或者换用更大的中文模型。小模型追求速度,识别率靠环境安静程度和发音清晰度保证。
6. 接口 API 与批量任务扩展
原型跑通后,下一步就是考虑怎么把它接进自己的程序里。Asterisk 提供了三种常用方式,按距离从近到远分别是 AGI、AMI、ARI。
6.1 AGI:内联脚本接口
AGI 已经在上面用过了。它的特点是跟随呼叫流程执行,适合做“接听后做什么”的逻辑。缺点是一个通话对应一个脚本进程,复杂逻辑写起来比较繁琐。
6.2 AMI:外部管理接口
AMI 走 TCP 5038 端口,可以让外部程序发命令,比如发起呼叫、查询通道状态、监听事件。先开启 AMI,编辑/etc/asterisk/manager.conf:
[general] enabled = yes port = 5038 bindaddr = 127.0.0.1 [admin] secret = strong-password-here read = all write = all注意bindaddr = 127.0.0.1,只允许本机连接,不要暴露到公网。重启 Asterisk 后,用 Python 脚本通过 socket 发命令:
import socket import time def ami_action(sock, lines): cmd = "\r\n".join(lines) + "\r\n\r\n" sock.sendall(cmd.encode()) time.sleep(1) return sock.recv(65536).decode(errors="ignore") s = socket.create_connection(("127.0.0.1", 5038), timeout=5) print(ami_action(s, [ "Action: Login", "Username: admin", "Secret: strong-password-here", ])) print(ami_action(s, [ "Action: Originate", "Channel: PJSIP/1001", "Context: captain-context", "Exten: 1000", "Priority: 1", "Timeout: 30000", ])) s.close()这段代码的作用是登录 AMI,然后触发 1001 分机拨打 1000 自动应答入口。执行后,1001 分机会振铃,接起后进入之前配置的语音流程。
6.3 批量外呼任务
批量外呼的原理就是用脚本循环调用 AMI 的 Originate。准备一个 CSV 名单:
name,channel 张三,PJSIP/1001 李四,PJSIP/1002Python 批量脚本模板如下:
import csv import socket import time AMI_HOST = "127.0.0.1" AMI_PORT = 5038 AMI_USER = "admin" AMI_PASS = "strong-password-here" def send_ami(sock, lines): cmd = "\r\n".join(lines) + "\r\n\r\n" sock.sendall(cmd.encode()) time.sleep(0.3) def originate(sock, channel): send_ami(sock, [ "Action: Originate", "Channel: %s" % channel, "Context: captain-context", "Exten: 1000", "Priority: 1", "Timeout: 30000", ]) with open("call_list.csv", newline="", encoding="utf-8") as f: rows = list(csv.DictReader(f)) s = socket.create_connection((AMI_HOST, AMI_PORT), timeout=5) send_ami(s, ["Action: Login", "Username: %s" % AMI_USER, "Secret: %s" % AMI_PASS]) for row in rows: originate(s, row["channel"]) print("已发起呼叫:", row["name"]) time.sleep(5) # 避免并发过高,每个呼叫间隔 5 秒 s.close()批量任务最重要的三个工程点是:并发控制、失败重试、完整日志。上面模板里的time.sleep(5)就是最简单的限流方式;生产级系统还要记录每个号码的呼叫结果,失败号码放进重试队列。再次强调,批量外呼只允许应用于已授权的测试线路,严禁用于营销骚扰或其他违法违规用途。
7. 资源占用与性能观察
这套系统不需要 GPU,但不代表可以完全忽略资源。运行过程中重点关注四个指标。
7.1 CPU
- 空闲状态下,Asterisk 本身几乎不占 CPU。
- 呼叫建立时,CPU 会出现短暂峰值。
- espeak-ng 合成语音和 Vosk 转写是 CPU 消耗大户。小模型转写基本能跟上实时语音,但会占用一个核心的大部分算力。
观察命令:
top -p $(pgrep -f asterisk | tr '\n' ',' | sed 's/,$//') 2>/dev/null7.2 内存
Vosk 小模型加载后占用内存不算夸张,但如果你换成大模型,内存占用会明显上升。测试时用:
free -h判断是否够用。如果内存紧张,优先保证不要同时跑多个转写进程。
7.3 通话通道
观察当前活跃通道:
sudo asterisk -rx "core show channels" sudo asterisk -rx "pjsip show channels"并发越高,CPU 和内存压力越大。测试时从 1 路开始,逐步增加到 5 路、10 路,记录每一档的 CPU 和内存变化,就能摸清这台机器的容量上限。
7.4 磁盘
每通留言都会生成 wav 文件,长时间跑测试一定要设置录音保留周期,避免磁盘被写满:
du -sh /tmp/message.wav如果用/tmp存录音,重启后文件会丢,正式使用建议把输出目录改到/var/spool/captain-recordings,并写一个定时清理脚本。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 软电话注册不上 | 端口不通或密码错误 | 检查 UDP 5060,查看asterisk -rx "pjsip show endpoints" | 放行端口,核对分机号和密码 |
| 能注册但互拨不通 | context 配置不一致 | 查看extensions.conf和 endpoint 的 context | 统一改成captain-context |
| 通话建立但听不到声音 | RTP 端口未放行或编解码不一致 | 检查 UDP 10000-20000,查看日志 | 放行 RTP 端口,统一用 ulaw/alaw |
| 有杂音或单通 | 网络抖动或 NAT 未配置 | 查看通话时的丢包统计 | 优先用有线网络,配置 STUN 或 NAT 参数 |
| 欢迎语播放失败 | wav 文件不存在或权限错误 | 查Playback日志 | 确认 welcome.wav 在 sounds 目录且可读 |
| 录音文件没有生成 | AGI 脚本无执行权限 | 查看 AGI 日志 | chmod +x captain_bot.py并重启 Asterisk |
| Vosk 识别结果为空 | 录音格式与模型不匹配 | 检查 wav 采样率 | 统一录音格式,或换大模型 |
| AMI 登录失败 | 密码错误或绑定地址限制 | 检查 manager.conf 和端口 | 确认 admin 密码,bindaddr改为本机 IP |
| 5060 端口被占用 | 其他 VoIP 服务冲突 | `ss -ulpn | grep 5060` |
| 批量呼叫全部失败 | AMI 会话未保持或超时 | 查看脚本报错和 AMI 事件日志 | 增加事件监听,确认 Originate 成功返回 |
排查的第一原则是看日志。Asterisk 日志默认在/var/log/asterisk/messages,启动失败、拨号失败、AGI 报错都会写在这里。遇到问题先tail -n 200 /var/log/asterisk/messages,绝大多数线索都在里面。
9. 最佳实践与使用建议
这套原型跑通只是第一步,真正要稳定用起来,下面这些工程习惯值得从一开始就建立。
9.1 先小参数验证,再扩大规模
第一次测试只用两个分机、一段 10 秒录音、小模型转写。全链路通了,再逐步加并发、换大模型、接外部接口。一上来就堆功能,出问题反而难定位。
9.2 配置和脚本分目录管理
建议建立统一目录:
/opt/captain/ ├── config/ # 备份的 Asterisk 配置 ├── scripts/ # AGI 和 AMI 脚本 ├── recordings/ # 录音输出 ├── transcript/ # 转写文本输出 └── logs/ # 自定义运行日志不要直接改完配置就忘了备份。一套能跑通的最小配置值得单独保存,出问题可以快速回滚。
9.3 录音与转写流程解耦
AGI 脚本只负责录音,转写由独立进程处理。这样即使识别模型加载慢,也不会阻塞通话流程。生产环境可以用消息队列或定时任务扫描录音目录,发现新文件就触发转写。
9.4 接口服务严格限制访问
AMI 只监听 127.0.0.1,密码用强随机字符串,不要复用默认口令。外部程序需要调用时,通过本机 API 服务转发,而不是直接开放 5038 端口。
9.5 合规红线不能碰
再强调一遍:这条线必须守住。
- 只在自己有权的测试设备上运行。
- 接运营商线路必须确认入网资质和实名要求。
- 录音涉及他人声音和个人信息,必须告知、取得同意、加密存储。
- 外呼和批量任务严禁用于骚扰、诈骗、营销轰炸。
- 不要伪造主叫号码,不要尝试绕过运营商监管。
10. 总结与下一步
回到开头那个梗:“谁想要本船长的电话号码?”这个问题到现在也没办法回答,但你已经知道该怎么自己造一个“电话号码入口”了。这套基于 Asterisk + Python + Vosk 的电话机器人原型,最值得尝试的点在于:不吃显卡、配置轻量、全链路都能在局域网里跑通,而且从自动接听、录音、转写再到 AMI 接口调用,每一步都有明确的验证方法。
最先要验证的功能,永远是分机注册和互拨。这个通了,说明电话交换的基础链路没问题,后面加自动应答、录音、转写才有意义。
最容易踩的坑也提前说:UDP 5060 和 RTP 端口没放行、AGI 脚本没有执行权限、Vosk 模型语言和录音格式不匹配。这三个问题占据了大多数新手排错时间。
往后扩展的方向也很明确:把 Vosk 换成更大的模型提升识别率,把 espeak-ng 换成高音质 TTS,把 AMI 脚本改成带事件监听的完整任务队列,甚至可以在会后处理里接一个大语言模型,让“船长”不仅能接电话,还能听懂留言并自动回复。这套原型本身就是一个很好的语音机器人实验基座,建议收藏备用,等需要搭电话自动化流程时直接照着这套思路落地。