- 后端
- 音视频
【免费下载链接】fonoster
🚀 The open-source alternative to Twilio.
Fonoster 是一个开源的、可作为 Twilio 替代方案的可编程电信栈(Programmable Telecommunications Stack),它让企业能够以云上工具的方式,把电话业务与互联网无缝连接。本文以项目 README.md 为主线,结合仓库源码,完整讲解它的核心能力、12 个语音控制原语(Verbs)、可编程语音应用(Voice Application)的编写方法、基于 SDK 的外呼流程,以及基于 Docker 的整套部署方案。
平台概览:从 PBX 到 API 优先的可编程电信栈
Fonoster 的定位非常明确——它是一个API 优先的电信平台。与传统 PBX 设备不同,Fonoster 把"控制通话流程"这件事彻底编程化:开发者不接触交换机配置,而是编写一个监听呼叫请求的 Voice Application,用代码来决定呼叫的走向。
从仓库根目录的 assets/architecture.png 架构图可以看到整个栈的分层结构:
架构中的关键层次包括:
- SIPnet(基于 Routr):负责 SIP 信令的路由与注册,是终端用户 SIP 设备接入的入口;
- Asterisk + RTPEngine:Asterisk 通过 ARI(Asterisk REST Interface)提供呼叫控制,RTPEngine 负责 RTP 媒体流的转发与处理;
- APIServer:以 gRPC 方式向 WebUI 与 SDK 暴露统一 API,并通过 NATS 处理通话事件等异步消息(见 mods/apiserver/src/voice/VoiceDispatcher.ts 与 mods/apiserver/src/events/nats.ts);
- 数据与基础设施:Postgres 存放业务数据,InfluxDB 存放通话统计(详见 compose.yaml 中的服务编排)。
README 中列出的核心特性包括:
- 多租户(Multitenancy)支持;
- 便捷部署 PBX 功能;
- 可编程语音应用(Programmable Voice Applications);
- NodeJS SDK;
- 支持 Amazon S3 存储;
- 使用 Let's Encrypt 保护 API 端点;
- OAuth2 与 JWT 两种认证方式;
- 基于角色的访问控制(RBAC);
- 插件化的命令行工具(见 mods/ctl);
- 支持 Google Speech API。
认识 Voice Application:用代码接管通话流程
Voice Application是 Fonoster 中控制通话流程的服务端程序。它可以组合使用以下任意数量的语音原语(Verbs)来编排通话:
| 原语 | 作用 |
|---|---|
Answer | 接听来电 |
Hangup | 挂断通话 |
Play | 取一个媒体文件的 URL,把声音流式播放给呼叫方 |
PlayDtmf | 取一段 DTMF 序列并播放给对方 |
Say | 取一段文本,合成语音后流式播放结果 |
Gather | 等待 DTMF 或语音事件,并返回结果 |
SGather | 返回一个流,用于后续的 DTMF 与语音识别结果 |
Stream | 建立双向音频流,与呼叫方收发音频 |
Dial | 把呼叫转接给某个 Agent(坐席)或 PSTN 上的号码 |
Record | 录制呼叫方语音,并把音频保存到存储子系统 |
Mute | 让通道停止发送媒体,即静音通道 |
Unmute | 恢复通道的媒体流 |
这些 Verbs 在仓库中都有独立的实现文件,位于 mods/voice/src/verbs 目录,并通过 mods/voice/src/verbs/index.ts 统一导出。Answer、Hangup、PlayDtmf、Record、Stream、StreamGather、Mute、Unmute、StopSay、SetAudioFilters等也在其中,与 README 的清单一一对应。
一个完整的 Voice Application 示例
README 给出了一个可运行的 Voice Application 示例:它问候来电者、通过语音识别收集姓名、再通过按键收集 4 位 PIN 码:
const VoiceServer = require("@fonoster/voice").default; const { GatherSource, VoiceRequest, VoiceResponse } = require("@fonoster/voice"); new VoiceServer().listen(async (req: VoiceRequest, voice: VoiceResponse) => { const { ingressNumber, sessionRef, appRef } = req; // When Answering Machine Detection is enabled, `req.amd` carries an early // verdict for outbound calls: { status: "HUMAN" | "MACHINE" | "UNKNOWN", // confidence, detector, latencyMs }. It is absent when AMD did not run. if (req.amd?.status === "MACHINE") { return voice.hangup(); } await voice.answer(); await voice.say("Hi there! What's your name?"); const { speech: name } = await voice.gather({ source: GatherSource.SPEECH }); await voice.say("Nice to meet you " + name + "!"); await voice.say("Please enter your 4 digit pin."); const { digits } = await voice.gather({ maxDigits: 4, finishOnKey: "#" }); await voice.say("Your pin is " + digits); await voice.hangup(); }); // Your app will live at tcp://127.0.0.1:50061 // and you can easily publish it to the Internet with: // ngrok tcp 50061这个例子展示了几个关键点:
- 事件回调模型:
VoiceServer().listen(handler)注册一个异步处理器,每次来电都会触发; - 请求上下文:回调的第一个参数
req携带ingressNumber(来电号码)、sessionRef(会话引用)和appRef(应用引用); - 同步式编程体验:
await voice.answer()、await voice.say(...)、await voice.gather(...)让复杂的 IVR 流程读起来像顺序代码; - 双模式收集:
gather既支持source: GatherSource.SPEECH的语音识别,也支持maxDigits/finishOnKey的 DTMF 按键收集; - 网络拓扑:应用默认监听
tcp://127.0.0.1:50061,可通过ngrok tcp 50061快速暴露到公网,便于本地联调。
底层原理:Verb 如何通过 gRPC 会话流工作
从源码看,Voice Application 与 Fonoster 后端通过 gRPC 双向流会话交互。核心机制位于 mods/voice/src/verbs/Verb.ts:
- 每个 Verb 继承抽象基类
Verb,其run()方法把请求参数与mediaSessionRef合并后,通过voice.write()写入会话流; - 在写入前,它会注册
StreamEvent.DATA、StreamEvent.END、StreamEvent.ERROR三个监听器:收到与自身期望内容(getExpectedContent)匹配的响应时 resolve 本次 Promise;会话提前结束或传输失败时 reject,避免 Promise 永远悬挂; - 每个 Verb 通过
getValidationSchema()定义自己的参数校验规则,由 mods/voice/src/verbs/validateRequest.ts 执行。例如Gather要求finishOnKey必须是单个0-9*#字符、timeout和maxDigits必须是正整数(见 mods/voice/src/verbs/Gather.ts);Say要求text非空、playbackRef为合法 UUID(见 mods/voice/src/verbs/Say.ts);Play要求url是合法 URL(见 mods/voice/src/verbs/Play.ts);Dial则校验destination、正整数timeout与recordDirection枚举(见 mods/voice/src/verbs/Dial.ts)。
服务端一侧,mods/voice/src/VoiceServer.ts 负责构建 gRPC 服务:默认情况下它会从 identity 服务获取公钥,用createAuthInterceptor对请求做 JWT 鉴权(可通过skipIdentity配置跳过),并把createSession方法绑定到你的 handler 上,同时注册 gRPC 健康检查。
应答机检测(AMD)的早期判定
README 示例中还展示了 Fonoster 的应答机检测(Answering Machine Detection)能力:当外呼启用了 AMD 时,请求对象req.amd会携带一个早期判定结果。仓库中 mods/common/src/voice/voice.ts 定义了对应的类型与枚举:
enum AmdStatus { UNSPECIFIED = "AMD_STATUS_UNSPECIFIED", HUMAN = "HUMAN", MACHINE = "MACHINE", VOICEMAIL = "VOICEMAIL", IVR = "IVR", UNKNOWN = "UNKNOWN" } type Amd = { status: AmdStatus; confidence: number; detector: string; latencyMs: number; };也就是说,req.amd会携带status(判定结果,如HUMAN/MACHINE)、confidence(置信度)、detector(使用的检测器)和latencyMs(检测耗时);当 AMD 未运行时该字段不存在。开发者在代码里通过req.amd?.status === "MACHINE"即可在接通应答机时直接挂断,典型应用场景是外呼营销时跳过机器应答。AMD 模块的独立实现位于 mods/amd,其配置项(如探测时长、超时、最低置信度)在 compose.yaml 中通过AMD_*环境变量暴露。
用 SDK 发起呼叫:API 优先的一等公民
"Everything in Fonoster is an API first",发起呼叫也不例外。README 给出了用 SDK 外呼的完整示例:
const SDK = require("@fonoster/sdk"); async function main(request) { const apiKey = "your-api-key"; const apiSecret = "your-api-secret" const accessKeyId = "WO00000000000000000000000000000000"; const client = new SDK.Client({ accessKeyId }); await client.loginWithApiKey(apiKey, apiSecret); const calls = new SDK.Calls(client); const response = await calls.createCall(request); console.log(response); // successful response } const request = { from: "+18287854037", to: "+17853178070", appRef: "3e61ecb7-a1b6-4a93-84c3-4f1979165bca", // Optional metadata to be sent to the Voice Application metadata: { name: "John Doe", message: "Please call me back." } }; main(request).catch(console.error);这段代码的关键流程与字段如下:
- 凭据:
apiKey/apiSecret用于换取访问令牌,accessKeyId标识调用者所属的工作区/租户(格式类似WO开头的 32 位字符串); - 认证:
client.loginWithApiKey(apiKey, apiSecret)完成登录,SDK 客户端负责维护令牌; - 呼叫请求:
from为发起方号码,to为目标号码,appRef指向处理该呼叫的 Voice Application(对应上文示例里的req.appRef),metadata为可选的透传数据——它会随请求一起送达 Voice Application,可用于传递用户姓名、留言等业务上下文; - 调用链:
Calls.createCall在 mods/sdk/src/Calls.ts 中实现,底层通过 gRPC 调用 apiserver 的呼叫服务;apiserver 一侧的完整实现位于 mods/apiserver/src/calls,呼叫创建后会通过 NATS 发布通话事件,并由 Voice Dispatcher 把会话接入对应的 Voice Application(见 mods/apiserver/src/voice/VoiceDispatcher.ts)。
快速开始与 Docker 部署
README 推荐的入门路径中,自托管部署是第一步。仓库根目录的 compose.yaml 提供了一整套docker compose编排,包含以下服务:
- dashboard:管理后台(WebUI);
- apiserver:业务 API 服务(gRPC 端口 50051),挂载 config/keys 下的公私钥与 config/integrations.json 集成配置;
- autopilot:AI 语音助手服务(端口 50061);
- routr:SIP 信令服务器(暴露 5060 UDP 及 5060-5063,并配置了健康检查);
- rtpengine:RTP 媒体引擎。由于 RTP 需要大范围端口,Docker 下默认只开放 10000-10100 端口区间;README 对应的部署建议是:在 Linux 上改用
network_mode: host并移除 ports 段以获得生产级媒体吞吐(Windows/Mac 不支持 host 网络模式,需用端口段方案); - amd:应答机检测服务;
- asterisk:语音控制(基于 Fonoster 的 asterisk 镜像,配置了 ARI 代理、编解码与 DTMF 模式等环境变量);
- postgres / influxdb:关系型数据库与通话时序数据库;
- nats:异步事件总线;
- envoy:边缘网关,默认暴露 8449 端口;若启用 Let's Encrypt,则需挂载 letsencrypt 目录并把 443 端口映射到 envoy(注意:挂载目录不支持符号链接);
- autoheal:配合 routr 的
autoheal=true标签做崩溃自动重启。
启动前需要准备好config/keys下的公钥/私钥(routr、apiserver、asterisk 等组件通过挂载./config/keys/public.pem完成互信)以及config/integrations.json集成配置文件(示例见 config/integrations.example.json)。部署完成后,即可通过 SDK 或管理后台创建应用、号码与凭据,开始编写自己的第一个 Voice Application。
小结
Fonoster 把"可编程语音"从设备配置解放出来,交付为一段可运行的代码:12 个 Verbs 覆盖接听、播放、合成、收集、转接、录制、静音等全部常见通话操作;VoiceServer().listen()让 IVR、语音机器人、外呼营销等场景都能以顺序代码的方式编写;SDK 的createCall让"发起呼叫"和调用任意云 API 一样简单;而 compose.yaml 的一次性编排则提供了从信令(Routr)、媒体(RTPEngine)、控制(Asterisk/APIServer)到数据(Postgres/InfluxDB)的完整闭环。
如果希望进一步深入,建议继续阅读以下仓库内文档与源码:
- mods/voice/README.md 与 mods/voice/src/VoiceResponse.ts:Voice Application 的完整 API 与响应对象;
- mods/sdk/README.md 与 mods/sdk/src/Calls.ts:SDK 的全部资源类与呼叫接口;
- mods/amd/README.md:应答机检测模块的原理与配置;
- CONTRIBUTING.md:参与贡献的指南。
- 后端
- 音视频
【免费下载链接】fonoster
🚀 The open-source alternative to Twilio.
相关推荐
Fonoster NodeJS SDK终极指南:10个实战场景快速上手开源通信平台
Fonoster NodeJS SDK终极指南:10个实战场景快速上手开源通信平台 Fonoster是一个功能强大的开源通信平台,作为Twilio的替代方案,它
后端音视频Twilio 可编程通信 API 集成实战:用 SMS / MMS / WhatsApp / Voice 构建 B2B SaaS 事务消息与自定义短信流程
Twilio 可编程通信 API 集成实战:用 SMS / MMS / WhatsApp / Voice 构建 B2B SaaS 事务消息与自定义短信流程 本文
AI 技能人工智能WebRTC 信令服务实战:用 socketio-over-nodejs 搭建 Socket.io 信令服务器
WebRTC 信令服务实战:用 socketio over nodejs 搭建 Socket.io 信令服务器 socketio over nodejs 是 W
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考